Skip to content

Gateway 详解:API 网关

Spring Cloud Gateway 是 Spring 官方基于 WebFlux + Netty 框架开发的 API 网关,使用非阻塞异步 I/O 模型,是替代 Zuul 的下一代网关方案。Gateway 本质上是一个反向代理,所有外部请求先经过它,再由它路由到后端微服务。它的核心价值在于将鉴权、限流、日志、熔断、灰度发布等通用能力统一收敛到网关层,让下游服务只关注业务逻辑。

一、为什么需要网关?

在微服务架构中,网关是所有外部请求的统一入口。没有网关时:

前端 → order-service   (鉴权、限流、日志、熔断、灰度)
前端 → user-service    (鉴权、限流、日志、熔断、灰度)
前端 → product-service (鉴权、限流、日志、熔断、灰度)
每个服务都要重复实现这些通用能力,代码严重重复,升级维护极其困难

有了网关后:

前端 → Gateway (鉴权、限流、日志、熔断、灰度、路由) → order-service   (纯业务逻辑)
                                                    → user-service    (纯业务逻辑)
                                                    → product-service (纯业务逻辑)

网关的核心价值:

序号价值说明组件
1解耦前端后端服务拆分对前端透明,网关做聚合和适配Route + StripPrefix
2统一入口外部请求统一经过网关,统一管理Gateway 本身
3路由转发动态路由、灰度发布、负载均衡Route + Predicate + LoadBalancer
4协议转换HTTP → gRPC,REST → WebSocketNettyRoutingFilter + WebSocket
5可观测性统一日志、链路追踪、指标采集GlobalFilter + Sleuth + Micrometer
6安全防护鉴权、防篡改、防重放、SQL 注入防护、XSS 防护GlobalFilter(自定义鉴权) + SecurityFilter
7流量管控限流、熔断、降级、超时控制RequestRateLimiter + CircuitBreaker + Retry

二、Gateway 架构原理

2.1 基于 WebFlux 的非阻塞架构

Gateway 选择 WebFlux + Netty 而非传统的 Servlet 容器,是因为网关的本质是 I/O 密集型应用——它不执行复杂业务逻辑,主要工作就是接收请求 → 转发请求 → 返回响应。对于 I/O 密集型场景,非阻塞异步模型比阻塞同步模型在资源利用率上有数量级的优势。

Servlet 容器(Tomcat)阻塞模型:
  每个请求占用一个线程,200 个线程 = 最多 200 个并发请求
  线程阻塞在 I/O 等待上,CPU 大量空闲

Netty 非阻塞模型(Gateway):
  少数几个 EventLoop 线程处理所有请求
  请求不阻塞,I/O 事件到达时回调处理
  少量线程即可支撑上万并发连接

核心线程模型:

                    ┌─────────────────────────┐
                    │      Boss EventLoop      │  ← 1 个线程,负责接收 TCP 连接
                    │   (NioEventLoopGroup)    │
                    └────────────┬────────────┘
                                 │ 将连接注册到 Worker
                    ┌────────────▼────────────┐
                    │   Worker EventLoops      │  ← CPU 核数 × 2 个线程
                    │   (NioEventLoopGroup)    │     负责读写、编解码、业务处理
                    └────────────┬────────────┘
                                 │ 事件驱动
                    ┌────────────▼────────────┐
                    │   Reactor Netty          │
                    │   (HttpServer/HttClient) │
                    └─────────────────────────┘

2.2 请求处理全流程

Gateway 的请求处理流程可以拆解为以下步骤:

客户端请求


┌──────────────────────────────────────────────────────────────┐
│ 1. Netty Server 接收 HTTP 请求                                │
│    - 解析 HTTP 请求行、请求头、请求体                           │
│    - 构造 ServerHttpRequest / ServerHttpResponse              │
└──────────────────────────┬───────────────────────────────────┘


