Skip to content

Spring Cloud Gateway 使用指南 ​

Spring Cloud Gateway 是 Spring Cloud 官方推出的第二代 API 网关,基于 Spring WebFlux(Reactor + Netty)构建,异步非阻塞,性能远超第一代网关 Zuul。

适用版本:Spring Cloud Gateway 3.1.x(Spring Cloud 2021.0.x + Spring Boot 2.7.x)

一、什么是 API 网关? ​

网关在微服务中的角色 ​

在微服务架构中,API 网关是所有客户端请求的统一入口。它不是某个具体业务功能的实现者,而是一层"大门"——把所有的外部请求集中到一个地方处理,再转发到相应的后端服务。

text
客户端(浏览器 / App)
        │
        ▼
 ┌──────────────┐
 │  API Gateway │  ← 统一入口:认证、路由、限流、日志
 └──────┬───────┘
        │ 路由转发
  ┌─────┼─────┐
  ▼     ▼     ▼
┌────┐┌────┐┌────┐
│用户││订单││商品│  ← 微服务
└────┘└────┘└────┘

网关承担的核心职责:

职责说明
路由转发根据请求路径、Header 等将请求转发到对应的微服务
负载均衡结合 LoadBalancer 将流量均匀分发到多个实例
身份认证在网关层统一校验 Token/JWT,避免每个微服务各自认证
限流熔断控制流量速率,防止瞬时高并发打垮后端服务
日志监控统一记录请求日志、耗时、状态码,方便排查问题
跨域处理统一配置 CORS,避免每个服务各自处理跨域
协议转换对外暴露 HTTP/HTTPS,内部可能走 gRPC 或其他协议

Gateway vs Zuul ​

Spring Cloud Gateway 是 Zuul 的替代方案,两者对比:

对比维度Spring Cloud GatewayZuul 1.x
底层框架Spring WebFlux(Reactor + Netty)Servlet 2.5(阻塞 IO)
IO 模型异步非阻塞同步阻塞
性能高吞吐、低资源占用连接数较多时性能急剧下降
长连接支持原生支持(WebSocket 等)需要额外配置
限流内置 RequestRateLimiter需自行实现
动态路由配合 Nacos 等自刷新需配合 Archaius
Spring Boot 2.7.x原生支持,当前推荐已停止维护

Zuul 1.x 基于 Servlet 2.5,使用阻塞 IO。每个请求占用一个线程,线程池耗尽后后续请求会被阻塞。Gateway 基于 Reactor 的异步非阻塞模型,少量线程即可处理大量并发连接,两者性能差距可达数倍。


二、核心概念 ​

Spring Cloud Gateway 的工作机制可以用一句话概括:请求到达 Gateway → 匹配路由(Route)→ 执行断言(Predicate)→ 经过过滤器链(Filter Chain)→ 转发到后端服务。

text
请求 → Gateway → Route 匹配 → Predicate 断言 → Filter 链 → 后端服务
                      │               │              │
                      │ - id         │ - Path       │ - AddRequestHeader
                      │ - uri        │ - Header     │ - StripPrefix
                      │ - predicates │ - Method     │ - RateLimiter
                      │ - filters    │ - ...        │ - ...

Route(路由) ​

路由是网关的基本构造块。每条路由包含四要素:

