01|Spring Cloud Gateway:请求大门的「智能导航员」
在微服务时代,每一次 API 调用都像快递小哥找门牌号——地址对了,还得看门禁、称重、安检。Spring Cloud Gateway 就是那位 7×24 小时值班的「智能导航员」:先认路、再安检、最后把包裹精准投递到后台小哥手里。今天我们把它的"认路—安检—投递"全过程拆开揉碎,让你一次看懂。
02|架构鸟瞰:三大件 + 一条链
Gateway 的核心只有三张王牌:
| 组件 | 职责 | 类比 |
|---|---|---|
| Route(路由) | 定义「哪个地址→哪个服务」 | 门牌号映射表 |
| Predicate(断言) | 判断「这条请求是否归我管」 | 门禁人脸识别 |
| Filter(过滤器) | 对请求/响应做「加工」 | 安检 X 光机 |
三大件被串成一条 Filter Chain,请求进来后按序执行,最后把 ServerHttpRequest 变成 ServerHttpResponse 原路返回。整个流程异步、非阻塞,基于 WebFlux + Netty,天生给高并发场景开绿灯。
03|请求接收:Netty 如何先把第一道关
-
Netty 监听端口
Gateway 启动时自动注册NettyReactiveWebServerFactory,默认监听 8080(可改server.port)。 -
Reactor 线程模型
BossGroup 接收 TCP 连接 → WorkerGroup 进行 IO 读写 → 事件派发到 WebFlux 的 DispatcherHandler。 -
HttpWebHandlerAdapter
把 Netty 的HttpObject转成 Spring 的ServerHttpRequest,随后交给 RoutePredicateHandlerMapping 做路由匹配。
一句话:Netty 只负责「收快递」,接下来「认路」交给 Spring。
04|路由匹配:Predicate 如何 0.1 ms 找到门牌
Gateway 把 YAML/Properties 里配置的每条路由解析成 RouteDefinition,启动时注册到 RouteDefinitionLocator。请求到达后:
// 伪代码:RoutePredicateHandlerMapping
public Mono<HandlerMethod> getHandler(ServerHttpExchange exchange) {
return routeLocator.getRoutes() // 1. 拿到全部路由
.filter(route -> route.getPredicate() // 2. 断言测试
.test(exchange))
.next() // 3. 只取第一个命中的
.map(route -> new GatewayWebHandler(…)); // 4. 包装成 Handler
}- 匹配顺序 = 配置顺序;命中即返回,不再往后轮询。
- 官方内置 11 种断言:Path、Method、Header、Query、Cookie、Time、RemoteAddr、Weight、CloudFoundry、Host、JWT。
- 支持 AND / OR 组合,例:
spring:
cloud:
gateway:
routes:
- id: user-api
uri: lb://user-service
predicates:
- Path=/api/user/**
- Method=GET
- Header=Token, \d+上面这条路由只认「GET + 路径前缀 + 带数字 Token」的请求,其余一律 404。
05|过滤器链:安检、盖章、装袋一条龙
命中路由后,Gateway 把「前置过滤器」→「代理调用」→「后置过滤器」串成 FilteringWebHandler 责任链:
public Mono<Void> handle(ServerWebExchange exchange) {
return chain.filter(exchange) // ① 前置逻辑
.then(proxyFilter.filter(…)) // ② 发请求到下游
.then(chain.filter(exchange)); // ③ 后置逻辑
}5.1 内置高频过滤器速查
| 过滤器 | 作用 | 典型配置 |
|---|---|---|
| StripPrefix=n | 去掉前 n 段路径 | /api/order/v1/info → /v1/info |
| AddRequestHeader | 向下游加头 | X-Gateway-Version: 3.1.0 |
| Retry | 失败重试 | 3 次、backoff=50ms |
| RequestRateLimiter | 基于 Redis 令牌桶限流 | 每秒 500 次 |
| CircuitBreaker | 熔断 | 失败率≥50% 打开 30s |
5.2 自定义全局过滤器:打印耗时
@Component
public class ElapsedFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long start = System.currentTimeMillis();
return chain.filter(exchange)
.then(Mono.fromRunnable(() ->
log.info("[{}] elapsed: {} ms",
exchange.getRequest().getURI().getRawPath(),
System.currentTimeMillis() - start)));
}
@Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; }
}全局过滤器对 所有路由生效,适合做日志、鉴权、灰度染色等横切逻辑。
06|跳转/代理:如何把 HTTP 转成后台调用
Gateway 默认使用 NettyRoutingFilter 完成代理:
- 把
ServerHttpRequest转成 Netty 的 FullHttpRequest; - 通过 LoadBalancerClient 解析
lb://service-name拿到真实 IP 列表; - 使用 Netty HttpClient 发送请求,支持 HTTP/1.1 与 HTTP/2;
- 收到响应后再封装成
ServerHttpResponse写回客户端。
关键源码片段:
// NettyRoutingFilter
HttpClient.RequestSender sender =
httpClient.request(method)
.uri(url)
.send((req, nettyOut) -> req.headers(headers)
.send(request.getBody()));
return sender.responseConnection((res, conn) -> {
exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
return response.writeWith(conn.inbound().receive().retain());
});整个过程异步、背压、零拷贝,内存峰值远低于 Zuul 1.x。
07|实战:统一加签、灰度、限流三合一
需求:
- 所有出网关的请求带
X-Gateway-Sign头; - 20% 流量导到
user-service-canary; - 每个 IP 每秒最多 100 次调用。
7.1 路由层灰度
spring:
cloud:
gateway:
routes:
- id: user-gray
uri: lb://user-service-canary
predicates:
- Path=/api/user/**
- Weight=group1, 20
- id: user-stable
uri: lb://user-service
predicates:
- Path=/api/user/**
- Weight=group1, 807.2 限流 & 加签
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder,
SignGatewayFilterFactory signFactory,
RedisRateLimiter rateLimiter) {
return builder.routes()
.route("user-api", r -> r.path("/api/user/**")
.filters(f -> f
.requestRateLimiter(c -> c.setRateLimiter(rateLimiter)
.setKeyResolver(exchange ->
Mono.just(getClientIp(exchange))))
.filter(signFactory.apply(new SignGatewayFilterFactory.Config())))
.uri("lb://user-service"))
.build();
}提示:SignGatewayFilterFactory 只需实现
apply(Config)返回GatewayFilter,即可把加签逻辑插到任意位置。
08|性能调优:让网关再快 30%
-
WebFlux 线程池
默认reactor.netty.ioWorkerCount= CPU 核数 × 2;若容器核数被 cgroup 限制,显式设置-Dreactor.netty.ioWorkerCount=8。 -
连接池复用
全局配置:spring: cloud: gateway: httpclient: pool: maxConnections: 512 # 全局最大连接 acquireTimeout: 3s compression: true # 开启 gzip -
禁用不必要的编解码器
如果只做反向代理,不需要视图解析,把spring.main.web-application-type=reactive并排除spring-boot-starter-thymeleaf。 -
响应缓存
对读多写少接口,用LocalResponseCache过滤器把 200 响应按 URI 缓存 5s,可降 50% QPS 到下游。 -
Netty 零拷贝
文件上传场景开启file-region=true,大文件直接走DefaultFileRegion,避免堆内拷贝。
09|常见翻车现场 & 排查清单
| 症状 | 可能原因 | 定位工具 |
|---|---|---|
| 504 Timeout | 下游慢 / 连接池耗尽 | logging.level.org.springframework.cloud.gateway=DEBUG |
| 404 但服务正常 | Predicate 顺序 / StripPrefix 多截 | curl -v + /actuator/gateway/routes |
| 限流失效 | KeyResolver 拿到的是代理 IP | 在 X-Forwarded-For 里取第一个非私有地址 |
| 高 CPU | 线程池打满 + 大量 SSL 握手 | jstack 看 reactor-http-epoll-* 是否阻塞 |
10|小结 & 思考题
- Spring Cloud Gateway = Route + Predicate + Filter 三元组,基于 WebFlux/Netty 全异步。
- 路由匹配顺序优先,过滤器链责任链模式,代理调用长连接复用。
- 通过自定义全局过滤器,可低成本实现「鉴权、加签、灰度、缓存」等横切能力。
- 性能瓶颈 80% 在连接池和线程数,调优先盯 Netty 配置。
思考题:
如果业务要求「同一用户 5 分钟内只能调用 1000 次」,你会把限流 Key 放在哪个维度(IP、userId、JWT 里的 jti)?如何防止用户刷接口不断换 IP?欢迎在评论区聊聊你的方案!
11|一键跑通示例仓库
git clone https://github.com/your-org/gateway-lab.git
cd gateway-lab && docker-compose up
# 已内置 Redis、Consul、user-service / user-service-canary
# 网关端口 8080,访问
curl http://localhost:8080/api/user/profile
# 看实时路由
curl http://localhost:8080/actuator/gateway/routes | jq把代码拉到本地,改一行配置,就能看到灰度流量在两个版本间来回切换——动手永远比看文章更治愈。
(此内容由 AI 辅助生成,仅供参考)