┌──────────────────────────────────────────────────────────────┐
│ 2. RoutePredicateHandlerMapping 路由匹配                       │
│    - 遍历所有 Route,依次用 Predicate 匹配请求                  │
│    - 匹配到第一个符合条件的 Route 后停止(短路匹配)              │
│    - 如果所有 Route 都不匹配,返回 404                          │
└──────────────────────────┬───────────────────────────────────┘


┌──────────────────────────────────────────────────────────────┐
│ 3. FilteringWebHandler 执行过滤器链                             │
│    - 加载该 Route 的全部 GatewayFilter(前置过滤)               │
│    - 加载所有 GlobalFilter(前置过滤)                          │
│    - 按 Order 排序后依次执行                                    │
│    - 过滤器链执行完成后,发起对下游服务的请求                     │
└──────────────────────────┬───────────────────────────────────┘


┌──────────────────────────────────────────────────────────────┐
│ 4. NettyRoutingFilter 转发请求                                 │
│    - 根据 Route 的 uri 构造目标地址                             │
│    - lb://order-service → 从注册中心获取实例列表 → 负载均衡选一个  │
│    - 通过 Netty HttpClient 发起非阻塞请求                       │
└──────────────────────────┬───────────────────────────────────┘


┌──────────────────────────────────────────────────────────────┐
│ 5. 接收下游服务响应                                              │
│    - 收到响应后,过滤器链中的后置过滤器执行(倒序)               │
│    - 如:添加响应头、记录日志、收集指标                          │
└──────────────────────────┬───────────────────────────────────┘


┌──────────────────────────────────────────────────────────────┐
│ 6. 将响应写回客户端                                              │
│    - Netty 将响应数据写回客户端连接                              │
└──────────────────────────────────────────────────────────────┘

三、三大核心组件深度解析