要素说明示例
id路由唯一标识user-service-route
uri目标服务地址lb://user-service
predicates匹配规则(可多个,全部满足才匹配)Path=/api/user/**
filters对请求/响应的处理操作StripPrefix=1
yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route          # 路由 ID
          uri: lb://user-service          # 目标:走负载均衡
          predicates:
            - Path=/api/user/**           # 匹配 /api/user/** 的请求
          filters:
            - StripPrefix=1               # 去掉第一段路径(/api)

以上配置的含义:当请求路径以 /api/user/ 开头时,去掉第一段 /api,然后将剩余路径转发到 user-service。

例如:GET /api/user/profile → 断言匹配 → StripPrefix=1 去掉 /api → 转发到 lb://user-service/user/profile

Predicate(断言) ​

Predicate 源自 Java 8 的 java.util.function.Predicate,即"如果满足条件,则返回 true"。在 Gateway 中,只有当所有断言都返回 true 时,请求才会被路由到对应的 uri。

一条路由可以配置多个断言,它们之间是AND关系,全部满足才会匹配。

Filter(过滤器) ​

Filter 可以对请求(进入后端服务前)和响应(返回客户端前)进行修改。Gateway 提供了两套过滤器:

类型接口作用范围
GatewayFilter路由级过滤器作用在特定路由上,通过 filters 配置
GlobalFilter全局过滤器对所有路由生效,不需要显式配置

三、快速开始 ​

3.1 创建网关模块 ​

text
gateway-service/
├── pom.xml
└── src/main/
    ├── java/com/example/gateway/
    │   └── GatewayApplication.java
    └── resources/
        ├── bootstrap.yml
        └── application.yml

3.2 Maven 依赖 ​

xml
<!-- pom.xml -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.18</version>
</parent>

<properties>
    <spring-cloud.version>2021.0.9</spring-cloud.version>
    <spring-cloud-alibaba.version>2021.0.6.0</spring-cloud-alibaba.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- Spring Cloud Gateway 核心依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

    <!-- Nacos 注册中心 + 配置中心 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

    <!-- Bootstrap 上下文支持(Spring Boot 2.4+ 必须显式引入) -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>

    <!-- Spring Cloud LoadBalancer -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
</dependencies>

Gateway 与 spring-boot-starter-web 不能共存

Spring Cloud Gateway 基于 WebFlux,而 spring-boot-starter-web 基于 Servlet 容器。两者同时引入会导致冲突,启动报错。如果模块中已有 spring-boot-starter-web,必须移除。

3.3 配置文件 ​

yaml
# bootstrap.yml
spring:
  application:
    name: gateway-service
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      username: nacos
      password: nacos
      discovery:
        namespace: public
        group: DEFAULT_GROUP
yaml
# application.yml
server:
  port: 9000

spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1

        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - StripPrefix=1

3.4 启动类 ​

java
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

不需要 @EnableDiscoveryClient

Spring Cloud 2021.0.x 之后,只要在 classpath 中存在注册中心依赖,就会自动注册。不需要在启动类上添加 @EnableDiscoveryClient 注解。

3.5 验证 ​

  1. 确保 Nacos 已启动(http://localhost:8848/nacos)
  2. 确保 user-service 已注册到 Nacos
  3. 启动网关,访问 http://localhost:9000/api/user/profile

如果 user-service 暴露了 /user/profile 接口,请求会经过网关转发到 http://user-service/user/profile。


四、路由配置方式 ​

Gateway 支持两种路由配置方式:YAML 配置文件和 Java DSL(RouteLocator Bean)。

4.1 YAML 配置(推荐) ​

适合大多数场景,配置清晰直观,支持热更新(配合 Nacos)。

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1

        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - StripPrefix=1

4.2 Java DSL(RouteLocator Bean) ​

适合按环境条件动态构造路由的场景(比如根据配置文件决定是否启用某条路由)。

java
@Configuration
public class GatewayRouteConfig {

    @Bean
    public RouteLocator customRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("user-service-route", r -> r
                .path("/api/user/**")
                .filters(f -> f.stripPrefix(1))
                .uri("lb://user-service"))
            .route("order-service-route", r -> r
                .path("/api/order/**")
                .filters(f -> f.stripPrefix(1))
                .uri("lb://order-service"))
            .build();
    }
}

两种方式对比

特性YAML 配置Java DSL
可读性结构清晰,一目了然需要阅读代码
热更新配合 Nacos 支持动态刷新需要重启
条件逻辑不支持支持 if/else 等条件判断
生产推荐推荐少量特殊场景使用
配置与代码分离是否

五、路由断言工厂(Predicate Factories) ​

断言决定"什么样的请求会被匹配到这条路由"。Gateway 内置了十几种断言工厂,每条路由可以组合多个断言——全部满足才算匹配。

5.1 Path —— 路径匹配 ​

最常用的断言,按请求路径匹配。

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/user/**        # 匹配 /api/user/ 下所有路径

多条路径用逗号分隔:

yaml
predicates:
  - Path=/api/user/**, /api/member/**  # 同时匹配两个路径前缀
通配符说明
*匹配当前层级任意内容(不含 /)
**匹配任意层级任意内容

Path 断言是 Ant 风格的模糊匹配,大小写敏感。例如 /API/User 不会匹配 /api/user/**。

5.2 After / Before / Between —— 时间控制 ​

用于按时间段控制路由是否生效,在定时切换、灰度发布中很实用。

yaml
predicates:
  # 在指定时间之后生效
  - After=2025-06-01T00:00:00+08:00[Asia/Shanghai]
yaml
predicates:
  # 在指定时间之前生效
  - Before=2025-12-31T23:59:59+08:00[Asia/Shanghai]
yaml
predicates:
  # 在指定时间段内生效
  - Between=2025-06-01T00:00:00+08:00[Asia/Shanghai], 2025-06-30T23:59:59+08:00[Asia/Shanghai]

时间断言中用到了 Java 的 ZonedDateTime 格式。可以通过代码快速生成时间字符串:

java
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);  // 输出可直接复制使用

5.3 Header —— 请求头匹配 ​

根据请求中是否包含指定 Header 及其值来匹配。

yaml
predicates:
  # Header "X-Request-Id" 的值匹配正则 \d+(纯数字)
  - Header=X-Request-Id, \d+

  # Header "X-Source" 的值等于 "mobile"
  - Header=X-Source, mobile
text
# 匹配:Header 中 X-Source: mobile
# 匹配:Header 中 X-Request-Id: 12345
# 不匹配:请求头没有 X-Source
# 不匹配:X-Request-Id 值为 "abc"

根据请求携带的 Cookie 名称和值(正则)匹配。

yaml
predicates:
  # Cookie "token" 的值匹配正则 .+(至少一个字符,即"存在")
  - Cookie=token, .+

  # Cookie "lang" 的值等于 "zh-CN"
  - Cookie=lang, zh-CN

5.5 Query —— 查询参数匹配 ​

根据 URL 查询参数名和值匹配。

yaml
predicates:
  # 包含名为 "version" 的查询参数,值可以是任意内容
  - Query=version

  # 包含 "version" 且值为 "v2"
  - Query=version, v2

  # 包含 "price" 且值为纯数字
  - Query=price, \d+
text
# 匹配:/api/items?version=v2
# 匹配:/api/items?version=v2&price=100
# 不匹配:/api/items(没有 version 参数)
# 不匹配:/api/items?version=v1(值不匹配)

5.6 Method —— 请求方法匹配 ​

限制匹配的 HTTP 方法。

yaml
predicates:
  # 只匹配 GET 请求
  - Method=GET

  # 匹配 GET 或 POST
  - Method=GET,POST

5.7 RemoteAddr —— 来源 IP 匹配 ​

根据客户端 IP 匹配。

yaml
predicates:
  # 只允许 192.168 网段访问
  - RemoteAddr=192.168.0.1/16

  # 允许多个 IP 段
  - RemoteAddr=192.168.0.1/16, 10.0.0.1/8

RemoteAddr 的实际值取决于网络环境。如果前面有 Nginx 反向代理,获取到的 IP 是 Nginx 的 IP,需要配合 X-Forwarded-For 使用自定义的 RemoteAddrRoutePredicateFactory。

5.8 Weight —— 权重路由(灰度/金丝雀发布) ​

按权重比例将流量分发到不同目标服务,是实现金丝雀发布和蓝绿部署的基础。

yaml
spring:
  cloud:
    gateway:
      routes:
        # 稳定版:承载 80% 流量
        - id: user-service-stable
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
            - Weight=user-group, 8      # 权重 8 → 80%

        # 灰度版:承载 20% 流量
        - id: user-service-canary
          uri: lb://user-service-canary
          predicates:
            - Path=/api/user/**
            - Weight=user-group, 2      # 权重 2 → 20%

核心规则:

要点说明
group权重分组名(同一组内的路由互相按权重分配)
weight权重值(最终占比 = 当前权重 ÷ 同组权重之和)
路径同组中的路由 Path 断言要完全一致

灰度发布工作流

  1. 部署 user-service(稳定版)和 user-service-canary(灰度版)到 Nacos
  2. 配置权重 9:1,先释放 10% 流量到灰度版
  3. 观察灰度版的错误率和响应时间
  4. 逐步调整权重:9:1 → 5:5 → 1:9 → 关闭稳定版
  5. 灰度版转为正式版

5.9 Host —— 主机名匹配 ​

yaml
predicates:
  # 匹配 myapp.example.com 或其子域
  - Host=**.myapp.example.com

  # 精确匹配多个域名
  - Host=api.example.com, api2.example.com

六、网关过滤器工厂(GatewayFilter Factories) ​

过滤器对请求和响应进行修改。与断言不同,过滤器是操作型的——它可以添加/修改请求头、重写路径、限流、熔断等。

6.1 AddRequestHeader —— 添加请求头 ​

在转发给后端服务之前,添加一个请求头。

yaml
filters:
  # 添加 X-Request-From: gateway
  - AddRequestHeader=X-Request-From, gateway

后端服务可通过 @RequestHeader 获取:

java
@GetMapping("/info")
public String info(@RequestHeader(value = "X-Request-From", required = false) String from) {
    return "来自: " + from;  // 输出:来自: gateway
}

6.2 AddRequestParameter —— 添加请求参数 ​

在转发之前,向查询参数中添加键值对。

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

6.3 AddResponseHeader —— 添加响应头 ​

在返回给客户端之前,添加一个响应头。

yaml
filters:
  - AddResponseHeader=X-Gateway-Version, 3.1.5

6.4 RemoveRequestHeader / RemoveResponseHeader —— 移除请求/响应头 ​

yaml
filters:
  # 移除敏感请求头(避免内部信息泄露给后端服务)
  - RemoveRequestHeader=Cookie
  - RemoveRequestHeader=X-Internal-Token

  # 移除响应头
  - RemoveResponseHeader=X-Powered-By

6.5 路径重写系列 ​

路径重写是网关最常用的操作之一。客户端请求的路径通常带了统一的 api 前缀或者其他修饰,但后端服务不需要这些前缀。

StripPrefix —— 去掉路径前缀 ​

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1      # 去掉第 1 段路径
text
客户端请求:        /api/user/profile    →    StripPrefix=1
去掉第 1 段 /api:  /user/profile       →    转发给后端

StripPrefix 的值表示去掉的段数:

yaml
# StripPrefix=2
# /gateway/api/user/profile → /user/profile(去掉前面两段)

PrefixPath —— 添加路径前缀 ​

与 StripPrefix 相反——在转发之前给路径添加前缀。

yaml
filters:
  - PrefixPath=/api
text
客户端请求:        /user/profile
添加前缀 /api:     /api/user/profile    →    转发给后端

SetPath —— 完全重写路径 ​

通过模板表达式完全重写路径,最灵活的方式。

yaml
filters:
  # 将 /api/user/{segment} 重写为 /user/{segment}
  - SetPath=/user/{segment}
text
客户端请求:        /api/user/profile
SetPath 重写:      /user/profile        →    转发给后端

支持灵活的模板替换:

yaml
# 将 /api/v1/user/123 重写为 /user/v1/123
filters:
  - SetPath=/user/{version}/{id}
predicates:
  - Path=/api/{version}/user/{id}

6.6 RedirectTo —— 重定向 ​

返回 302 重定向响应,不转发到后端服务。

yaml
filters:
  # 302 重定向到新地址
  - RedirectTo=302, https://new-site.example.com
text
请求:         GET http://old-site.example.com/page
响应:         302 Found
Location:     https://new-site.example.com/page

6.7 SetStatus —— 设置响应状态码 ​

直接返回指定状态码,不转发到后端。

yaml
filters:
  # 返回 403 Forbidden
  - SetStatus=403

6.8 RequestRateLimiter —— 请求限流 ​

结合 Redis 实现令牌桶算法限流,保护后端服务不被流量冲垮。

添加依赖 ​

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

必须是 reactive 版本

Gateway 基于 WebFlux,Redis 依赖必须使用 spring-boot-starter-data-redis-reactive,不能使用普通的 spring-boot-starter-data-redis,否则会与 WebFlux 不兼容。

配置 Redis + KeyResolver ​

yaml
spring:
  redis:
    host: 127.0.0.1
    port: 6379
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter:
                  replenishRate: 10       # 每秒填充的令牌数
                  burstCapacity: 20       # 令牌桶总容量

KeyResolver 配置 ​

KeyResolver 决定"按什么维度限流"。

java
@Configuration
public class RateLimiterConfig {

    /**
     * 按 IP 限流:同一 IP 共享令牌
     */
    @Bean
    @Primary
    public KeyResolver ipKeyResolver() {
        return exchange -> {
            String ip = exchange.getRequest().getRemoteAddress()
                    .getAddress().getHostAddress();
            return Mono.just(ip);
        };
    }

    /**
     * 按路径限流:访问同一路径共享令牌
     */
    @Bean
    public KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getURI().getPath());
    }

    /**
     * 按用户限流:结合认证信息
     */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> {
            String user = exchange.getRequest()
                    .getQueryParams().getFirst("userId");
            return Mono.just(user == null ? "anonymous" : user);
        };
    }
}

