在微服务架构中,API Gateway 不只是一个请求转发器,而是外部流量进入内部服务体系的统一边界。Spring Cloud Gateway 将路由匹配、服务发现、负载均衡、过滤器链、鉴权、跨域、限流、超时、重试、熔断和监控等能力集中到网关层。本文从 Gateway 的核心模型和请求处理链路出发,结合 Nacos、Redis 与 Resilience4J 给出一套面向 Spring Boot 3 / Spring Cloud Gateway 4.x 的完整实践,并说明 Gateway 5.x 下需要关注的版本变化。
为什么微服务架构需要网关 在单体应用中,系统通常只有一个对外入口:
1 2 3 4 Client | v Application
当系统拆成多个微服务以后,情况开始发生变化:
1 2 3 4 5 auth-service : 8081 order-service : 8082 file-service : 8083 user-service : 8084 payment-service : 8085
如果客户端直接访问每一个微服务,就会出现很多问题。
客户端必须知道所有服务地址;服务拆分、合并或迁移时客户端也要跟着修改;每个服务都需要重复处理认证、跨域、访问控制、日志等公共逻辑;内部微服务直接暴露到公网还会扩大攻击面。
API Gateway 的价值就在于把这些分散的入口收敛为一个统一入口:
所有外部请求首先进入 Gateway,再根据请求路径、Host、Header、Method 等信息路由到真正的服务。鉴权、限流、日志和故障保护等横切逻辑也可以在这里统一完成。Spring Cloud Gateway 官方将自身定位为 Spring 生态中的 API Gateway,用于完成 API 路由,并处理安全、监控指标和弹性治理等横切关注点。
一个比较典型的微服务入口架构如下:
flowchart LR
C["Client / Browser / APP"]
E["CDN / WAF / Nginx / Load Balancer"]
G["Spring Cloud Gateway"]
A["auth-service"]
U["user-service"]
O["order-service"]
F["file-service"]
N["Nacos<br/>服务发现"]
R["Redis<br/>限流 / 会话辅助"]
C --> E
E --> G
G --> A
G --> U
G --> O
G --> F
N -. 服务实例 .-> G
R -. 状态与令牌桶 .-> G
这里需要区分两个容易混淆的概念:流量网关和业务/API 网关并不一定互相替代。
能力
Nginx / LB / WAF
Spring Cloud Gateway
主要位置
网络入口、边缘层
微服务应用入口
静态资源
很适合
通常不是主要用途
TLS 终止
很适合
可以,但通常放在前层
四层/七层负载均衡
强
主要关注应用服务路由
服务发现
通常需要额外方案
可直接接入 Spring DiscoveryClient
API 路由
可以
核心能力
Java 业务规则
不适合复杂逻辑
很适合
鉴权
基础能力
可与 Spring Security 等深度集成
限流
支持
支持按用户、路径等业务维度限流
熔断/重试
能力因产品而异
与 Spring Cloud 体系整合
请求/响应改写
支持
GatewayFilter 体系
业务上下文
较弱
较强
因此生产环境中经常不是:
而是:
1 2 3 4 5 6 7 8 9 Internet | CDN / WAF | Nginx / Cloud Load Balancer | Spring Cloud Gateway | Microservices
Nginx 负责更靠近网络边缘的 TLS、静态资源、连接管理和负载均衡,Gateway 则负责更贴近微服务业务语义的路由、鉴权、服务发现、限流和故障治理。
先说版本:不要把旧教程直接复制到新项目 Spring Cloud Gateway 从早期 Spring Cloud 2.x 到今天已经经历了明显变化。
早期 Gateway 教程大量基于 Spring Cloud 2.1.x.RELEASE,运行于 Spring Boot 2、Spring Framework 5 和早期 Project Reactor 体系。很多核心思想今天依然成立,例如 Route、Predicate、Filter 和 pre/post Filter Chain,但是部分依赖、配置和组件已经发生变化。最典型的就是过去经常看到的 Hystrix 熔断方案。
截至 2026 年 8 月,Spring 官方文档列出的稳定版本包括 Gateway 5.0.2、4.3.5、4.2.7 和 4.1.9。其中 Gateway 4.3.5 属于 Spring Framework 6 / Spring Boot 3 技术栈,而 Gateway 5.0.2 已进入 Spring Framework 7 / Spring Boot 4 技术栈。
本文为了兼顾当前大量 Spring Boot 3 微服务项目,主要采用:
1 2 3 4 5 6 7 Spring Boot 3 Spring Cloud Gateway 4.3.x Spring WebFlux Nacos Spring Cloud LoadBalancer Redis Spring Cloud CircuitBreaker + Resilience4J
进行讲解。
如果项目已经升级到 Spring Boot 4 / Gateway 5.x,核心模型没有推倒重来,但一定要按照对应版本官方文档核对 starter、属性路径和 API。
还有一个需要特别修正的老教程写法。
过去经常使用:
1 <artifactId > spring-cloud-starter-gateway</artifactId >
当前 Gateway 4.3.5 和 5.0.2 官方文档针对 Server WebFlux 版本推荐的 starter 已经是:
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-gateway-server-webflux</artifactId > </dependency >
Server WebFlux Gateway 基于 Spring WebFlux、Project Reactor 和 Netty,不是传统 Servlet/Tomcat 模式,也不应该再按照普通 Spring MVC 项目的思路处理请求线程。
这也是为什么 Gateway 项目中不要随手塞入:
尤其不要因为某个公共模块间接引入 MVC,就把 Gateway 变成一个 WebMVC、WebFlux 混杂的项目。
Spring Cloud Gateway 的三个核心概念 理解 Gateway,最重要的不是记住几十个配置项,而是先建立三个核心模型:
它们基本描述了 Gateway 的全部路由逻辑。
可以简单理解成:
1 Route = Predicate + URI + Filters + Metadata
即:
什么请求需要匹配,匹配后发到哪里,转发前后还需要做什么处理。
Route:一条完整的路由规则 Route(路由)代表一条完整的转发规则。
例如:
1 2 3 4 5 6 7 8 9 10 spring: cloud: gateway: routes: - id: order-route uri: lb://order-service predicates: - Path=/api/orders/** filters: - StripPrefix=1
这里表示:
1 2 3 4 5 6 7 8 9 10 11 请求: /api/orders/123 满足: Path=/api/orders/** 目标: lb://order-service 过滤: StripPrefix=1
id 是路由唯一标识。
uri 是目标地址。
predicates 决定请求是否匹配。
filters 决定匹配后如何修改请求或响应。
Gateway 同时支持简写方式和完整参数方式配置 Predicate、Filter。
Predicate:这个请求是不是我的 Predicate(路由断言)本质上是一个条件判断。
例如:
1 2 predicates: - Path=/api/orders/**
意思是:
1 2 如果 path 满足 /api/orders/** 那么命中这条 Route
除了 Path,还可以根据很多请求属性进行判断,例如:
1 2 3 4 5 6 7 8 Path Host Header Cookie Method Query RemoteAddr 时间
一个请求可以配置多个 Predicate。
例如:
1 2 3 4 predicates: - Path=/api/admin/** - Method=GET - Header=X-Client-Type,internal
可以理解成:
1 2 3 Path 匹配 AND Method = GET AND Header 匹配
多个 Predicate 共同决定请求是否属于这条 Route。
Filter:匹配之后做什么 Filter 是 Gateway 真正扩展能力最强的部分。
例如:
1 2 filters: - StripPrefix=1
可以修改请求 Path。
除此之外还可以:
1 2 3 4 5 6 7 8 9 10 11 12 添加 Header 删除 Header 修改 Header 重写 Path 修改请求参数 修改响应 限流 熔断 重试 Token Relay 请求体处理 响应体处理
Spring Cloud Gateway 4.3.5 自带大量 GatewayFilter Factory,包括 CircuitBreaker、RequestRateLimiter、RewritePath、StripPrefix、Retry、TokenRelay 等。
因此 Gateway 的本质可以浓缩为:
1 2 3 4 5 6 7 8 Predicate 决定: 这个请求走哪条 Route? Filter 决定: 走这条 Route 时需要做什么? URI 决定: 最终发到哪里?
Gateway 一次请求到底经历了什么 理解 Filter 之前,需要先理解整个 Gateway 请求生命周期。
官方描述的核心流程是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Client | v Gateway Handler Mapping | | 查找匹配 Route v Gateway Web Handler | v Filter Chain | | pre filters v Proxy Request | v Downstream Service | v Filter Chain | | post filters v Client
Gateway Handler Mapping 首先判断请求是否能够匹配某个 Route。匹配之后交给 Gateway Web Handler,后者构建对应的过滤器链。过滤器既可以在代理请求发出之前执行,也可以在下游响应返回之后执行。
用时序图表示更加直观:
sequenceDiagram
participant C as Client
participant HM as Gateway Handler Mapping
participant GW as Gateway Web Handler
participant F as Filter Chain
participant LB as LoadBalancer
participant S as Downstream Service
C->>HM: HTTP Request
HM->>HM: 匹配 Route / Predicate
HM->>GW: Route
GW->>F: 构建过滤器链
F->>F: Pre Filters
F->>LB: 处理 lb://service-name
LB->>S: Proxy Request
S-->>LB: HTTP Response
LB-->>F: Response
F->>F: Post Filters
F-->>GW: Response
GW-->>C: HTTP Response
这一点非常重要,因为很多网关功能其实都只是 Filter 的不同形态:
1 2 3 4 5 6 7 鉴权 -> pre 限流 -> pre Header 注入 -> pre URL 改写 -> pre 访问日志 -> pre + post 响应统计 -> post 响应 Header -> post
GatewayFilter 与 GlobalFilter Spring Cloud Gateway 中经常遇到两个过滤器概念:
1 2 GatewayFilter GlobalFilter
GatewayFilter GatewayFilter 通常跟某一条 Route 绑定。
例如:
1 2 3 4 5 6 7 routes: - id: order-route uri: lb://order-service predicates: - Path=/api/orders/** filters: - StripPrefix=1
这里的 StripPrefix 只影响 order-route。
适合处理:
1 2 3 4 5 某服务独有的 URL Rewrite 某接口限流 某服务重试 某服务熔断 某条 Route Header 修改
GlobalFilter GlobalFilter 会参与所有匹配路由的过滤器链,因此非常适合处理:
1 2 3 4 5 6 7 统一鉴权 TraceId 访问日志 租户信息 统一安全 Header 黑白名单 统一请求检查
当请求成功匹配 Route 后,Gateway 会把 GlobalFilter 和当前 Route 对应的 GatewayFilter 合并,再根据 Ordered 排序。高优先级 Filter 在 pre 阶段更早执行,在 post 阶段反而更晚退出,因此整个执行结构实际上类似一个嵌套调用栈。
假设:
1 2 3 Filter A order = -100 Filter B order = 0 Filter C order = 100
大致执行过程是:
1 2 3 4 5 6 7 A pre B pre C pre downstream C post B post A post
所以 Filter 顺序不能随便写。
例如比较合理的顺序可能是:
1 2 3 4 5 Trace Filter -200 Security Filter -100 Tenant Filter -90 Logging Filter -50 业务 Filter 0
当然,具体 order 应根据项目已有 Filter 与 Gateway 内部 Filter 一起设计,而不是看到“数字越小越早”就一路写成 Integer.MIN_VALUE。
Gateway 项目依赖 以 Spring Boot 3 + Gateway 4.3.x Server WebFlux 为例,可以准备以下核心依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-gateway-server-webflux</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis-reactive</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > </dependencies >
具体版本应交给对应 Spring Boot、Spring Cloud 和 Spring Cloud Alibaba BOM 管理,不建议分别给每一个 Spring Cloud 组件手工指定版本。
特别注意:
1 2 3 4 Spring Boot Spring Cloud Spring Cloud Alibaba Nacos
是存在版本兼容关系的。
不要采用这种拼装式升级:
1 2 3 4 Boot 升最新 Cloud 随便找一个 Alibaba 再随便找一个 Nacos Server 也升最新
然后期待 Maven 用爱发电。
Nacos + Gateway 的基本架构 采用 Nacos 后,服务启动时向注册中心注册自己的实例:
1 2 3 4 order-service ├── 10.0.0.11:8080 ├── 10.0.0.12:8080 └── 10.0.0.13:8080
Gateway 不再需要把目标地址写死:
1 uri: http://10.0.0.11:8080
而是:
lb:// 表示该目标通过 Spring Cloud LoadBalancer 解析,Gateway 根据服务名获取服务实例,然后选择一个实例完成请求转发。DiscoveryClient Route Locator 也可以根据注册中心中的服务自动创建 lb://service-name 路由。
架构关系如下:
flowchart TD
O1["order-service<br/>10.0.0.11:8080"]
O2["order-service<br/>10.0.0.12:8080"]
O3["order-service<br/>10.0.0.13:8080"]
N["Nacos"]
G1["Gateway-1"]
G2["Gateway-2"]
C["Client"]
LB["Nginx / Load Balancer"]
O1 --> N
O2 --> N
O3 --> N
N -. ServiceInstance .-> G1
N -. ServiceInstance .-> G2
C --> LB
LB --> G1
LB --> G2
G1 --> O1
G1 --> O2
G1 --> O3
G2 --> O1
G2 --> O2
G2 --> O3
配置一套路由 假设系统中存在:
1 2 3 auth-service file-service main-service
可以配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 server: port: 1000 spring: application: name: gateway cloud: nacos: discovery: server-addr: 127.0 .0 .1 :8848 gateway: routes: - id: auth-route uri: lb://auth-service predicates: - Path=/a/** - id: file-route uri: lb://file-service predicates: - Path=/f/** - id: main-route uri: lb://main-service predicates: - Path=/m/**
这样客户端不再需要访问:
1 2 3 http://127.0.0.1:8081/a/login http://127.0.0.1:8082/f/upload http://127.0.0.1:8083/m/users
而可以统一访问:
1 2 3 http://gateway:1000/a/login http://gateway:1000/f/upload http://gateway:1000/m/users
这就是网关最基本的统一入口与路由功能。类似的 Nacos + lb://service-name 路由方式也是实际微服务项目中最常见的 Gateway 组合之一。
是否应该开启 Discovery Locator 还可以配置:
1 2 3 4 5 6 spring: cloud: gateway: discovery: locator: enabled: true
开启以后 Gateway 可以根据注册中心中的服务自动创建路由。
默认情况下,通过 DiscoveryClient 创建的路由采用:
这样的 Path,并将 serviceId 从真正发送给下游的 Path 中剥离。目标 URI 使用:
官方同时说明,这种模式需要 Spring Cloud LoadBalancer。
例如注册中心存在:
可能形成类似:
1 2 3 4 5 6 7 /order-service/orders/1 | v order-service | v /orders/1
它很方便,但生产环境需要慎重。
如果注册中心中存在:
1 2 3 4 payment-internal admin-center job-service config-service
而你无条件把所有服务都自动暴露为网关路由,那么原本只应该在内部网络中访问的服务也可能拥有外部入口。
因此生产环境更推荐:
注册中心负责发现服务,Route 负责显式定义哪些 API 可以被外部访问。
也就是:
照样使用服务发现,但 Route 白名单由 Gateway 配置控制。
Path 路由与路径改写 实际系统通常不希望把后端服务真实路径直接暴露出去。
外部可能希望:
后端 Controller 却是:
可以使用:
1 2 filters: - StripPrefix=1
假设:
执行一次 StripPrefix 后会变成:
也可以使用 RewritePath 完成更加灵活的路径转换。
例如:
1 2 3 4 5 6 7 8 9 10 spring: cloud: gateway: routes: - id: order-route uri: lb://order-service predicates: - Path=/api/order/** filters: - RewritePath=/api/order/?(?<segment>.*), /order/${segment}
这样:
会转成:
网关对外 API 路径与内部服务 Controller 路径不必完全一致。
这是 Gateway 很重要的价值之一:内部服务可以重构,而外部 API Contract 尽量保持稳定。
跨域为什么适合放在 Gateway 对于前后端分离系统,如果所有浏览器 API 都经过 Gateway,那么 CORS(Cross-Origin Resource Sharing)策略也适合在 Gateway 统一处理。
Gateway 支持全局 CORS,也支持针对 Route 单独定义 CORS。官方还提供 add-to-simple-url-handler-mapping,用来处理 OPTIONS 预检请求没有命中某些 Route Predicate 的情况。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 spring: cloud: gateway: globalcors: add-to-simple-url-handler-mapping: true cors-configurations: '[/**]' : allowedOriginPatterns: - "https://*.example.com" allowedMethods: - GET - POST - PUT - PATCH - DELETE - OPTIONS allowedHeaders: - "*" exposedHeaders: - Authorization - X-Request-Id allowCredentials: true maxAge: 3600
开发阶段可能经常看到:
1 allowedOriginPatterns: "*"
但生产环境不建议无脑放开全部 Origin,尤其是允许 Cookie 或其他 Credential 的系统。
更合理的是:
1 2 3 https://www.example.com https://admin.example.com https://*.example.com
并明确控制:
1 2 3 4 5 allowedOrigins / allowedOriginPatterns allowedMethods allowedHeaders exposedHeaders allowCredentials
还要记住:
CORS 是浏览器的跨域访问机制,不是系统的身份认证机制。
允许某个 Origin 请求 API,不代表这个 Origin 拥有对应业务权限。
统一鉴权应该如何做 网关是统一入口,所以非常适合承担第一层身份校验。
一种最简单的模型是:
1 2 3 4 5 6 7 8 9 10 11 Client | Authorization: Bearer xxx | Gateway | 校验 Token | 解析 userId / tenantId / role | Downstream Service
可以通过 GlobalFilter 完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 @Component public class AuthenticationFilter implements GlobalFilter , Ordered { private final PathMatcher pathMatcher = new AntPathMatcher (); private static final List<String> WHITE_LIST = List.of( "/auth/login" , "/auth/register" , "/actuator/health" ); @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { String path = exchange.getRequest() .getURI() .getPath(); boolean ignored = WHITE_LIST.stream() .anyMatch(pattern -> pathMatcher.match(pattern, path)); if (ignored) { return chain.filter(exchange); } String authorization = exchange.getRequest() .getHeaders() .getFirst(HttpHeaders.AUTHORIZATION); if (authorization == null || !authorization.startsWith("Bearer " )) { exchange.getResponse() .setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder () { return -100 ; } }
这段代码展示的是 Filter 结构,而不是完整安全方案。
一些早期工程示例会采用:
1 2 3 4 5 6 userId + token | v Redis | 比较 token 是否一致
这种方法可以用于理解网关统一鉴权的基本思路,但企业系统最好直接接入成熟认证体系,比如:
1 2 3 4 5 OAuth 2.0 OpenID Connect JWT Spring Security Resource Server 统一身份中心
而不是长期维护自己手写的 Token 协议。早期实践资料中也明确说明,Redis Token 对比只是为了演示 Gateway 过滤器能力。
401 和 403 不要混用 认证失败:
表示:
授权失败:
表示:
不要为了统一错误响应,把鉴权失败全部返回:
1 500 Internal Server Error
500 应该表示服务器内部发生了非预期故障,而不是“用户没有登录”。
假设内部服务约定:
1 2 3 X-User-Id X-Tenant-Id X-Role-Id
Gateway 解析 Token 后写入这些 Header。
那么 Gateway 接收到外部请求时应该先删除客户端伪造的同名 Header,再写入经过认证得到的可信值。
逻辑应该是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 外部: X-User-Id: admin | v Gateway 删除 | Token -> userId=10086 | Gateway 写入 X-User-Id: 10086 | v 内部服务
否则攻击者可以直接:
1 2 X-User-Id : 1X-Role : ADMIN
然后愉快地给自己升职。
白名单应该精确匹配 登录、验证码、健康检查等接口通常需要跳过登录校验。
可以把它们配置成:
1 2 3 4 5 6 7 gateway: security: permit-paths: - /auth/login - /auth/register - /auth/sms/** - /actuator/health
但是不要简单写:
因为:
和:
可能被错误地放到同一类。
应使用明确的 Path Pattern 语义,并且把:
分开管理。
Gateway 限流到底在限制什么 限流不是简单的:
真正需要先决定的是:
可能是:
1 2 3 4 5 6 7 8 9 整个 Gateway 某 Route 某 API 某 IP 某用户 某租户 某 AppKey 某 API Key 某业务操作
例如:
1 2 3 4 5 6 7 8 9 10 11 /login 每 IP 5 次/分钟 /search 每用户 20 次/秒 /export 每租户 2 个并发任务 /open-api 每 AppKey 100 QPS
这才是业务网关与简单网络限流之间比较明显的区别。
漏桶与令牌桶 限流领域经常遇到两个算法:
1 2 Leaky Bucket Token Bucket
漏桶 把请求想象成进入一个桶。
1 2 3 4 5 6 7 8 9 10 请求 ↓↓↓↓↓↓↓ +---------+ | | | Bucket | | | +----+----+ | | 固定速率 v
请求可以快速进入,但按照固定速率流出。
如果请求进入速度长期大于输出速度,桶最终会满,新请求被拒绝。
优点是:
适合希望严格平滑下游流量的场景。
令牌桶 令牌桶反过来:
1 2 3 4 5 6 7 8 9 10 11 12 Token Generator | | replenishRate v +-------------+ | Token Token | | Token Token | +-------------+ | | Request consume token v Request
系统按照固定速率向桶里放 Token。
请求到来时必须拿到 Token:
1 2 有 Token -> 放行 无 Token -> 拒绝
如果之前一段时间流量很低,桶里可以积累 Token,于是短时间内允许一定程度的突发流量。
因此:
算法
主要特征
漏桶
强调平滑输出
令牌桶
控制平均速率,同时允许一定突发
Gateway Redis RateLimiter
Token Bucket
使用 RequestRateLimiter + Redis 限流 Spring Cloud Gateway 提供 RequestRateLimiter GatewayFilter。
请求超过限流策略后,默认返回:
Redis RateLimiter 使用令牌桶算法,并依赖:
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis-reactive</artifactId > </dependency >
官方 Redis RateLimiter 中几个最重要的参数是:
1 2 3 replenishRate burstCapacity requestedTokens
含义分别为:
1 2 3 4 5 6 7 8 replenishRate 每秒向桶中补充多少 Token burstCapacity 桶最多能装多少 Token requestedTokens 一次请求需要消耗多少 Token,默认 1
假设:
1 2 3 redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 redis-rate-limiter.requestedTokens: 1
含义是:
1 2 3 稳定速率:10 request/s 最大桶容量:20 每次请求消耗:1 token
如果系统经过一段低流量时间,桶中积累了 20 个 Token,就可以允许一次短暂的 20 请求突发。
根据 Path 限流 先提供 KeyResolver:
1 2 3 4 5 6 7 8 9 10 11 @Configuration public class RateLimitConfiguration { @Bean public KeyResolver pathKeyResolver () { return exchange -> Mono.just(exchange.getRequest() .getPath() .value()); } }
Route:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 spring: cloud: gateway: routes: - id: api-route uri: lb://api-service predicates: - Path=/api/** filters: - name: RequestRateLimiter args: key-resolver: "#{@pathKeyResolver}" redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 redis-rate-limiter.requestedTokens: 1
这样不同 Path 会拥有不同限流 Key。
根据用户限流 更常见的做法是认证完成之后使用用户身份:
1 2 3 4 5 6 @Bean public KeyResolver userKeyResolver () { return exchange -> exchange.getPrincipal() .map(Principal::getName); }
这样可以实现:
1 2 3 user:10001 user:10002 user:10003
分别计数。
实际上 Gateway 默认 KeyResolver 就是基于 Principal 名称的 PrincipalNameKeyResolver。
IP 限流最容易踩坑 简单 Demo 经常写:
1 2 exchange.getRequest() .getRemoteAddress()
如果 Gateway 前面没有代理,这通常可以拿到直接连接方地址。
但真实生产架构可能是:
1 2 3 4 5 6 7 8 9 Client | CDN | WAF | Nginx | Gateway
Gateway 的 RemoteAddress 可能只是:
于是所有用户都变成同一个 IP。
另一个极端是无条件相信:
这也危险,因为客户端可能自行伪造。
正确思路是:
1 2 3 4 5 6 7 只信任受控代理层写入的 Forwarded Header | v 明确可信代理链 | v 解析真实 Client IP
不要把“从 Header 读字符串”误认为“获取真实 IP”。
为什么不推荐自己用 Redis INCR 手撸限流 一个非常直观的限流方案是:
1 2 3 4 5 6 7 Redis INCR gateway:ip:xxx 第一次: SET TTL 20s 超过 3 次: 写入黑名单 30s
这种实现用于学习非常容易理解,也可以实现一些特殊的封禁逻辑。实际工程资料中就采用过这种 GlobalFilter + Redis Counter 的做法。
但是如果目的只是普通 API Rate Limit,优先考虑 Gateway 自带 RateLimiter。
因为自己实现以后马上会遇到:
1 2 3 4 5 6 7 8 9 INCR 与 EXPIRE 原子性 TTL 丢失 集群并发 窗口边界 滑动窗口 突发流量 Redis 故障时 fail-open 还是 fail-close 不同接口权重 不同租户额度
最后你会发现自己正在重新开发半个限流组件。
自定义 Filter 更适合:
1 2 3 4 5 持续恶意 IP 封禁 风控名单 特殊业务额度 多级限流 动态策略
普通令牌桶限流优先交给成熟实现。
超时不是可选配置 如果网关完全不控制超时,就可能出现:
1 2 3 4 5 6 7 Client | Gateway | | 一直等待 v Downstream
当下游发生网络故障或处理时间异常,连接长期占用,最终可能拖垮 Gateway 自身。
Gateway 支持两类核心 HTTP Timeout:
1 2 connect-timeout response-timeout
其中:
1 2 3 4 5 connect-timeout 建立连接最多等待多久 response-timeout 建立连接后等待响应最多多久
可以全局配置:
1 2 3 4 5 6 spring: cloud: gateway: httpclient: connect-timeout: 1000 response-timeout: 5s
在 Gateway 4.3.5 中,全局 connect-timeout 使用毫秒,response-timeout 使用 java.time.Duration 格式。
还可以针对 Route 覆盖:
1 2 3 4 5 6 7 8 9 10 11 12 13 spring: cloud: gateway: routes: - id: report-route uri: lb://report-service predicates: - Path=/reports/** metadata: connect-timeout: 2000 response-timeout: 10000
Route 级别的这两个值均以毫秒表示。
为什么需要 Route 级超时?
因为:
1 2 3 4 5 /login 2s /query 3s /file/upload 30s /report/export 60s AI streaming 特殊策略
业务性质完全不同。
如果全部设置:
普通查询接口发生故障后也会挂 60 秒。
如果全部设置:
正常的大文件和长任务又会被误杀。
所以 Timeout 是业务 SLA 的一部分,不是随手复制的配置数字。
重试不是“失败就再试几次” Gateway 提供 Retry Filter。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 spring: cloud: gateway: routes: - id: query-route uri: lb://query-service predicates: - Path=/query/** filters: - name: Retry args: retries: 2 statuses: - BAD_GATEWAY - SERVICE_UNAVAILABLE - GATEWAY_TIMEOUT methods: - GET backoff: firstBackoff: 100ms maxBackoff: 1s factor: 2 basedOnPreviousValue: false
Gateway 4.3.5 中 Retry 支持:
1 2 3 4 5 6 7 8 retries statuses methods series exceptions backoff jitter timeout
如果启用 Filter 但不覆盖默认参数,官方默认值包括:
1 2 3 4 retries = 3 series = 5XX methods = GET exceptions = IOException / TimeoutException
为什么默认 Method 是 GET?
因为:
重复请求通常不会改变业务状态。
而:
如果第一次:
Gateway 再重试一次,就有可能变成:
因此:
重试必须建立在接口幂等性之上。
对于写操作,只有在业务已经实现:
1 2 3 4 5 Idempotency-Key 业务唯一号 数据库唯一约束 幂等表 状态机
等机制之后,才应该考虑自动重试。
还有一个经常被忽视的问题:Gateway 如果重试带 Request Body 的请求,需要缓存请求体,会增加内存压力;而且 Retry Filter 后面的 Filter 也会被再次执行。官方文档对此有明确提醒。
所以重试绝不是:
然后祈祷第十一次世界突然恢复正常。
熔断解决的不是一次失败,而是持续失败 假设 order-service 已经不可用。
如果 Gateway 仍然不断把请求打过去:
1 2 3 4 5 6 Gateway |--------> order-service timeout |--------> order-service timeout |--------> order-service timeout |--------> order-service timeout |--------> order-service timeout
最终:
1 2 3 4 请求大量堆积 线程/连接/内存资源被消耗 Gateway 自己也开始异常 其他正常服务受到影响
Circuit Breaker 的目的就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 发现下游持续异常 | v 打开熔断器 | v 后续请求快速失败 | v 等待恢复窗口 | v 试探下游 | +----+----+ | | 恢复 继续异常 | | 关闭 再次打开
核心思想是:
失败不可怕,持续等待一个已经明显不可用的依赖才可怕。
Hystrix 方案为什么不要再照搬 很多早期 Gateway 教程会写:
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix</artifactId > </dependency >
然后:
1 2 filters: - name: Hystrix
这是早期 Spring Cloud Gateway 时代的实现方式,在旧资料中非常常见。
当前 Gateway 应使用 Spring Cloud CircuitBreaker API。
Gateway 4.3.5 官方文档明确支持:
1 2 3 Spring Cloud CircuitBreaker + Resilience4J
并要求在使用 Resilience4J Reactor 实现时加入:
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId > </dependency >
Route 可以配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 spring: cloud: gateway: routes: - id: order-route uri: lb://order-service predicates: - Path=/orders/** filters: - name: CircuitBreaker args: name: orderCircuitBreaker fallbackUri: forward:/fallback/order
当熔断触发后,请求转发到 Gateway 内部:
官方当前 fallbackUri 支持 forward: URI。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @RestController @RequestMapping("/fallback") public class FallbackController { @GetMapping("/order") public Mono<ResponseEntity<Map<String, Object>>> orderFallback () { Map<String, Object> body = Map.of( "code" , "SERVICE_UNAVAILABLE" , "message" , "订单服务暂时不可用" ); return Mono.just( ResponseEntity .status(HttpStatus.SERVICE_UNAVAILABLE) .body(body) ); } }
不过生产系统不要把 fallback 设计成:
1 2 3 4 5 { "code" : 200 , "message" : "success" , "data" : null }
下游已经失败却还返回业务成功,会把真正的问题藏起来。
Retry、Timeout 和 CircuitBreaker 是一套组合拳 这三个机制不能孤立理解。
1 2 3 4 5 6 7 8 9 10 11 Timeout 决定: 一次调用最多等多久 Retry 决定: 失败以后还要不要再试 CircuitBreaker 决定: 当依赖持续失败时是不是继续调用
例如:
1 2 3 connect timeout = 1s response timeout = 3s retry = 2
最坏情况下,如果设计不当,请求时间可能远大于单次的 3 秒。
如果再叠加:
1 2 3 4 5 6 Frontend Retry Nginx Retry Gateway Retry Feign Retry Service Retry Database Retry
一个客户端请求可以在后端复制成一串请求风暴。
所以必须从调用链整体设计:
1 2 3 4 5 6 7 8 9 10 11 12 13 Client | | Retry? v Gateway | | Retry? v Service A | | Retry? v Service B
重试层级越多,越容易发生 Retry Storm。
比较合理的原则是:
能在最接近失败源的位置做可靠恢复,就不要让调用链每一层都独立重试。
黑名单和访问控制 Gateway 还可以承担一些基础访问控制。
例如:
1 2 3 4 5 6 7 8 9 gateway: security: path-whitelist: - /auth/** - /actuator/health ip-blacklist: - 192.0 .2 .100 - 192.0 .2 .101
然后通过 GlobalFilter:
1 2 3 4 5 6 7 8 9 10 11 Request | 检查可信 Client IP | 黑名单? | +--+--+ | | 是 否 | | 403 continue
但是黑名单并不是完整安全体系。
对于:
1 2 3 4 5 6 DDoS 海量恶意 IP Bot CC 攻击 SQL Injection Web 攻击
更前置的:
1 2 3 4 5 CDN WAF Cloud Firewall Nginx 云负载均衡
通常比 Java Gateway 更适合承担第一层防御。
让一台 JVM 先接住几十万恶意连接,然后再在 Filter 里优雅地返回 403,多少有一点“门已经被撞飞了,再检查访客证”的味道。
GlobalFilter 中不要执行阻塞操作 这是 Gateway 项目最重要的工程规则之一。
Server WebFlux Gateway 基于:
1 2 3 Spring WebFlux Project Reactor Netty
官方也特别指出,WebFlux Gateway 与传统同步 Servlet 编程模型存在明显区别。
因此 Filter 中不要直接做:
1 2 3 4 5 jdbcTemplate.query(...); repository.findById(...); Thread.sleep(...); blockingHttpClient.execute(...); future.get();
如果一个 Netty EventLoop 被阻塞:
1 2 3 4 5 EventLoop-1 | | JDBC 500ms | +---- 这 500ms 其他请求也可能被拖延
大量请求同时发生之后:
所以 Gateway 中的数据访问和网络调用优先采用:
1 2 3 4 Reactive Redis Reactive HTTP Client Reactive Security 异步机制
更重要的是:
不要把业务逻辑搬进 Gateway。
Gateway 应保持轻量。
错误方向:
1 2 3 4 5 6 Gateway ├── 查询订单数据库 ├── 计算价格 ├── 判断库存 ├── 查询用户积分 └── 组装业务结果
正确方向:
1 2 3 4 5 6 7 8 9 Gateway ├── Route ├── Authentication ├── Authorization Edge Check ├── Rate Limit ├── Traffic Policy ├── Header / Path Transform ├── Observability └── Proxy
否则 Gateway 最终会从“网关”成长为第二个单体应用。
请求链路中的服务发现和负载均衡 以:
为例,可以把内部处理理解成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 1. Route Predicate 匹配 /api/orders/** | v 2. 得到 Route uri = lb://order-service | v 3. LoadBalancer 根据 serviceName 查找实例 order-service | +--> 10.0.0.11:8080 +--> 10.0.0.12:8080 +--> 10.0.0.13:8080 | v 4. 选择 ServiceInstance 10.0.0.12:8080 | v 5. Reactor Netty 发出 HTTP 请求
DiscoveryClient Route Locator 官方默认也采用 lb://service-name 创建可负载均衡的 Route。
因此:
负责:
而:
1 Spring Cloud LoadBalancer
负责:
Gateway 则负责:
不要把服务发现、路由和负载均衡三个概念混成一个东西。
Gateway 不是只做请求前处理 利用 pre/post Filter,可以很方便地统计一次调用耗时。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @Component public class RequestCostFilter implements GlobalFilter , Ordered { private static final String START_TIME = RequestCostFilter.class.getName() + ".START_TIME" ; @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { long start = System.nanoTime(); exchange.getAttributes() .put(START_TIME, start); return chain.filter(exchange) .doFinally(signalType -> { Long begin = exchange.getAttribute(START_TIME); if (begin == null ) { return ; } long costMs = (System.nanoTime() - begin) / 1_000_000 ; String path = exchange.getRequest() .getURI() .getPath(); HttpStatusCode status = exchange.getResponse() .getStatusCode(); }); } @Override public int getOrder () { return -50 ; } }
逻辑就是:
1 2 3 4 5 6 7 8 9 10 11 12 Pre: 记录开始时间 | v Downstream | v Post: 结束时间 - 开始时间
这也是理解 Reactive Filter Chain 很好的一个例子。
Gateway 监控应该看哪些指标 网关是整个微服务入口,因此监控优先级非常高。
至少应该关注:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 QPS 响应时间 P50/P95/P99 4xx 5xx 429 连接失败 超时数量 熔断次数 重试次数 每 Route 请求量 每 Route 错误率 下游服务错误分布 Gateway CPU Gateway Memory Direct Memory EventLoop 延迟 Redis RateLimiter 异常
加入:
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency >
后,Gateway Metrics Filter 可以提供:
1 spring.cloud.gateway.requests
指标,并包含:
1 2 3 4 5 6 routeId routeUri outcome status httpStatusCode httpMethod
等标签,还可以与 Prometheus / Grafana 组合使用。
特别建议围绕 Route 构建 Dashboard:
1 2 3 4 5 6 7 8 order-route QPS 1280 P95 120ms P99 450ms 5xx 0.08% 429 0.01% timeout 12/min
因为 Gateway 自己可能是健康的:
但某个 Route 已经:
如果只看 Gateway JVM 指标,就会产生“系统很健康”的错觉。
Gateway 日志应该记录什么 网关访问日志建议至少包含:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 timestamp traceId requestId clientIp method path query routeId targetService status costMs userId tenantId userAgent
敏感字段不要直接记录:
1 2 3 4 5 6 7 8 Authorization Cookie Password AccessToken RefreshToken 银行卡 身份证 完整手机号
尤其不要直接:
1 log.info("headers={}" , request.getHeaders());
因为 Header 里很可能存在:
1 2 Authorization : Bearer eyJ...Cookie : SESSION=...
调试方便五分钟,安全事故调查半年。
TraceId 应该在 Gateway 尽早生成 如果请求没有 TraceId,可以在靠前的 Filter 中生成:
1 2 3 4 5 6 7 8 9 10 11 12 Client | v Gateway | 生成 traceId | +---- order-service | +---- inventory-service | +---- payment-service
从而把一次请求的日志串起来。
统一入口天然就是:
的最佳起点之一。
需要避免这种做法:
1 2 3 4 客户端说: X-Trace-Id=abc Gateway 无条件相信
外部 TraceId 可以作为参考,但内部链路追踪字段是否允许由客户端直接控制,应由安全策略明确规定。
Filter 顺序要形成规范 当 Gateway 功能越来越多,最终可能出现:
1 2 3 4 5 6 7 8 9 10 TraceFilter CorsFilter AuthenticationFilter AuthorizationFilter TenantFilter RateLimitFilter RequestLogFilter GrayFilter SignFilter HeaderFilter
如果每个人都随手:
Filter 顺序会迅速失控。
建议制定统一区间:
Order 范围
用途
-1000 ~ -900
Trace / Request Context
-900 ~ -800
安全 Header 清洗
-800 ~ -700
Authentication
-700 ~ -600
Tenant / App Context
-600 ~ -500
Authorization
-500 ~ -400
风控 / 黑名单
-400 ~ -300
Logging
-300 ~ -200
自定义治理
其他
根据框架内部 Filter 调整
这张表不是 Spring Cloud Gateway 的固定规定,而是一种工程治理方式。
重点不是具体数字,而是:
Filter Order 必须有统一设计,不应该成为“谁写得早谁占一个数字”的公共停车场。
动态路由应该怎么理解 很多系统希望:
1 2 3 4 修改 Nacos 配置 | v Gateway 自动更新 Route
从架构上看,动态 Route 通常需要解决:
1 2 3 4 5 6 Route 存哪里? 怎么加载? 怎么刷新? 怎么验证? 多台 Gateway 如何保持一致? 错误配置如何回滚?
这和:
1 2 3 discovery: locator: enabled: true
不是同一个概念。
Discovery Locator 的重点是:
而真正的动态路由管理系统通常还包括:
1 2 3 4 5 6 7 8 路由配置中心 版本 发布 校验 审计 灰度 回滚 集群刷新
对于规模较小的系统:
1 2 3 4 5 Git 管理 YAML + 配置中心 + 应用刷新
已经足够。
对于大量 API 的企业网关,则应该把 Route 当成配置资产,而不是散落在几十个 application.yml 中。
一个更完整的 Gateway 配置示例 下面把前面的能力组合起来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 server: port: 1000 spring: application: name: gateway data: redis: host: 127.0 .0 .1 port: 6379 cloud: nacos: discovery: server-addr: 127.0 .0 .1 :8848 gateway: httpclient: connect-timeout: 1000 response-timeout: 5s globalcors: add-to-simple-url-handler-mapping: true cors-configurations: '[/**]' : allowedOriginPatterns: - "https://*.example.com" allowedMethods: - GET - POST - PUT - PATCH - DELETE - OPTIONS allowedHeaders: - "*" allowCredentials: true maxAge: 3600 routes: - id: auth-route uri: lb://auth-service predicates: - Path=/api/auth/** filters: - StripPrefix=1 - id: order-query-route uri: lb://order-service predicates: - Path=/api/orders/** metadata: connect-timeout: 1000 response-timeout: 3000 filters: - StripPrefix=1 - name: RequestRateLimiter args: key-resolver: "#{@userKeyResolver}" redis-rate-limiter.replenishRate: 20 redis-rate-limiter.burstCapacity: 40 redis-rate-limiter.requestedTokens: 1 - name: Retry args: retries: 2 statuses: - BAD_GATEWAY - SERVICE_UNAVAILABLE - GATEWAY_TIMEOUT methods: - GET backoff: firstBackoff: 100ms maxBackoff: 1s factor: 2 basedOnPreviousValue: false - name: CircuitBreaker args: name: orderCircuitBreaker fallbackUri: forward:/fallback/order - id: file-route uri: lb://file-service predicates: - Path=/api/files/** metadata: connect-timeout: 2000 response-timeout: 30000 filters: - StripPrefix=1
这份配置已经体现了比较完整的 Gateway 思路:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 统一入口 | v CORS | v Global Authentication | v Route Predicate | v Rate Limit | v Timeout | v Retry | v Circuit Breaker | v LoadBalancer | v Microservice
其中具体的 QPS、Timeout、Retry 次数必须根据业务 SLA 和压测结果制定,不能直接把示例数字当作生产参数。
生产环境最容易出现的几个问题 Gateway 能访问,但是 lb:// 服务找不到 检查:
1 2 3 4 5 6 7 Nacos 是否注册成功 Gateway 是否接入同一个 Namespace Group 是否一致 服务名是否完全一致 LoadBalancer starter 是否存在 Gateway 是否能够访问 Nacos 服务实例是否健康
例如:
而注册中心实际是:
Gateway 不会猜“它俩看起来挺像”。
404 到底是谁返回的 Gateway 出现 404,需要区分两个阶段。
第一种:
例如:
请求:
就没有匹配。
第二种:
1 2 Route 已命中 但改写后的 Path 在下游不存在
例如:
1 2 3 4 5 6 7 8 9 10 11 Gateway: StripPrefix=2 原始: /api/order/123 下游最终: /123 Controller 实际: /order/123
所以排查 404 时要看:
1 2 3 4 5 6 原始 URI RouteId Predicate 改写后 URI 目标 ServiceInstance 下游 Controller Path
不要看到 404 就直接去重启 Nacos。
OPTIONS 请求莫名其妙 404 浏览器正式发送跨域请求前可能先发:
如果 Route Predicate 又限定:
OPTIONS 就可能不命中。
Gateway 官方提供:
1 2 3 4 5 spring: cloud: gateway: globalcors: add-to-simple-url-handler-mapping: true
专门帮助处理这种预检场景。
限流后所有用户一起被限 如果 KeyResolver 返回:
那就是整个接口共享一个桶。
如果想按用户:
如果想按租户:
如果想:
可以构造:
1 tenant:10001:order-route
限流策略正确不正确,80% 取决于 Key 怎么设计,而不仅仅是 replenishRate 写多少。
Filter 中调用数据库导致 Gateway 延迟越来越高 如果 GlobalFilter 中使用阻塞 JDBC:
1 2 3 每请求查一次用户 每请求查一次权限 每请求查一次租户
高峰时 Gateway 很容易成为瓶颈。
可考虑:
1 2 3 4 5 6 JWT 本地解析 Redis Reactive 本地短缓存 权限版本号 异步刷新 批量策略
把网关每次请求必须访问的外部依赖尽量压缩。
重试把故障放大 假设:
下游彻底故障时,理论请求压力可能迅速从:
膨胀成远高于原始流量的调用尝试。
正确组合应该是:
1 2 3 4 5 6 7 8 9 小次数 Retry + Backoff + Jitter + Timeout + CircuitBreaker
不是:
Gateway 成为单点 既然所有请求都经过 Gateway,那么:
几乎等于主动创造一个 Single Point of Failure。
生产环境至少应该:
1 2 3 +--> Gateway-1 Client -> LB -----+--> Gateway-2 +--> Gateway-3
Gateway 实例应该尽量无状态。
共享状态放到:
1 2 3 4 Redis Nacos 统一配置中心 外部身份系统
不要存进某台 Gateway JVM 的本地 Session,然后期待负载均衡器读心。
推荐的职责边界 最终还需要回答一个很重要的问题:
什么逻辑应该放 Gateway,什么逻辑不应该?
比较适合 Gateway:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 路由 服务发现 负载均衡 统一认证入口 粗粒度授权 API Key 校验 签名校验 黑白名单 限流 超时 重试 熔断 灰度路由 协议/Header 转换 TraceId 访问日志 基础审计 CORS
不适合 Gateway:
1 2 3 4 5 6 7 8 9 订单状态计算 库存扣减 结算规则 复杂权限业务 数据库聚合 核心领域逻辑 复杂事务 大量同步 RPC 复杂报表
尤其不要认为:
1 2 Gateway 已经做了权限校验 所以微服务内部不需要任何安全边界
Gateway 是外部入口的一道重要安全边界,但内部服务仍然需要根据系统的 Zero Trust、服务间认证以及权限模型决定是否继续验证调用身份。
一套比较合理的企业网关分层 把整套架构放在一起,可以形成:
flowchart TB
C["Web / APP / OpenAPI Client"]
EDGE["CDN / WAF / Nginx / Cloud LB"]
G["Spring Cloud Gateway"]
subgraph gateway["Gateway Cross-Cutting Concerns"]
TRACE["Trace / RequestId"]
SEC["Authentication"]
ACL["Access Control"]
RL["Rate Limit"]
CB["Circuit Breaker"]
RETRY["Retry / Timeout"]
OBS["Logging / Metrics"]
end
NACOS["Nacos"]
REDIS["Redis"]
subgraph services["Microservices"]
AUTH["Auth Service"]
USER["User Service"]
ORDER["Order Service"]
FILE["File Service"]
end
C --> EDGE
EDGE --> G
G --> TRACE
TRACE --> SEC
SEC --> ACL
ACL --> RL
RL --> CB
CB --> RETRY
RETRY --> OBS
OBS --> AUTH
OBS --> USER
OBS --> ORDER
OBS --> FILE
NACOS -. Discovery .-> G
REDIS -. RateLimit / Shared State .-> G
职责可以概括为:
1 2 3 4 5 6 7 8 9 10 11 Edge Gateway 解决网络边界问题 API Gateway 解决微服务入口治理问题 Microservice 解决领域业务问题 Service Mesh / RPC Governance 解决服务之间通信治理问题
不要试图让一个 Gateway 把四层职责全部包办。
从请求视角重新理解 Gateway 学习 Spring Cloud Gateway 最容易陷入的方式,是按配置项背:
1 2 3 4 5 6 7 8 Path Header StripPrefix RewritePath RequestRateLimiter Retry CircuitBreaker ...
更容易建立长期理解的方式,是从一个请求看整个过程:
1 2 GET /api/orders/10001 Authorization: Bearer xxx
进入系统以后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 1. Edge Layer Client | CDN / WAF / Nginx | Gateway 2. Gateway Route Matching Host ? Path ? Method ? Header ? | v order-route 3. Pre Filter TraceId Client IP Authentication Tenant Context Rate Limit Header Sanitization Path Rewrite 4. Service Discovery lb://order-service | v Nacos | v 10.0.0.12:8080 5. Resilience Connect Timeout Response Timeout Retry CircuitBreaker 6. Proxy Reactor Netty | v order-service 7. Response order-service | v Post Filters | Logging / Metrics / Headers | v Client
这条链路理解了,Gateway 大部分配置都会自然地找到自己的位置。
总结 Spring Cloud Gateway 真正解决的不是“把 /a 转发到 A 服务、把 /b 转发到 B 服务”这么简单的问题,而是为微服务建立一个可治理的统一 API 边界。
它最核心的模型仍然非常简单:
1 2 3 4 5 6 7 Route = Predicate + URI + Filter
但围绕这个模型,可以进一步构建:
1 2 3 4 5 6 7 8 9 10 11 12 13 服务发现 负载均衡 路径改写 统一鉴权 跨域处理 访问控制 Redis 令牌桶限流 Timeout Retry Circuit Breaker Trace Metrics Logging
Spring Cloud Gateway 的核心请求模型从早期 Spring Cloud 2.x 到今天依然保持连续:请求先匹配 Route,再由 Gateway Web Handler 执行对应 Filter Chain,Filter 可以在代理请求前后分别工作。变化更多发生在 Spring Boot、Starter、配置层级以及熔断组件等外围技术上。当前 Gateway 4.3.5 面向 Spring Boot 3 体系,而 Gateway 5.0.2 已进入 Spring Boot 4 / Spring Framework 7 体系,因此维护旧项目时尤其需要避免把旧版本 Hystrix、旧 starter 和新版本配置机械混用。
真正落到生产环境时,可以记住几条原则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Gateway 保持轻量,不承载领域业务。 显式控制外部 Route,不要因为接入注册中心就暴露所有服务。 鉴权失败使用正确的 401 / 403,不要伪装成 500。 Rate Limit 的关键是限流 Key,而不仅仅是 QPS 数字。 Retry 必须建立在幂等性之上。 Timeout、Retry、CircuitBreaker 要作为整体设计。 WebFlux Filter 中避免阻塞调用。 不要信任客户端自行传递的身份 Header 和代理 Header。 Gateway 自己必须高可用。 监控要下钻到 Route,而不仅仅看 Gateway JVM。 版本升级时重新核对 Gateway 官方文档,不要复制五年前的配置。
当这些边界建立起来之后,Spring Cloud Gateway 才真正从一个“反向代理 Java 版”变成微服务架构中的统一流量入口和 API 治理层。