┌──────────────────────────────────────────────────────────────┐
│                      Gateway 请求处理流程                       │
│                                                              │
│  请求 ──→ [Predicate 断言] ──→ [Filter 过滤器链] ──→ [Route 路由] ──→ 目标服务
│             匹配请求条件        前置/后置处理         转发目标       │
│                                                              │
│  示例:                                                       │
│  GET /api/order/123                                          │
│    → Path Predicate: /api/order/** 匹配成功                    │
│    → Auth Filter: 校验 JWT Token                              │
│    → RateLimit Filter: 检查 QPS 是否超限                       │
│    → Route: lb://order-service → 转发到订单服务                 │
│    → Log Filter: 记录请求日志                                  │
│    → 响应返回给客户端                                           │
└──────────────────────────────────────────────────────────────┘

3.1 Route(路由)

路由是 Gateway 的基本构建块,包含三个要素:

要素说明
id路由唯一标识,用于日志、监控、管理
uri转发目标地址,支持 lb://(注册中心)、http://(直连)、forward://(内部转发)
predicates断言集合,所有断言都满足时路由才生效
filters过滤器集合,对该路由的请求进行处理
order路由优先级,值越小越优先匹配(默认 0)
metadata自定义元数据,可用于自定义逻辑

uri 的三种形式:

yaml
spring:
  cloud:
    gateway:
      routes:
        # 1. lb:// 从注册中心获取实例(最常用)
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/order/**

        # 2. http:// 直连指定地址(测试、第三方服务)
        - id: external-api
          uri: https://third-party-api.example.com
          predicates:
            - Path=/api/external/**

        # 3. forward:// 内部转发(重定向到网关自身的接口)
        - id: fallback
          uri: forward:/fallback
          predicates:
            - Path=/api/**

路由匹配规则:

Gateway 的路由匹配采用短路优先策略——遍历所有路由定义,返回第一个匹配成功的路由。因此路由的定义顺序很重要,具体规则应该放在前面,通用规则放在后面。

路由匹配伪代码:

for (Route route : routes) {
    if (route.getPredicates().stream().allMatch(p -> p.test(request))) {
        return route;  // 匹配成功,短路返回
    }
}
return null;  // 所有路由都不匹配,返回 404

3.2 Predicate(断言)详解

断言是 Java 8 的 Predicate<ServerWebExchange> 函数式接口,用于判断请求是否满足路由条件。Gateway 内置了 12 种断言工厂。

3.2.1 路径断言:Path

最常用的断言,支持 Ant 风格路径匹配。

yaml
predicates:
  - Path=/api/order/**          # 匹配 /api/order/ 下的所有路径
  - Path=/api/{service}/**      # 路径变量 {service} 可被后续 Filter 引用

Ant 风格通配符:

通配符含义示例
?匹配单个字符/api/order/? 匹配 /api/order/1
*匹配零个或多个字符(不含路径分隔符)/api/*/detail 匹配 /api/order/detail
**匹配零个或多个路径段/api/order/** 匹配 /api/order/a/b/c

3.2.2 方法断言:Method

yaml
predicates:
  - Method=GET,POST          # 只允许 GET 和 POST 请求
  - Method=GET               # 只允许 GET 请求

3.2.3 请求头断言:Header

yaml
predicates:
  - Header=X-Request-Id, \d+           # 必须有 X-Request-Id 头,且值为数字
  - Header=Authorization, Bearer.*     # 必须有 Bearer Token
  - Header=X-Gray, true                # 灰度标识头

3.2.4 请求参数断言:Query

yaml
predicates:
  - Query=version, v2        # 必须包含 version 参数,且值为 v2
  - Query=token              # 必须包含 token 参数,值不限
yaml
predicates:
  - Cookie=sessionId, .+     # 必须包含 sessionId Cookie
  - Cookie=lang, zh-CN       # Cookie lang 必须为 zh-CN

3.2.6 主机名断言:Host

yaml
predicates:
  - Host=**.order.example.com      # 匹配 order.example.com 的所有子域名
  - Host=order.example.com         # 精确匹配

3.2.7 时间断言:After / Before / Between

用于定时发布、活动时间窗口等场景。

yaml
predicates:
  # 2025 年 1 月 1 日 0 点之后才生效
  - After=2025-01-01T00:00:00+08:00
  
  # 2025 年 12 月 31 日 23:59 之前生效
  - Before=2025-12-31T23:59:59+08:00
  
  # 仅在活动期间生效
  - Between=2025-06-01T00:00:00+08:00, 2025-06-18T23:59:59+08:00

3.2.8 权重断言:Weight(灰度核心)

实现流量染色和灰度发布的核心断言。

yaml
spring:
  cloud:
    gateway:
      routes:
        # 80% 流量到稳定版
        - id: order-v1
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
            - Weight=order-group, 80

        # 20% 流量到灰度版
        - id: order-v2
          uri: lb://order-service-gray
          predicates:
            - Path=/api/order/**
            - Weight=order-group, 20

Weight 底层原理: Weight 断言基于哈希取模。对请求进行哈希(默认用请求的 routeId + 随机数),然后对权重总和取模,落在哪个区间就匹配哪个路由。同一组内的所有权重之和必须为 100。

3.2.9 IP 地址断言:RemoteAddr

yaml
predicates:
  - RemoteAddr=192.168.1.0/24                # 只允许内网 IP
  - RemoteAddr=10.0.0.0/8, 172.16.0.0/12    # 允许多个内网段

3.2.10 自定义断言

java
@Component
public class TokenRoutePredicateFactory 
        extends AbstractRoutePredicateFactory<TokenRoutePredicateFactory.Config> {

    public TokenRoutePredicateFactory() {
        super(Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            String token = exchange.getRequest().getHeaders().getFirst("Authorization");
            // 自定义判断逻辑
            return token != null && token.startsWith("Bearer ") && token.length() > 10;
        };
    }

    @Data
    public static class Config {
        // 配置属性
    }
}

使用方式(方法名去掉 RoutePredicateFactory 前缀即为配置名):

yaml
predicates:
  - Token=true

3.3 Filter(过滤器)详解

过滤器分为三种类型:

类型作用范围实现方式
GatewayFilter单个路由通过 filters 配置指定
GlobalFilter全局所有路由实现 GlobalFilter 接口并注册为 Bean
过滤器工厂单个路由(可配置)继承 AbstractGatewayFilterFactory

3.3.1 过滤器执行顺序

过滤器的执行分为前置后置两个阶段。前置过滤器在请求转发到下游服务之前执行,后置过滤器在下游服务返回响应之后执行。

请求 → [前置 Filter 1] → [前置 Filter 2] → [前置 Filter 3]


                                          转发请求到下游服务


响应 ← [后置 Filter 3] ← [后置 Filter 2] ← [后置 Filter 1] ← 客户端

注意:后置过滤器按照与前置相反的顺序执行(类似栈的后进先出)。

Order 优先级: 值越小,越先执行(前置)或越后执行(后置)。

java
@Bean
public GlobalFilter customFilter() {
    return (exchange, chain) -> {
        // === 前置处理 ===
        System.out.println("前置:请求到达");

        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            // === 后置处理 ===
            System.out.println("后置:响应返回");
        }));
    };
}

3.3.2 内置 GatewayFilter 详解

(1)StripPrefix — 去掉路径前缀

最常见的过滤器,去掉网关路由前缀,让下游服务看到的路径与其内部路由一致。

yaml
filters:
  - StripPrefix=1      # /api/order/123 → /order/123
  - StripPrefix=2      # /gateway/api/order/123 → /order/123

(2)PrefixPath — 添加路径前缀

yaml
filters:
  - PrefixPath=/v1       # /order/123 → /v1/order/123

(3)RewritePath — 正则重写路径

yaml
filters:
  # 将 /api/order/123 → /order/123
  - RewritePath=/api/(?<segment>.*), /$\{segment}
  
  # 将 /api/v1/order/123 → /order/v2/123
  - RewritePath=/api/v1/(?<segment>.*), /api/v2/$\{segment}

(4)AddRequestHeader — 添加请求头

yaml
filters:
  - AddRequestHeader=X-Request-Source, gateway        # 固定值
  - AddRequestHeader=X-Request-Time, ${now}           # 动态值
  - AddRequestHeader=X-User-Id, #{@authUtil.getUserId()}  # SpEL 表达式

(5)AddRequestParameter — 添加请求参数

yaml
filters:
  - AddRequestParameter=source, gateway               # 添加 ?source=gateway

(6)RemoveRequestHeader — 移除请求头

yaml
filters:
  - RemoveRequestHeader=X-Internal-Token               # 移除内部 Token,防止泄露
  - RemoveRequestHeader=Cookie                         # 移除 Cookie

(7)SetResponseHeader — 设置响应头

yaml
filters:
  - SetResponseHeader=X-Response-Time, ${now}
  - SetResponseHeader=X-Gateway-Version, 2.0

(8)DedupeResponseHeader — 响应头去重

yaml
filters:
  - DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_FIRST

(9)Retry — 重试

yaml
filters:
  - name: Retry
    args:
      retries: 3                    # 重试 3 次
      statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE  # 这些状态码才重试
      methods: GET, POST            # 只重试 GET 和 POST
      backoff:
        firstBackoff: 100ms         # 第一次重试等待 100ms
        maxBackoff: 500ms           # 最大等待 500ms
        factor: 2                   # 退避因子:100ms → 200ms → 400ms

(10)MapRequestHeader — 请求头映射

yaml
filters:
  - MapRequestHeader=X-Custom-Token, Authorization    # 将 X-Custom-Token 映射为 Authorization

3.3.3 自定义过滤器工厂

java
@Component
public class AuthGatewayFilterFactory 
        extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {

    public AuthGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String token = request.getHeaders().getFirst("Authorization");

            if (token == null || !token.startsWith("Bearer ")) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }

            try {
                // 解析 JWT
                String userId = JwtUtil.parseUserId(token);
                ServerHttpRequest newRequest = request.mutate()
                        .header("X-User-Id", userId)
                        .build();
                return chain.filter(exchange.mutate().request(newRequest).build());
            } catch (Exception e) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
        };
    }

    @Data
    public static class Config {
        private boolean enabled = true;
    }
}

使用方式:

yaml
filters:
  - Auth=true

3.3.4 自定义 GlobalFilter

java
@Component
public class RequestLogFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        String method = request.getMethodValue();
        long startTime = System.currentTimeMillis();

        log.info("请求进入网关: {} {} ", method, path);

        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            long cost = System.currentTimeMillis() - startTime;
            HttpStatus status = exchange.getResponse().getStatusCode();
            log.info("请求完成: {} {} {} {}ms", method, path, status, cost);
        }));
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE; // 最先执行,最后完成
    }
}

3.3.5 内置 GlobalFilter 一览

类名Order作用
RemoveCachedBodyFilter-2147483648清除缓存请求体
AdaptCachedBodyGlobalFilter-2147482648缓存请求体以便重复读取
NettyWriteResponseFilter-1将响应写回客户端(最后执行)
ForwardPathFilter0处理 forward:// 路由
RouteToRequestUrlFilter10000将 Route 的 URI 转为请求 URL
ReactiveLoadBalancerClientFilter10150处理 lb:// 负载均衡
LoadBalancerServiceInstanceCookieFilter10151负载均衡粘性会话
NettyRoutingFilter2147483647发起 HTTP 请求到下游服务
ForwardRoutingFilter2147483647处理 forward:// 转发

自定义 GlobalFilter 的 Order 建议:

  • 鉴权:-100
  • 限流:-50
  • 日志:-200(最先执行)/ 200(最后完成)

四、负载均衡与注册中心集成

Gateway 通过 lb://service-name 与注册中心联动,自动发现服务实例。

4.1 负载均衡策略

Gateway 默认使用 Spring Cloud LoadBalancer 的轮询策略。

yaml
# 切换到随机策略
spring:
  cloud:
    loadbalancer:
      ribbon:
        enabled: false      # 禁用 Ribbon
      configurations:
        order-service:
          loadbalancer:
            health-check:
              path: /actuator/health
java
// 自定义负载均衡策略
@Bean
public ReactorLoadBalancer<ServiceInstance> customLoadBalancer(
        Environment env, LoadBalancerClientFactory factory) {
    String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
    return new RandomLoadBalancer(
            factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}

4.2 服务实例动态刷新

Gateway 与 Nacos 联动:

Gateway 启动

    ├── 注册到 Nacos

    ├── 订阅 order-service、user-service、product-service

    ├── Nacos 推送服务实例列表
    │     order-service: [192.168.1.10:8080, 192.168.1.11:8080]

    ├── 请求到达:GET /api/order/123
    │     → 匹配路由 order-service
    │     → lb://order-service
    │     → LoadBalancer 从实例列表中选择一个
    │     → 转发到 192.168.1.10:8080

    └── 实例变更(order-service 扩容到 3 个实例)
          → Nacos 推送变更事件
          → Gateway 更新本地实例列表
          → 新请求自动感知新实例

五、限流详解

Gateway 内置了基于 Redis 的令牌桶限流算法。

5.1 令牌桶算法原理

令牌桶示意图:

  令牌生成器(每秒 replenishRate 个令牌)


  ┌─────────────┐
  │  令牌桶      │  ← 容量 burstCapacity
  │  [●][●][●]  │     超过容量时丢弃令牌
  └──────┬──────┘


  请求到达 → 取令牌
         ├── 有令牌 → 放行,令牌减 1
         └── 无令牌 → 拒绝(429 Too Many Requests)
参数说明
redis-rate-limiter.replenishRate每秒生成令牌数,即允许的 QPS
redis-rate-limiter.burstCapacity令牌桶容量,允许的瞬时突发流量
redis-rate-limiter.requestedTokens每次请求消耗令牌数,默认 1

示例: replenishRate=10, burstCapacity=20

  • 匀速状态:每秒最多 10 个请求
  • 突发状态:桶中积攒了 20 个令牌,可以瞬间处理 20 个请求(冷启动时)
  • 突发后:桶空了,只能按 10 个/秒的速度处理

5.2 限流配置

yaml
spring:
  redis:
    host: localhost
    port: 6379
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                key-resolver: "#{@userKeyResolver}"

5.3 限流 Key 解析器

java
// 按用户 ID 限流(每个用户独立限流)
@Bean
public KeyResolver userKeyResolver() {
    return exchange -> {
        String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
        return Mono.just(userId != null ? userId : "anonymous");
    };
}

// 按 IP 限流
@Bean
public KeyResolver ipKeyResolver() {
    return exchange -> {
        String ip = Objects.requireNonNull(
                exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
        return Mono.just(ip);
    };
}

// 按接口限流(全局限流)
@Bean
public KeyResolver apiKeyResolver() {
    return exchange -> {
        String path = exchange.getRequest().getURI().getPath();
        return Mono.just(path);
    };
}

// 组合限流:用户 + 接口
@Bean
public KeyResolver combinedKeyResolver() {
    return exchange -> {
        String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
        String path = exchange.getRequest().getURI().getPath();
        return Mono.just((userId != null ? userId : "anonymous") + ":" + path);
    };
}

5.4 限流响应处理

默认限流返回 429,可以自定义响应内容:

java
@Bean
public WebExceptionHandler rateLimitExceptionHandler() {
    return (exchange, ex) -> {
        if (ex instanceof ResponseStatusException 
                && ((ResponseStatusException) ex).getStatusCode() == HttpStatus.TOO_MANY_REQUESTS) {
            exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
            exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
            String body = "{\"code\":429,\"message\":\"请求过于频繁,请稍后再试\"}";
            DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(body.getBytes());
            return exchange.getResponse().writeWith(Mono.just(buffer));
        }
        return Mono.error(ex);
    };
}

六、熔断降级

Gateway 可以集成 Resilience4j 或 Sentinel 实现熔断。

6.1 Resilience4j 熔断

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
yaml
spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - name: CircuitBreaker
              args:
                name: orderCircuitBreaker
                fallbackUri: forward:/fallback/order   # 熔断后跳转

resilience4j:
  circuitbreaker:
    configs:
      default:
        sliding-window-size: 10          # 滑动窗口大小
        minimum-number-of-calls: 5        # 最小调用次数
        failure-rate-threshold: 50        # 失败率阈值 50%
        wait-duration-in-open-state: 10s  # 熔断开启后等待 10s 进入半开
        permitted-number-of-calls-in-half-open-state: 3  # 半开状态允许 3 次调用

熔断状态机:

  CLOSED(关闭)
    │  失败率 ≥ 50%

  OPEN(开启)
    │  等待 10s

  HALF_OPEN(半开)
    ├── 3 次调用都成功 → CLOSED(恢复)
    └── 任意一次失败 → OPEN(重新熔断)

降级接口:

java
@RestController
public class FallbackController {

    @GetMapping("/fallback/order")
    public Mono<Map<String, Object>> orderFallback() {
        return Mono.just(Map.of(
            "code", 503,
            "message", "订单服务暂时不可用,请稍后重试"
        ));
    }
}

6.2 Sentinel 集成

xml
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
yaml
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080    # Sentinel 控制台
      scg:
        fallback:
          mode: response
          response-body: '{"code":429,"message":"被限流了"}'

七、灰度发布

7.1 基于权重的灰度

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: order-stable
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
            - Weight=order, 90
          filters:
            - AddRequestHeader=X-Version, stable

        - id: order-gray
          uri: lb://order-service-gray
          predicates:
            - Path=/api/order/**
            - Weight=order, 10
          filters:
            - AddRequestHeader=X-Version, gray

7.2 基于请求头的灰度

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: order-stable
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
            - Header=X-Gray, false

        - id: order-gray
          uri: lb://order-service-gray
          predicates:
            - Path=/api/order/**
            - Header=X-Gray, true

客户端在请求头中携带 X-Gray: true 即可命中灰度版本。这种方式适合内部测试人员或 VIP 用户优先体验新功能。

7.3 基于注册中心的灰度(Nacos 元数据)

yaml
# 灰度实例的 Nacos 元数据
spring:
  cloud:
    nacos:
      discovery:
        metadata:
          version: gray
java
// 自定义负载均衡,优先路由到同版本实例
@Bean
public ReactorLoadBalancer<ServiceInstance> versionLoadBalancer(
        Environment env, LoadBalancerClientFactory factory) {
    String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
    return new VersionBasedLoadBalancer(
            factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}

八、跨域配置(CORS)

Gateway 统一处理跨域,下游服务无需各自配置。

yaml
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOriginPatterns: "*.example.com"    # 允许的域名模式
            allowedMethods: "*"                        # 允许的方法
            allowedHeaders: "*"                        # 允许的请求头
            allowCredentials: true                     # 允许携带 Cookie
            maxAge: 3600                               # 预检请求缓存时间(秒)

CORS 原理简述:

跨域请求流程:

1. 预检请求(OPTIONS)
   浏览器 → OPTIONS /api/order/123
              Origin: https://app.example.com
              Access-Control-Request-Method: POST
   网关   → 200 OK
              Access-Control-Allow-Origin: https://app.example.com
              Access-Control-Allow-Methods: GET,POST,PUT,DELETE
              Access-Control-Allow-Headers: Content-Type,Authorization

2. 实际请求
   浏览器 → POST /api/order/123
              Origin: https://app.example.com
   网关   → 转发到 order-service
   order-service → 响应
   网关   → 添加 Access-Control-Allow-Origin 头
              → 返回给浏览器

九、Gateway vs Zuul

对比维度Spring Cloud GatewayZuul 1.xZuul 2.x
底层框架WebFlux + NettyServlet 3.0(Tomcat)Netty
I/O 模型非阻塞异步阻塞同步非阻塞异步
线程模型少量 EventLoop 线程一个请求一个线程事件驱动
并发能力数万连接 / 少量线程数百连接 / 数百线程数万连接 / 少量线程
性能(QPS)高(约 3 倍于 Zuul 1.x)
开发体验函数式路由,YAML 配置Filter 链式编程Filter 链式编程
Spring 官方支持否(已停止维护)否(已停止维护)
WebSocket原生支持需要额外配置支持
动态路由支持需自建路由表支持
社区活跃度活跃不活跃不活跃

十、动态路由

生产环境中,路由规则可能需要动态变更(如临时封禁某个接口、新增路由),无需重启网关。

java
@RestController
@RequestMapping("/gateway/routes")
public class RouteController {

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    @Autowired
    private ApplicationEventPublisher publisher;

    // 动态添加路由
    @PostMapping("/add")
    public Mono<String> addRoute(@RequestBody RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        publisher.publishEvent(new RefreshRoutesEvent(this));
        return Mono.just("路由添加成功");
    }

    // 动态删除路由
    @DeleteMapping("/delete/{routeId}")
    public Mono<String> deleteRoute(@PathVariable String routeId) {
        routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
        publisher.publishEvent(new RefreshRoutesEvent(this));
        return Mono.just("路由删除成功");
    }

    // 查看所有路由
    @GetMapping("/list")
    public Flux<RouteDefinition> listRoutes() {
        // 需要注入 RouteDefinitionLocator
        return routeDefinitionLocator.getRouteDefinitions();
    }
}

动态路由适用的场景:

  • 临时封禁某个接口(添加一个返回 403 的路由)
  • 新服务上线,无需重启网关即可添加路由
  • 灰度发布中动态调整流量比例
  • A/B 测试路由切换

十一、生产环境最佳实践

11.1 高可用部署

               ┌──────────────┐
               │   DNS / VIP   │
               └──────┬───────┘

              ┌───────┼───────┐
              │       │       │
        ┌─────▼──┐ ┌─▼──────┐ ┌─────▼──┐
        │Gateway1│ │Gateway2│ │Gateway3│
        │ :8080  │ │ :8080  │ │ :8080  │
        └────┬───┘ └───┬────┘ └───┬────┘
             │          │          │
             └──────────┼──────────┘

              ┌─────────▼─────────┐
              │   Nacos 集群      │ ← 服务发现
              └─────────┬─────────┘

        ┌───────────────┼───────────────┐
        │               │               │
   ┌────▼────┐   ┌─────▼────┐   ┌─────▼────┐
   │ Order   │   │  User    │   │ Product  │
   │ Service │   │ Service  │   │ Service  │
   └─────────┘   └──────────┘   └──────────┘

11.2 超时配置

yaml
spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 2000          # 连接超时 2s
        response-timeout: 10s          # 响应超时 10s
        pool:
          max-connections: 500         # 最大连接数
          max-idle-time: 30s           # 空闲连接最大存活时间
          max-life-time: 60s           # 连接最大生命周期
          acquire-timeout: 5000        # 等待连接超时 5s

11.3 请求体大小限制

yaml
spring:
  codec:
    max-in-memory-size: 10MB          # 请求体最大 10MB,防止 OOM

11.4 安全防护

java
@Component
public class SecurityFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();

        // 1. SQL 注入防护
        String query = request.getURI().getQuery();
        if (query != null && containsSqlInjection(query)) {
            return reject(exchange, "非法请求参数");
        }

        // 2. XSS 防护
        // 在请求头中设置 XSS 过滤标记

        // 3. IP 黑名单
        String ip = getClientIp(request);
        if (isBlacklisted(ip)) {
            return reject(exchange, "IP 已被封禁");
        }

        return chain.filter(exchange);
    }

    private boolean containsSqlInjection(String input) {
        String[] patterns = {"drop ", "delete ", "insert ", "update ", "select ", "union ", "exec "};
        String lower = input.toLowerCase();
        for (String p : patterns) {
            if (lower.contains(p)) return true;
        }
        return false;
    }

    private Mono<Void> reject(ServerWebExchange exchange, String msg) {
        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
        byte[] bytes = ("{\"code\":403,\"message\":\"" + msg + "\"}").getBytes();
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
        exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
        return exchange.getResponse().writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return -200; // 在鉴权之前执行
    }
}

11.5 常见问题

Q:Gateway 适合做聚合层吗?

不适合。Gateway 是非阻塞的,但聚合操作(如并行调用多个服务、合并结果)会让 Gateway 承担业务逻辑,违背网关「轻量级」原则。聚合层应放在 BFF(Backend For Frontend)层。

Q:Gateway 能处理文件上传吗?

可以,但需要配置 max-in-memory-size。大文件上传建议走直连或 CDN 上传后回调。

Q:Gateway 和 Nginx 的区别?

维度GatewayNginx
定位应用层网关(L7)通用反向代理(L4/L7)
动态路由原生支持需 OpenResty/Lua
服务发现与注册中心联动需自建或插件
限流Redis 令牌桶内置 limit_req
灰度发布原生支持需 Lua 脚本
性能高(Java/Netty)极高(C)
适用场景微服务架构所有场景

常见组合: Nginx(前端 + 静态资源 + SSL 终止) → Gateway(微服务路由 + 鉴权 + 限流)

Q:文件上传 max-in-memory-size 配置不生效?

Spring Cloud Gateway 默认使用 DefaultDataBufferFactory 而非 NettyDataBufferFactory,在某些版本中 spring.codec.max-in-memory-size 可能不生效。可以显式配置:

java
@Bean
public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {
    NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
    factory.addServerCustomizers(server -> 
        server.httpRequestDecoder(spec -> spec.maxHeaderSize(8192)));
    return factory;
}

Q:Gateway 如何做链路追踪?

添加 spring-cloud-starter-sleuth 依赖,Gateway 会自动生成 TraceId 和 SpanId,并在转发请求时透传给下游服务。无需额外代码。

Q:Gateway 如何排查路由匹配问题?

yaml
logging:
  level:
    org.springframework.cloud.gateway: DEBUG

开启 DEBUG 日志后,可以看到每个请求匹配了哪个路由、执行了哪些过滤器。