参数详解:

参数说明建议值
replenishRate每秒向桶中放入的令牌数根据后端能力设定
burstCapacity令牌桶总容量(允许的瞬时峰值)≥ replenishRate
requestedTokens每次请求消耗的令牌数默认 1

令牌桶算法解释

  • 桶里最多放 burstCapacity 个令牌
  • 每秒往桶里加 replenishRate 个令牌(满了就不加了)
  • 每个请求需要消耗 requestedTokens 个令牌才被放行
  • 桶里令牌不够时返回 HTTP 429 Too Many Requests

举例:replenishRate=10, burstCapacity=20

  • 匀速请求:每秒最多通过 10 个
  • 突发请求:瞬时最多放行 20 个
  • 超过后被限流

6.9 CircuitBreaker —— 熔断 ​

当后端服务出错率高或响应变慢时,熔断器会"熔断"该路由,直接返回降级响应,避免请求积压。

Spring Cloud Gateway 自带 Spring Cloud CircuitBreaker 抽象层,支持 Sentinel、Resilience4J 两种实现。以下使用 Sentinel 作为熔断器(与 Spring Cloud Alibaba 统一技术栈)。

添加依赖 ​

xml
<!-- Sentinel 网关适配 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>

<!-- CircuitBreaker 抽象层 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>

