目录
[一、核心认知:Gateway 解决什么问题](#一、核心认知:Gateway 解决什么问题)
[1.1 本质定位](#1.1 本质定位)
[1.2 和 Netflix Zuul 的核心区别](#1.2 和 Netflix Zuul 的核心区别)
[2.1 三大核心概念](#2.1 三大核心概念)
[2.2 完整执行流程](#2.2 完整执行流程)
[三、Spring Cloud 整合实战](#三、Spring Cloud 整合实战)
[3.1 引入依赖](#3.1 引入依赖)
[3.2 基础路由配置](#3.2 基础路由配置)
[示例 1:固定地址转发(直连模式)](#示例 1:固定地址转发(直连模式))
[示例 2:服务发现模式(生产标准用法)](#示例 2:服务发现模式(生产标准用法))
[4.1 常用断言工厂](#4.1 常用断言工厂)
[1. Path 路径断言(最常用)](#1. Path 路径断言(最常用))
[2. After / Before 时间断言](#2. After / Before 时间断言)
[3. Between 时间区间断言](#3. Between 时间区间断言)
[4. Header 请求头断言](#4. Header 请求头断言)
[5. Cookie 断言](#5. Cookie 断言)
[6. Query 请求参数断言](#6. Query 请求参数断言)
[7. Method 请求方法断言](#7. Method 请求方法断言)
[8. Host 域名断言](#8. Host 域名断言)
[9. Weight 权重断言](#9. Weight 权重断言)
[4.2 常用局部过滤器](#4.2 常用局部过滤器)
[1. StripPrefix 路径截取(最高频)](#1. StripPrefix 路径截取(最高频))
[2. AddRequestHeader / AddResponseHeader](#2. AddRequestHeader / AddResponseHeader)
[3. PrefixPath](#3. PrefixPath)
[4. Retry 重试](#4. Retry 重试)
[4.3 全局过滤器与自定义鉴权](#4.3 全局过滤器与自定义鉴权)
[5.1 请求处理全链路源码拆解](#5.1 请求处理全链路源码拆解)
[第一步:DispatcherHandler 分发请求](#第一步:DispatcherHandler 分发请求)
[第二步:RoutePredicateHandlerMapping 匹配路由](#第二步:RoutePredicateHandlerMapping 匹配路由)
[第三步:FilteringWebHandler 执行过滤器链](#第三步:FilteringWebHandler 执行过滤器链)
[5.2 路由加载与动态刷新](#5.2 路由加载与动态刷新)
[6.1 网关限流(Redis 令牌桶实现)](#6.1 网关限流(Redis 令牌桶实现))
[6.2 整合 Sentinel 限流熔断](#6.2 整合 Sentinel 限流熔断)
[6.3 全局跨域配置](#6.3 全局跨域配置)
[6.4 全局异常处理](#6.4 全局异常处理)
[1. 启动失败:和 Spring MVC 冲突](#1. 启动失败:和 Spring MVC 冲突)
[2. StripPrefix 路径截取错误](#2. StripPrefix 路径截取错误)
[3. 请求体只能读取一次](#3. 请求体只能读取一次)
[4. 跨域配置不生效](#4. 跨域配置不生效)
[5. 大文件上传超时 / 失败](#5. 大文件上传超时 / 失败)
[6. 过滤器执行顺序混乱](#6. 过滤器执行顺序混乱)
[7. 服务名路由 404](#7. 服务名路由 404)
(基于 Spring Cloud Alibaba 2.2.9.RELEASE + Spring Cloud Gateway 2.2.9.RELEASE 整理,覆盖实战、原理、踩坑)
一、核心认知:Gateway 解决什么问题
1.1 本质定位
Spring Cloud Gateway 是 Spring 官方推出的第二代微服务网关,替代了已经停止维护的 Netflix Zuul 1.x。它基于 Spring 5、Spring Boot 2、WebFlux、Reactor、Netty 构建,全程异步非阻塞,性能比传统 Servlet 架构的 Zuul 高出数倍。
在微服务架构里,网关是所有外部请求的统一入口,相当于整个系统的「大门保安」,所有客户端不直接访问后端服务,全部先走网关。核心承担这几类工作:
- 路由转发:根据请求路径、参数等规则,把请求转发到对应的后端服务
- 统一管控:鉴权、日志、限流、熔断、跨域、黑白名单等通用逻辑,不用每个服务都写一遍
- 服务透明化:对外只暴露网关地址,后端服务集群、扩缩容对前端完全透明
- 流量治理:灰度发布、流量染色、版本路由等高级流量调度能力
前提:Gateway 底层是响应式 WebFlux + Netty,不是 Servlet 架构,因此不能和 spring-boot-starter-web 同时引入,否则会直接启动失败,这是最高频的踩坑点。
1.2 和 Netflix Zuul 的核心区别
| 对比维度 | Spring Cloud Gateway | Netflix Zuul 1.x |
|---|---|---|
| 底层架构 | Netty + WebFlux,响应式非阻塞 | Servlet,同步阻塞 |
| 线程模型 | 少量线程即可支撑高并发,资源占用低 | 每个请求一个线程,高并发下线程膨胀严重 |
| 性能 | 官方测试是 Zuul 的 2~4 倍 | 性能较低,易成为瓶颈 |
| 维护主体 | Spring 官方持续迭代 | Netflix 已停止维护 |
| 功能丰富度 | 断言、过滤器、限流、熔断、灰度等全内置 | 基础路由过滤,高级功能需自己扩展 |
| 异步支持 | 原生支持异步、响应式编程 | 仅同步模式 |
面试考点:Gateway 性能高的核心原因是「非阻塞 + 事件驱动」的 Netty 模型,不用为每个请求分配独立线程,大大降低了线程切换和内存开销,高并发下优势极其明显。
二、核心架构与执行流程
2.1 三大核心概念
Gateway 所有功能都围绕三个核心概念展开:
-
Route(路由) 网关的最小配置单元,一条路由对应一套转发规则。由 ID、目标 URI、断言集合、过滤器集合四部分组成。请求匹配上某条路由后,就会被转发到对应的目标地址。
-
Predicate(断言) 路由的匹配条件,用来判断请求能不能命中当前路由。支持多种匹配维度:请求路径、请求时间、请求头、Cookie、请求参数、请求方法等。多个断言可以组合使用,全部满足才算匹配成功。 本质是 Java 8 的 Predicate 函数式接口,输入是 ServerWebExchange,输出是 boolean。
-
Filter(过滤器) 请求转发的前后置处理器,可以修改请求、响应,也可以做鉴权、限流、日志等逻辑。分为两类:
- 局部过滤器
GatewayFilter:只对配置了它的单条路由生效 - 全局过滤器
GlobalFilter:对所有路由都生效,无需单独配置
- 局部过滤器
2.2 完整执行流程

流程里两个关键点:
- 路由匹配是按顺序匹配,命中第一条就停止,所以配置路由时要把精确的规则放前面,宽泛的放后面,避免被宽泛路由提前拦截。
- 过滤器是责任链模式,前置逻辑正序执行,后置逻辑倒序执行,和 Servlet Filter、Spring MVC 拦截器的执行规律一致。
三、Spring Cloud 整合实战
3.1 引入依赖
网关服务单独创建项目,引入网关和服务发现依赖。版本由 Spring Cloud Alibaba 父工程统一管理,无需单独指定。
XML
<dependencies>
<!-- Nacos 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud Gateway 网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
提醒:绝对不能引入
spring-boot-starter-web,Gateway 依赖的是spring-boot-starter-webflux,两者冲突会直接启动报错。如果项目里间接引入了 web,一定要排除掉。
3.2 基础路由配置
路由配置支持 YAML 配置和 Java 代码配置两种,生产环境 90% 都用 YAML,直观易维护。
示例 1:固定地址转发(直连模式)
适合后端地址固定的场景,直接写死目标 IP 和端口。
bash
server:
port: 9000
spring:
cloud:
gateway:
routes:
- id: user-service-route # 路由唯一ID,自定义即可
uri: http://127.0.0.1:8081 # 转发目标地址
predicates:
- Path=/api/user/** # 断言:路径匹配/api/user开头的请求
filters:
- StripPrefix=1 # 过滤器:去掉第一层路径前缀/api
效果:请求 http://网关地址:9000/api/user/1,会被转发到 http://127.0.0.1:8081/user/1。
示例 2:服务发现模式(生产标准用法)
结合 Nacos 服务注册中心,用服务名替代固定地址,自动结合 LoadBalancer 做负载均衡,后端扩缩容不用改网关配置。
bash
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: false # 关闭自动按服务名生成路由,手动配置更可控
routes:
- id: user-service-route
uri: lb://user-service # lb:// 开头代表从注册中心找服务,做负载均衡
predicates:
- Path=/api/user/**
filters:
- StripPrefix=1
- id: order-service-route
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- StripPrefix=1
lb://是核心前缀,告诉网关启用负载均衡客户端,从注册中心拉取user-service的实例列表,按负载均衡策略选择实例转发。
四、核心功能详解
4.1 常用断言工厂
Gateway 内置了十几种断言工厂,覆盖绝大多数匹配场景,几个最常用的如下:
| 断言类型 | 作用 | 配置示例 |
|---|---|---|
| Path | 按请求路径匹配,最常用 | - Path=/api/user/**,/api/admin/** |
| After | 指定时间之后的请求才匹配 | - After=2025-01-01T00:00:00+08:00[Asia/Shanghai] |
| Before | 指定时间之前的请求才匹配 | - Before=2025-06-01T00:00:00+08:00[Asia/Shanghai] |
| Header | 请求头包含指定字段才匹配 | - Header=token, [a-z0-9]+ |
| Cookie | Cookie 包含指定字段才匹配 | - Cookie=userId, \\d+ |
| Query | 请求参数包含指定字段才匹配 | - Query=source, h5 |
| Method | 指定请求方法才匹配 | - Method=GET,POST |
多个断言是「与」的关系,必须全部满足才能命中路由。
1. Path 路径断言(最常用)
根据请求的 URI 路径匹配,支持 ** 通配符匹配多级路径,* 匹配单级路径。
bash
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
# 匹配所有 /api/user 开头的请求,例如 /api/user/1、/api/user/list/page
- Path=/api/user/**
filters:
- StripPrefix=1
多路径写法:用逗号分隔,命中任意一个路径即可
bash
predicates:
- Path=/api/user/**,/api/member/**
2. After / Before 时间断言
After:只有在指定时间之后发起的请求才匹配Before:只有在指定时间之前发起的请求才匹配
时间格式必须是 ZonedDateTime 标准格式,带时区。
bash
spring:
cloud:
gateway:
routes:
- id: activity-route
uri: lb://activity-service
predicates:
# 2025年11月1日 0点 之后发起的请求才命中该路由
- After=2025-11-01T00:00:00+08:00[Asia/Shanghai]
场景:大促活动提前配置好路由,到时间自动生效,不用半夜发版;或者活动结束自动下线,切回普通接口。
3. Between 时间区间断言
请求时间在两个时间点之间才匹配,适合限时活动。
bash
predicates:
# 仅在双十一当天 0点到24点之间生效
- Between=2025-11-11T00:00:00+08:00[Asia/Shanghai], 2025-11-12T00:00:00+08:00[Asia/Shanghai]
4. Header 请求头断言
根据请求头中的字段匹配,支持正则表达式校验值。
bash
spring:
cloud:
gateway:
routes:
- id: gray-route
uri: lb://user-service-gray
predicates:
- Path=/api/user/**
# 请求头必须包含 version 字段,且值为 v2 才命中灰度路由
- Header=version, v2
正则写法:值满足正则规则才匹配
java
predicates:
# token 请求头的值必须是纯数字才匹配
- Header=token, \d+
实战场景:灰度发布时,前端在请求头带上版本号,网关把流量切到灰度集群。
5. Cookie 断言
根据 Cookie 中的字段匹配,同样支持正则。
bash
predicates:
# Cookie 中必须有 userType 字段,且值为 vip 才命中
- Cookie=userType, vip
实战场景:根据用户等级分流,VIP 用户走专属服务集群。
6. Query 请求参数断言
根据 URL 后面的查询参数匹配。
bash
predicates:
# 请求参数中必须包含 source,且值为 h5 才命中
- Query=source, h5
只判断存在、不校验值:
bash
predicates:
# 只要有 debug 参数就命中,不管值是什么
- Query=debug
7. Method 请求方法断言
只允许指定的 HTTP 方法命中路由。
bash
predicates:
# 只有 GET 和 POST 请求才命中
- Method=GET,POST
实战场景:读写分离路由,写操作单独走一条高可用链路。
8. Host 域名断言
根据请求的 Host 域名匹配。
bash
predicates:
# 只有通过 api.example.com 域名访问才命中
- Host=api.example.com
多域名写法:
bash
predicates:
- Host=api.example.com, m.example.com
场景:一套网关集群承载多个业务域名,按域名分发到不同业务服务。
9. Weight 权重断言
按权重比例把流量分配到不同的目标服务,是灰度发布、流量切分的核心手段。
bash
spring:
cloud:
gateway:
routes:
- id: user-v1-route
uri: lb://user-service-v1
predicates:
- Path=/api/user/**
# 同组名 weight-group,权重 80
- Weight=user-weight-group, 80
- id: user-v2-route
uri: lb://user-service-v2
predicates:
- Path=/api/user/**
# 同组名,权重 20
- Weight=user-weight-group, 20
效果:80% 的流量走 v1 版本,20% 的流量走 v2 版本;两条路由必须用同一个分组名才能按权重分配流量。
多断言组合示例(生产常用写法)
同一条路由下多个断言同时生效,全部满足才命中:
bash
spring:
cloud:
gateway:
routes:
- id: vip-activity-route
uri: lb://activity-service
predicates:
# 路径匹配
- Path=/api/activity/seckill
# 活动时间内
- Between=2025-11-11T00:00:00+08:00[Asia/Shanghai], 2025-11-11T23:59:59+08:00[Asia/Shanghai]
# VIP用户Cookie
- Cookie=userLevel, vip
# POST请求
- Method=POST
只有同时满足「路径正确 + 活动时间内 + VIP 用户 + POST 方法」四个条件,才会命中这条秒杀路由。
4.2 常用局部过滤器
局部过滤器只对当前路由生效,需要配置在对应路由的 filters 下。
1. StripPrefix 路径截取(最高频)
作用:去掉 URL 中前面的 N 层路径前缀。
bash
filters:
- StripPrefix=2
请求路径 /api/biz/user/1,截取 2 层前缀后,转发到后端的路径变成 /user/1。
为什么要截前缀:网关层用前缀区分不同服务,后端服务本身不需要这个前缀,所以要截掉,是最常用的过滤器,几乎每条路由都会配。
2. AddRequestHeader / AddResponseHeader
作用:给请求 / 响应添加自定义头,常用于传递网关标识、灰度标记、链路追踪 ID。
bash
filters:
- AddRequestHeader=gateway-route, user-service
- AddResponseHeader=system-version, v1.2.0
3. PrefixPath
作用:给请求路径统一添加前缀,和 StripPrefix 相反。
bash
filters:
- PrefixPath=/inner
请求路径 /user/1,转发时变成 /inner/user/1。
4. Retry 重试
作用:后端调用失败时自动重试,适合幂等接口。
bash
filters:
- name: Retry
args:
retries: 2 # 重试次数
statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE # 触发重试的状态码
methods: GET # 只对GET请求重试,避免写接口重复执行
4.3 全局过滤器与自定义鉴权
全局过滤器对所有路由生效,不用每条路由单独配置,适合做全局限流、鉴权、日志、请求 ID 生成等通用逻辑。
内置的核心全局过滤器:
LoadBalancerClientFilter:解析lb://前缀,调用负载均衡器选择实例,把服务名替换成实际 IP 端口NettyRoutingFilter:真正发起 HTTP 请求转发的过滤器,是过滤器链的最后一步NettyWriteResponseFilter:把后端返回的响应写回给客户端
实战:自定义全局鉴权过滤器
所有请求必须携带有效 token 才能放行,是网关最常用的能力之一。
java
/**
* 全局鉴权过滤器
* 实现GlobalFilter + Ordered接口,所有路由自动生效
*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 白名单路径直接放行,比如登录、验证码接口
// 这种可以放配置文件里面动态读取
if (path.startsWith("/api/user/login") || path.startsWith("/api/common/captcha")) {
return chain.filter(exchange);
}
// 从请求头获取token
List<String> tokenList = request.getHeaders().get("token");
if (tokenList == null || tokenList.isEmpty()) {
// 没有token,返回401未授权
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
String result = "{\"code\":401,\"message\":\"未登录,请先登录\"}";
DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
String token = tokenList.get(0);
// 这里可以调用鉴权服务、校验token有效性
// 校验通过,把用户ID放到请求头里传给下游服务
ServerHttpRequest newRequest = request.mutate()
.header("userId", parseUserIdFromToken(token))
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
}
@Override
public int getOrder() {
// 顺序值越小越先执行,鉴权要放在最前面,比限流、转发都早
return -100;
}
private String parseUserIdFromToken(String token) {
// 实际项目里解析JWT或者调用用户中心校验
return "1001";
}
}
返回响应的时候要用响应式的 Mono 写法,不能像 MVC 里直接 PrintWriter 输出,因为 Gateway 是 WebFlux 异步模型。
五、底层核心原理
5.1 请求处理全链路源码拆解
一次请求从进入网关到转发出去,核心经过三个核心组件,按顺序执行:
第一步:DispatcherHandler 分发请求
它是 WebFlux 的总请求分发器,相当于 Spring MVC 里的 DispatcherServlet。所有请求进来先到这里,它会遍历所有的 HandlerMapping,找到能处理当前请求的那个。 Gateway 自定义的 RoutePredicateHandlerMapping 就是其中一个 HandlerMapping,专门负责路由匹配。
第二步:RoutePredicateHandlerMapping 匹配路由
核心方法 getHandlerInternal,做两件事:
- 从
RouteLocator里拿到所有已加载的路由定义 - 按配置顺序遍历每条路由,调用路由上所有断言的
test()方法 - 找到第一条全部断言都匹配成功的路由,包装后返回给 DispatcherHandler
为什么是第一条匹配就返回?因为路由是有优先级的,配置顺序就是优先级顺序,前面的优先级高,匹配上就不再往后找,这也是为什么精确路由要写在宽泛路由前面的原因。
第三步:FilteringWebHandler 执行过滤器链
拿到匹配的路由后,由 FilteringWebHandler 来执行,核心逻辑:
- 收集所有全局过滤器 + 当前路由的所有局部过滤器
- 按
order值从小到大排序,组成完整的过滤器链 - 依次执行每个过滤器的
filter方法,形成责任链调用 - 最终执行到
NettyRoutingFilter,通过 Netty 客户端向后端服务发起真实的 HTTP 请求 - 后端返回响应后,按倒序执行每个过滤器的后置逻辑
5.2 路由加载与动态刷新
所有路由定义都由 RouteDefinitionLocator 负责加载,这是一个顶层接口,有多个实现:
PropertiesRouteDefinitionLocator:从 application.yml 配置文件读取路由DiscoveryClientRouteDefinitionLocator:从注册中心自动生成路由(开启动态路由时用)- 支持从 Nacos、Apollo 等配置中心加载路由,实现动态刷新
动态刷新原理: Gateway 支持监听配置变更事件,收到变更后会重新调用 RouteLocator 加载路由,更新内存中的路由缓存,不用重启网关就能生效,生产环境做路由变更非常方便。
六、生产级高级特性
6.1 网关限流(Redis 令牌桶实现)
Gateway 内置了基于 Redis 的令牌桶限流过滤器,生产环境最常用。原理是每秒生成固定数量的令牌放到桶里,每个请求拿一个令牌,拿不到就被限流。
第一步:引入依赖
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
第二步:配置限流规则
bash
spring:
redis:
host: 127.0.0.1
port: 6379
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100 # 每秒生成令牌数(平均QPS)
redis-rate-limiter.burstCapacity: 200 # 令牌桶最大容量(峰值QPS)
key-resolver: "#{@ipKeyResolver}" # 限流维度的Bean
第三步:定义限流维度
java
/**
* 按IP地址限流
* 也可以按用户ID、按接口路径等自定义维度
*/
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
在 Gateway 中,如果配置了 RequestRateLimiter 过滤器,它就需要知道"到底把谁算作一个用户"。
-
如果不配置
KeyResolver:Gateway 会把所有请求的流量混在一起统计(比如限流 100 QPS,是所有用户加起来只能有 100 QPS)。 -
配置了这段代码后 :Gateway 会以 IP 为维度来统计流量。
举个例子 :
你配置了全局限流为 10 次/秒。
-
用户 A(IP:192.168.1.1)一秒内请求了 10 次 → 触发限流,返回 429。
-
用户 B(IP:192.168.1.2)在同一秒内也请求了 10 次 → 不受影响 ,正常通过。
因为 KeyResolver 返回的是 IP,Gateway 内部会把这个 IP 作为 Redis(或本地内存)计数器的 Key,各个 IP 的计数器是互相隔离的。
6.2 整合 Sentinel 限流熔断
如果需要更灵活的限流、熔断降级,可以直接整合 Sentinel,和微服务体系统一管控。 引入 sentinel-spring-cloud-gateway-adapter 依赖后,配置网关流控规则、降级规则,支持按路由、按 API 分组限流,支持慢调用熔断、异常比例熔断,和之前 Sentinel 笔记的逻辑完全一致。
6.3 全局跨域配置
网关层统一处理跨域,后端服务就不用再配了,推荐用代码配置,比 YAML 兼容性更好。
java
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
// 允许的跨域源,生产环境不要写*,指定具体域名
config.addAllowedOriginPattern("*");
// 允许所有请求头
config.addAllowedHeader("*");
// 允许所有请求方法
config.addAllowedMethod("*");
// 允许携带凭证(Cookie)
config.setAllowCredentials(true);
// 预检请求缓存时间,3600秒内不用再发预检请求
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
6.4 全局异常处理
Gateway 是 WebFlux 架构,不能用 Spring MVC 的 @RestControllerAdvice 处理异常,需要自定义异常处理器。
默认网关报错会返回白标签错误页,很不友好,自定义后统一返回 JSON 格式,和后端服务的返回格式对齐。核心是实现 ErrorWebExceptionHandler 接口,重写异常处理逻辑,对限流异常、404、500 等做不同的提示。
七、真实项目踩坑汇总
1. 启动失败:和 Spring MVC 冲突
现象:启动时报错 Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway。 原因:引入了 spring-boot-starter-web,或者间接依赖里带了 web。
解决:排除 web 依赖,确保只有 spring-boot-starter-webflux。如果是公共依赖包带进来的,用 exclusion 排除掉。
2. StripPrefix 路径截取错误
现象:转发后后端报 404,抓包发现路径不对。
原因:StripPrefix 的数值和实际前缀层数不匹配,比如有两层前缀只截了一层。
解决:数清楚网关路径有几层前缀,严格对应;复杂路径可以用 Postman 先测单条路由,确认转发路径正确。
3. 请求体只能读取一次
现象:自定义过滤器里读取了请求体做日志、参数校验,转发到后端后后端拿不到请求体。
原因:WebFlux 的请求体是数据流,只能消费一次,读取过就没了。
解决:用 ServerHttpRequestDecorator 包装请求,把请求体缓存起来,下游读取的时候重新生成流。注意大文件请求不要缓存,会占内存。
4. 跨域配置不生效
现象:前端浏览器报跨域错误,明明配了 CorsFilter。 常见原因:
- 过滤器顺序太低,被其他过滤器抢先返回了错误响应,没走到跨域过滤器
- 同时配置了 YAML 跨域和代码跨域,配置冲突
- 后端服务也配了跨域,重复设置导致浏览器拦截 解决:代码配置设置最高优先级,只在网关层做跨域,后端服务关闭跨域配置。
5. 大文件上传超时 / 失败
现象:上传几兆的文件就报错或者超时。
原因:Gateway 默认对请求体大小有限制,内存缓冲区默认不大。
解决:调大 spring.codec.max-in-memory-size 参数,同时调整 Netty 的超时时间;超大文件建议走对象存储直传,不要走网关。
6. 过滤器执行顺序混乱
现象:有时候鉴权在限流后面,有时候又在前面,行为不稳定。
原因:自定义过滤器没指定 order,默认顺序不可控。
解决:所有自定义过滤器都实现 Ordered 接口,显式指定 order 值。约定好顺序规范:鉴权(-100)→ 日志(-50)→ 限流(0)→ 业务过滤器 → 转发(100+)。
7. 服务名路由 404
现象:配置了 lb://user-service 但报 404 或者找不到服务。 排查顺序:
- 检查服务有没有正确注册到 Nacos,服务名是不是完全一致(大小写敏感)
- 检查网关有没有引入 nacos discovery 依赖
- 检查 uri 是不是
lb://开头,不是 http:// - 检查路径截取是不是正确,后端有没有对应的接口
八、复习速记
- 核心定位:Spring 官方第二代网关,基于 Netty + WebFlux 非阻塞架构,高性能,替代 Zuul,是微服务的统一入口。
- 三大核心:Route 路由(转发规则)、Predicate 断言(匹配条件)、Filter 过滤器(前后置处理)。
- 执行流程:DispatcherHandler 分发 → 路由断言匹配 → 组装过滤器链 → 前置过滤 → Netty 转发 → 后置过滤 → 返回响应。
- 过滤器分类:局部过滤器单路由生效,全局过滤器全路由生效;内置负载均衡、转发等核心全局过滤器。
- 限流方案:内置 Redis 令牌桶限流,支持 IP、用户、路径多维度;也可整合 Sentinel 实现更丰富的流量治理。
- 核心红线:不能和 Spring MVC 共存;请求体只能读一次;路由按顺序匹配,精确规则放前面。
- 生产必做:统一鉴权、限流熔断、全局异常、跨域处理、服务发现整合、日志监控、动态路由配置。