路由级熔断配置 ​

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1
            - name: CircuitBreaker
              args:
                name: userServiceCircuitBreaker
                fallbackUri: forward:/fallback/user

降级处理接口 ​

java
@RestController
@RequestMapping("/fallback")
public class FallbackController {

    @GetMapping("/user")
    public Mono<Map<String, Object>> userFallback() {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("code", 503);
        result.put("message", "用户服务暂时不可用,请稍后重试");
        result.put("timestamp", System.currentTimeMillis());
        return Mono.just(result);
    }
}

6.10 Retry —— 重试 ​

当后端服务临时不可用时(如网络抖动),自动重试。

yaml
filters:
  - name: Retry
    args:
      retries: 3                        # 重试次数
      statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE  # 遇到这些状态码才重试
      methods: GET                      # 只对 GET 请求重试(安全)
      backoff:
        firstBackoff: 100ms             # 第一次重试等 100ms
        maxBackoff: 2000ms              # 最大重试间隔 2s
        factor: 2                       # 每次间隔翻倍
        basedOnPreviousValue: true      # 基于上次间隔计算

重试策略注意事项

  • 对 POST/PUT/DELETE 请求重试需谨慎,可能造成重复提交(如重复创建订单)
  • retries 值不宜过大,否则会延长请求响应时间
  • 重试应配合熔断使用,避免不断重试已经挂掉的服务

七、自定义全局过滤器 ​

内置的过滤器可以覆盖大部分常规场景,但有些业务逻辑(如全局 Token 校验、请求日志)需要自定义过滤器。

实现 GlobalFilter 接口即可(对所有路由生效,无需在 YAML 中显式配置)。

7.1 示例:JWT 鉴权过滤器 ​

java
@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    // 白名单路径(不需要鉴权)
    private static final List<String> WHITE_LIST = Arrays.asList(
            "/api/auth/login",
            "/api/auth/register",
            "/api/public/**"
    );

    private final AntPathMatcher pathMatcher = new AntPathMatcher();

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

        // 1. 白名单路径直接放行
        if (WHITE_LIST.stream().anyMatch(p -> pathMatcher.match(p, path))) {
            return chain.filter(exchange);
        }

        // 2. 获取 Token
        String token = exchange.getRequest().getHeaders()
                .getFirst(HttpHeaders.AUTHORIZATION);

        if (token == null || !token.startsWith("Bearer ")) {
            log.warn("请求缺少有效 Token: {}", path);
            return unauthorized(exchange, "Token 缺失或无效");
        }

        // 3. 解析 Token(伪代码)
        try {
            String tokenValue = token.substring(7);
            // Claims claims = jwtUtil.parseToken(tokenValue);
            // exchange.getRequest().mutate()
            //         .header("X-User-Id", claims.getUserId());
            log.debug("Token 验证通过");
        } catch (Exception e) {
            log.warn("Token 解析失败: {}", e.getMessage());
            return unauthorized(exchange, "Token 已过期或格式错误");
        }

        // 4. 放行
        return chain.filter(exchange);
    }

    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("code", 401);
        result.put("message", message);
        byte[] bytes = JSON.toJSONString(result).getBytes(StandardCharsets.UTF_8);

        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE,
                MediaType.APPLICATION_JSON_VALUE);

        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        // 数值越小优先级越高,NettyWriteResponseFilter 之前执行
        return -100;
    }
}

7.2 示例:请求日志过滤器 ​

java
@Component
@Slf4j
public class RequestLogGlobalFilter implements GlobalFilter, Ordered {

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

        log.info(">> {} {}", request.getMethod(), request.getURI());

        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            long cost = System.currentTimeMillis() - startTime;
            HttpStatus status = exchange.getResponse().getStatusCode();
            log.info("<< {} {} {} {}ms",
                    request.getMethod(), request.getURI(),
                    status != null ? status.value() : "null", cost);
        }));
    }

    @Override
    public int getOrder() {
        return -200;  // 最先执行,记录所有请求
    }
}

7.3 Filter 执行顺序 ​

多个 GlobalFilter 的执行顺序由 getOrder() 决定,数字越小越先执行。

java
getOrder() = -200  →  请求日志(最先执行)
getOrder() = -100  →  鉴权检查
getOrder() = 0     →  默认顺序(如 NettyWriteResponseFilter)
getOrder() = 100   →  其他

八、跨域配置(CORS) ​

在网关层统一配置 CORS,所有后端服务无需各自处理跨域问题。

8.1 YAML 配置方式 ​

yaml
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            # 允许的来源(生产环境不要用 *)
            allowedOrigins:
              - "https://www.example.com"
              - "https://admin.example.com"
            # 允许的 HTTP 方法
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            # 允许的请求头
            allowedHeaders:
              - "*"
            # 是否允许携带 Cookie
            allowCredentials: true
            # 预检请求缓存时间(秒)
            maxAge: 3600

8.2 Java 配置方式 ​

java
@Configuration
public class CorsConfig implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        // OPTIONS 预检请求直接返回 200
        if (CorsUtils.isCorsRequest(request) &&
            request.getMethod() == HttpMethod.OPTIONS) {
            ServerHttpResponse response = exchange.getResponse();
            response.getHeaders().add("Access-Control-Allow-Origin", "*");
            response.getHeaders().add("Access-Control-Allow-Methods",
                    "GET, POST, PUT, DELETE, OPTIONS");
            response.getHeaders().add("Access-Control-Allow-Headers", "*");
            response.setStatusCode(HttpStatus.OK);
            return Mono.empty();
        }

        return chain.filter(exchange);
    }
}

CORS 配置注意事项

  • allowedOrigins 和 allowedOriginPatterns 只能配置其一,不能同时使用
  • 生产环境不要使用 *,应明确列出域名
  • 如果使用了自定义 Token 的 Header(如 X-Token),需要在 allowedHeaders 中加上

九、WebSocket 路由配置 ​

Spring Cloud Gateway 原生支持 WebSocket 协议的路由转发,无需额外配置即可透传 WebSocket 握手和帧数据。

9.1 WebSocket 路由原理 ​

Gateway 基于 Netty 的异步非阻塞特性,天然支持长连接。WebSocket 请求到达 Gateway 后,Gateway 会:

  1. 识别 Upgrade: websocket 请求头,判断是否为 WebSocket 握手
  2. 匹配路由规则,将请求转发到后端 WebSocket 服务
  3. 建立双向通道,后续数据帧在客户端 ↔ Gateway ↔ 后端服务之间透明传输
text
客户端 ──WebSocket──▶ Gateway ──WebSocket──▶ 后端服务
   ◀────────── 双向数据帧透传 ──────────▶

9.2 基础配置 ​

WebSocket 路由的配置与普通 HTTP 路由完全一致,只需将 uri 指向 WebSocket 服务即可:

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: websocket-route
          uri: lb://ws-service          # 或 ws://localhost:8085
          predicates:
            - Path=/ws/**

WebSocket 路由不需要特殊断言

Gateway 会自动根据请求头中的 Upgrade: websocket 和 Connection: Upgrade 识别 WebSocket 请求,不需要额外的断言配置。

9.3 完整配置示例 ​

以 Spring WebSocket + SockJS 为例:

Gateway 路由配置:

yaml
spring:
  cloud:
    gateway:
      routes:
        # WebSocket 主路由
        - id: websocket-chat
          uri: lb://chat-service
          predicates:
            - Path=/chat/**

        # SockJS 降级轮询路由(当 WebSocket 不可用时)
        - id: websocket-chat-sockjs
          uri: lb://chat-service
          predicates:
            - Path=/chat/**
            - Header=X-Forwarded-Proto, http

SockJS 路径处理

如果后端使用 SockJS,客户端会访问 /chat/123/xxx/websocket 等路径。此时 StripPrefix 要谨慎使用——通常不需要去掉前缀,因为 SockJS 的路径结构与后端服务一致。

9.4 跨域 WebSocket 配置 ​

如果 WebSocket 客户端与 Gateway 不同域,需要配置 CORS:

yaml
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins:
              - "https://www.example.com"
            allowedMethods:
              - GET
              - POST
            allowedHeaders:
              - "*"
            allowCredentials: true
            maxAge: 3600

9.5 生产环境 WebSocket 调优 ​

yaml
spring:
  cloud:
    gateway:
      httpclient:
        websocket:
          # WebSocket 最大帧大小(默认 65536,即 64KB)
          max-frame-payload-length: 1048576   # 1MB
        # 连接超时和响应超时对 WebSocket 长连接同样适用
        connect-timeout: 5000
        response-timeout: 0                   # 0 表示永不超时(WebSocket 需要)
配置项说明推荐值
response-timeout设为 0 禁用超时0(WebSocket 长连接)
max-frame-payload-length最大帧大小按业务需求设置
connect-timeout连接超时3000-5000ms

9.6 常见问题 ​

WebSocket 连接建立后立即断开?

检查后端服务是否正常,确认 response-timeout 是否设置为 0(或足够大)。WebSocket 是长连接,默认的响应超时会断开连接。

Gateway 后 WebSocket 无法获取真实客户端 IP?

Gateway 转发 WebSocket 时会自动传递 X-Forwarded-For 头,后端可以通过该头获取真实 IP。


十、Nacos 动态路由 ​

将路由配置从 YAML 迁移到 Nacos 配置中心,实现不重启应用即可修改路由规则。

10.1 核心思路 ​

Gateway 通过监听 Nacos 的配置变更事件,实时更新路由表。Spring Cloud Alibaba 已经提供了自动刷新机制——只需将路由配置发布到 Nacos 即可。

10.2 配置文件改造 ​

将路由配置移到 Nacos 中,YAML 中只保留基础配置。

yaml
# application.yml —— 只保留基础配置
spring:
  application:
    name: gateway-service
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      username: nacos
      password: nacos
      discovery:
        namespace: public
        group: DEFAULT_GROUP
      config:
        file-extension: yaml
        namespace: public
        group: DEFAULT_GROUP
        # 关键配置:启用动态路由
        extension-configs:
          - data-id: gateway-routes.yaml
            group: DEFAULT_GROUP
            refresh: true           # 开启自动刷新

在 Nacos 控制台中创建 gateway-routes.yaml,内容为纯路由定义:

yaml
# Nacos 中的 gateway-routes.yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1

        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
          filters:
            - StripPrefix=1

10.3 动态路由刷新原理 ​

  1. Gateway 启动时从 Nacos 拉取 gateway-routes.yaml
  2. Nacos 客户端建立 gRPC 长连接监听配置变更
  3. 在 Nacos 控制台修改 gateway-routes.yaml 并发布
  4. Gateway 收到推送,自动重建路由表

验证动态路由是否生效

  1. 调整 Nacos 中某条路由的 uri 指向其他服务
  2. 等待几秒(Nacos 配置推送通常 1-2 秒生效)
  3. 调用对应接口,观察是否转发到新的后端服务
  4. 查看日志中是否有 Refresh routes 相关日志

10.4 高级:自定义动态路由监听器 ​

如果内置的刷新机制不够灵活,可以自己监听并处理路由变更。

java
@Component
@Slf4j
public class DynamicRouteService {

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    /**
     * 动态添加路由
     */
    public void addRoute(RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        log.info("动态添加路由: {}", definition.getId());
    }

    /**
     * 动态删除路由
     */
    public void deleteRoute(String routeId) {
        routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
        log.info("动态删除路由: {}", routeId);
    }

    /**
     * 动态更新路由
     */
    public void updateRoute(RouteDefinition definition) {
        deleteRoute(definition.getId());
        addRoute(definition);
    }
}

动态路由的注意事项

  • 路由 ID 是唯一标识,增删改都以 ID 为准
  • 动态路由和 YAML 中配置的路由共存,不是替换关系
  • 删除 YAML 中的路由后,如果 Nacos 中有同 ID 的路由,它仍然生效

十一、Sentinel 网关限流 ​

Spring Cloud Alibaba Sentinel 提供了专门的网关限流功能,比 RequestRateLimiter 更强大——支持 API 分组、热点参数限流、控制台实时管理。

11.1 添加依赖 ​

xml
<!-- Sentinel 核心 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- Sentinel 网关适配器 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>

<!-- Sentinel 控制台 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

11.2 配置文件 ​

yaml
spring:
  cloud:
    sentinel:
      # 控制台地址
      transport:
        dashboard: 127.0.0.1:8080
      # 将规则持久化到 Nacos
      datasource:
        ds1:
          nacos:
            server-addr: 127.0.0.1:8848
            data-id: ${spring.application.name}-sentinel-rules
            group-id: DEFAULT_GROUP
            data-type: json
            rule-type: gw-flow      # 网关流控规则

11.3 两种限流模式 ​

路由维度限流 ​

按路由 ID 设置限流规则:

json
// Nacos 中的 gateway-service-sentinel-rules
[
    {
        "resource": "user-service-route",
        "resourceMode": 0,
        "grade": 1,
        "count": 100,
        "intervalSec": 1
    }
]
字段说明值
resource资源名(路由 ID)user-service-route
resourceMode0=路由ID模式0
grade1=QPS限流1
count阈值100
intervalSec统计窗口(秒)1

API 分组限流 ​

将多个路径归为一个 API 组,统一限流:

java
@Configuration
public class SentinelApiConfig {

    @PostConstruct
    public void initApiDefinitions() {
        // 将多个路径归为同一个 API 组
        Set<ApiDefinition> definitions = new HashSet<>();

        ApiDefinition userApi = new ApiDefinition("user-api")
                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                    add(new ApiPathPredicateItem()
                            .setPattern("/api/user/**")
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                }});

        definitions.add(userApi);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }
}

然后对 API 组设置规则:

json
{
    "resource": "user-api",
    "resourceMode": 1,
    "grade": 1,
    "count": 200
}

resourceMode: 1 表示按 API 组模式限流。

11.4 自定义限流异常处理 ​

限制触发时默认返回 429,可以自定义返回内容:

java
@Configuration
public class SentinelGatewayConfig {

    @PostConstruct
    public void init() {
        GatewayCallbackManager.setBlockHandler(
            new BlockRequestHandler() {
                @Override
                public Mono<ServerResponse> handleRequest(
                        ServerWebExchange exchange, Throwable t) {
                    Map<String, Object> result = new LinkedHashMap<>();
                    result.put("code", 429);
                    result.put("message", "请求过于频繁,请稍后重试");
                    return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
                            .contentType(MediaType.APPLICATION_JSON)
                            .bodyValue(result);
                }
            }
        );
    }
}

十二、负载均衡 ​

Spring Cloud Gateway 集成了 Spring Cloud LoadBalancer,自动将请求分发到注册在 Nacos 的多个服务实例。

12.1 工作流程 ​

text
                     Nacos 注册中心
                     ┌─────────────┐
                     │ user-service │
                     │  实例1 :8081 │
                     │  实例2 :8082 │
                     │  实例3 :8083 │
                     └──────┬──────┘
                            │ 拉取实例列表
                            ▼
网关请求 → Gateway → LoadBalancer → 选择一个实例 → 转发

12.2 配置方式 ​

YAML 中使用 lb:// 前缀即可启用负载均衡:

yaml
routes:
  - id: user-service-route
    uri: lb://user-service   # lb:// 前缀触发负载均衡

12.3 负载均衡策略 ​

默认使用轮询策略(RoundRobin)。可以通过 Java 配置切换:

java
@Configuration
public class LoadBalancerConfig {

    /**
     * 随机策略
     */
    @Bean
    public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
            Environment env, LoadBalancerClientFactory factory) {
        String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(
                factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

常用策略对比:

策略说明适用场景
轮询(默认)依次分配请求各实例性能一致
随机随机选择实例实例较多时
加权按权重分配实例配置不同
最少连接选连接数最少的长连接场景

Spring Cloud 2021.0.x 使用 LoadBalancer,不是 Ribbon

从 Spring Cloud 2020.0 开始,Ribbon 已进入维护模式,Spring Cloud Gateway 默认集成了 LoadBalancer。不要引入 Ribbon 依赖。


十三、生产环境最佳实践 ​

13.1 全局超时配置 ​

网关是流量入口,超时不控制会导致请求堆积。

yaml
spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 3000          # 连接超时(毫秒)
        response-timeout: 30s          # 响应超时
        pool:
          max-connections: 1000        # 最大连接数
          max-idle-time: 5m            # 空闲连接保活时间

13.2 熔断保障 ​

为每条关键路由配置熔断,防止级联故障:

yaml
filters:
  - name: CircuitBreaker
    args:
      name: productServiceCB
      fallbackUri: forward:/fallback/product

13.3 重试 + 幂等性 ​

yaml
filters:
  - name: Retry
    args:
      retries: 2
      statuses: SERVICE_UNAVAILABLE, GATEWAY_TIMEOUT
      methods: GET                     # 仅 GET 请求重试
      backoff:
        firstBackoff: 50ms
        maxBackoff: 500ms

POST/PUT 重试风险

重试 POST 请求可能造成重复写入。如果业务确实需要对写操作重试,必须保证后端接口的幂等性——同样的请求无论执行多少次,结果都一样。

常见幂等方案:

  • 请求头带唯一 Idempotent-Key,后端去重
  • 数据库唯一索引约束
  • Redis 分布式锁

13.4 安全性配置 ​

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: internal-api
          uri: lb://internal-service
          predicates:
            - Path=/internal/**
            - RemoteAddr=192.168.0.0/16    # 仅内网可访问
          filters:
            - RemoveResponseHeader=X-Powered-By
            - RemoveResponseHeader=Server

13.5 响应体缓存 ​

Gateway 默认不会缓存响应体。如果需要读取响应体做修改(如加密、脱敏),需要开启缓存:

java
@Component
public class BodyCachingFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 缓存请求体
        return DataBufferUtils.join(exchange.getRequest().getBody())
                .flatMap(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);

                    // 将缓存后的请求体放回 exchange
                    ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
                            .body(Flux.just(ByteBuffer.wrap(bytes)))
                            .build();

                    return chain.filter(
                            exchange.mutate().request(mutatedRequest).build());
                });
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;  // 最高优先级
    }
}

13.6 JVM 参数调优 ​

Gateway 基于 Netty,网络 IO 密集。推荐 JVM 参数:

bash
java -jar gateway-service.jar \
  -Xms1g -Xmx1g \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \
  -Dio.netty.leakDetection.level=paranoid

13.7 监控端点 ​

暴露 /actuator 获取网关运行指标:

yaml
management:
  endpoints:
    web:
      exposure:
        include: gateway, health, metrics
  endpoint:
    gateway:
      enabled: true

访问 http://localhost:9000/actuator/gateway/routes 查看所有路由定义和运行状态。

13.8 生产配置清单 ​

配置项作用推荐值
连接超时防止连接 hang 住3-5s
响应超时防止请求堆积30-60s
限流防止流量打垮后端按业务设置
熔断防止级联故障必须配置
重试提升可用性GET 最多 3 次
CORS解决跨域问题明确列出域名
鉴权统一认证入口白名单 + Token
JVM性能保障G1GC

13.9 完整配置参考 ​

以下是一个较为完整的生产级配置示例:

yaml
# bootstrap.yml
spring:
  application:
    name: gateway-service
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      username: nacos
      password: nacos
      discovery:
        namespace: 4a2b3c1d-xxxx-xxxx-xxxx-xxxxxxxxxxxx
        group: DEFAULT_GROUP
      config:
        file-extension: yaml
        namespace: 4a2b3c1d-xxxx-xxxx-xxxx-xxxxxxxxxxxx
        group: DEFAULT_GROUP
        extension-configs:
          - data-id: gateway-routes.yaml
            group: DEFAULT_GROUP
            refresh: true
yaml
# application.yml
server:
  port: 9000

spring:
  redis:
    host: 127.0.0.1
    port: 6379
  cloud:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080
      datasource:
        ds1:
          nacos:
            server-addr: 127.0.0.1:8848
            data-id: ${spring.application.name}-sentinel-rules
            group-id: DEFAULT_GROUP
            data-type: json
            rule-type: gw-flow
    gateway:
      httpclient:
        connect-timeout: 3000
        response-timeout: 30s
        pool:
          max-connections: 1000
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOriginPatterns:
              - "https://*.example.com"
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowedHeaders:
              - "*"
            allowCredentials: true
            maxAge: 3600

logging:
  level:
    org.springframework.cloud.gateway: INFO

management:
  endpoints:
    web:
      exposure:
        include: gateway, health, metrics, info
  endpoint:
    gateway:
      enabled: true

常见问题排查 ​

路由没有生效 ​

  1. 检查路由 id 是否重复,后定义的同 ID 路由会覆盖前面的
  2. 检查多个 predicates 是否全部满足(AND 关系)
  3. 访问 /actuator/gateway/routes 确认路由是否注册
  4. 开启 DEBUG 日志:logging.level.org.springframework.cloud.gateway: DEBUG

503 Service Unavailable ​

出现 503 通常表示 Gateway 找不到后端服务的可用实例:

  1. 确认后端服务在 Nacos 中已注册且为"健康"状态
  2. 确认 uri 中使用的服务名与 Nacos 中的服务名完全一致
  3. 确认 lb:// 前缀是否遗漏
  4. 检查 spring-cloud-starter-loadbalancer 依赖是否引入

CORS 还是报跨域错误 ​

Gateway 配置了 CORS 但前端仍然报跨域:

  1. 确认 allowedOriginPatterns 和 allowedOrigins 没有同时配置(冲突导致失效)
  2. 检查自定义的 GlobalFilter 是否在返回时修改了响应头导致 CORS 头丢失
  3. 检查是否在路由的 filters 中使用了 StripPrefix,造成 OPTIONS 预检请求路径不匹配
  4. 如果使用了 Token 认证的 Header,确认 allowedHeaders 中包含了该自定义 Header
最近更新