服务网关
Java 架构师 · 服务治理
网关是系统对外的统一入口,承担路由、鉴权、限流、灰度与协议转换。它把横切关注点从业务服务中剥离,是微服务治理的第一道闸门。Spring Cloud Gateway 之所以成为 Java 体系主流,是因为它构建在 WebFlux(响应式编程)+ Netty 之上,能够用少量线程支撑高并发,并通过 Route/Predicate/Filter 的可编程模型与注册中心联动做动态治理。本章深入它的响应式链路、核心模型与限流算法,并与 Kong/Nginx 做工程权衡。
一、网关的定位与本质
网关 = "边缘代理 + 智能路由 + 治理收口"。它解决的是:认证、限流、灰度、日志、跨域这些每个服务都要做、却又不该每个服务都重复做的横切逻辑。
与 Nginx 的区别:
- Nginx(C + 事件驱动):偏静态转发与极致性能,配置 reload 才生效,动态性弱。
- Gateway(JVM + WebFlux):运行在应用内、可编程、与 Nacos 等注册中心实时联动,适合做动态路由、业务级鉴权与限流。
- Kong(OpenResty/Lua):插件生态丰富、多语言友好,适合需要现成限流/鉴权插件且非纯 Java 栈的团队。
选型判断
纯 Java 体系 + 已用 Spring Cloud → Gateway;多语言统一治理 + 要现成插件 → Kong;只做高性能静态转发/CDN 边缘 → Nginx。三者也可组合:Nginx/LB 在最外层,Gateway 在业务网关层。
二、Gateway 的响应式链路(WebFlux 底层)
Gateway 不是传统的 Servlet 同步模型,而是基于 Project Reactor 响应式流:请求进来后不占用 Tomcat 线程,而是封装成 Mono/Flux 在事件循环(EventLoop)上处理,I/O 等待时线程让出,由 Netty 在就绪时回调。这让少量线程即可扛高并发,但也意味着:Filter 里不能写阻塞代码(如同步 JDBC、Thread.sleep),否则会拖垮整个事件循环。
一次请求的完整链路:
关键认知:WebFlux 的"非阻塞"靠整条链路都不阻塞。如果你在 Filter 里调了一个阻塞的 RestTemplate 或 synchronized,响应式优势归零,线程被占满,网关反而成为瓶颈。
三、核心模型:Route = ID + URI + Predicate + Filter
- Route(路由):转发的基本单位。
- Predicate(断言):匹配条件,基于 Path、Header、Host、Method、时间、Cookie 等。多个 Predicate 是 AND 关系。
- Filter(过滤器):请求/响应链路上做改写、鉴权、埋点、限流。分
GlobalFilter(全局)与GatewayFilter(路由级)。
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service # lb:// 走负载均衡(从 Nacos 取实例)
predicates:
- Path=/api/order/** # 路径匹配
- Header=X-Version, v2 # 且 Header 命中 → 灰度
filters:
- StripPrefix=1 # 去掉第一级前缀再转发
- name: RequestRateLimiter # 限流过滤器
args:
redis-rate-limiter.replenishRate: 100 # 每秒补充 100 令牌
redis-rate-limiter.burstCapacity: 200 # 桶容量 200(允许突发)
key-resolver: "#{@userKeyResolver}" # 按用户维度限流自定义 Filter 的坑
- 顺序(Order):
GlobalFilter的顺序决定执行先后,鉴权必须在路由转发之前;顺序写错会导致"未鉴权就转发"。 - 阻塞调用:在响应式 Filter 中调用阻塞 SDK(如同步 Redis、JDBC)必须用
Mono.fromCallable(...).subscribeOn(Schedulers.boundedElastic())切换到弹性线程池,否则阻塞 EventLoop。 - 异常传播:用
onErrorResume兜住下游超时,返回统一错误,避免 500 裸奔。
四、限流算法:令牌桶 vs 漏桶
网关限流是稳定性第一道防线。两种经典算法:
| 维度 | 令牌桶(Token Bucket) | 漏桶(Leaky Bucket) |
|---|---|---|
| 流量整形 | 允许突发(桶里有存量就放) | 严格匀速,削平突发 |
| 实现 | Gateway RequestRateLimiter 默认 | 需自定义或 Sentinel |
| 适用 | 大多数 API(兼顾突发与保护) | 需严格限速的下游(如第三方配额) |
Gateway 的 RequestRateLimiter 基于令牌桶(Redis + Lua 原子实现),replenishRate 是令牌生成速率,burstCapacity 是桶容量(即允许的突发量)。基于 Redis 保证了集群维度总限流(多网关实例共享令牌桶),这是比单机限流更正确的做法。
Sentinel 与网关集成:Sentinel 提供 SentinelGatewayFilter + SentinelGatewayBlockExceptionHandler,可在网关层做路由级/参数级流控 + 熔断 + 热点参数限流(如对某 userId 单独限流),并支持集群限流与热点防护,比原生 RequestRateLimiter 能力更强。实践中常二者配合:Gateway 原生限流做入口粗粒度,Sentinel 做细粒度与熔断。
五、灰度发布(金丝雀)在网关实现
灰度本质是"按规则把部分流量导到新版本"。Gateway 支持:
- 权重路由:
Weight=group1, 90+Weight=group1, 10把 10% 流量给 v2。 - Header/参数路由:
Header=X-Canary, true命中即走 v2(用于内部测试/特定用户)。 - 按用户取模:自定义
KeyResolver对 userId 哈希取模,1% 流量进灰度。
网关成为单点与瓶颈
网关承载全量流量,一旦它慢或挂,全线崩溃。必须:独立集群部署(不与业务混部)、配限流 + 全局超时 + 熔断、下游设兜底、自身指标(QPS/错误率/P99 延迟)纳入告警。鉴权应做"统一认证"(校验 JWT)但细粒度授权下沉服务——网关无法知道"用户 A 能否改订单 B"。
六、最佳实践
- 认证在网关统一做(校验 JWT/签名),通过可信请求头(如
X-User-Id,由网关注入且下游信任)传递身份,避免每个服务重复校验。 - 全链路超时与重试在网关收敛(重试只对幂等 GET/特定写开启,避免重试放大故障)。
- 灰度先小流量(1%→10%→100%)验证,配置走 Nacos 动态下发,不写死在代码。
- 防重放:对敏感写接口做请求签名(timestamp+nonce+sign),网关校验防重放攻击。
- 坚持"网关只做横切,业务下沉":聚合类需求(BFF)单独一层,避免胖网关难维护。
本节小结
Spring Cloud Gateway 的底层是 WebFlux 响应式模型——少量线程靠"不阻塞"扛高并发,因此 Filter 链绝不能有阻塞调用。核心模型 Route=ID+URI+Predicate+Filter,Predicate 匹配、Filter 治理。限流优先令牌桶(允许突发)并基于 Redis 做集群总限流;Sentinel 补强熔断与热点防护。灰度通过权重/Header 实现。架构师要记住:网关是统一收口横切逻辑的地方,但它是全量流量的单点,必须独立部署、限流、告警,且细粒度授权要下沉到服务。
📚 官方文档与参考资料
- Spring Cloud(含 Gateway):https://spring.io/projects/spring-cloud
- Spring Cloud Alibaba(Sentinel 网关流控):https://sca.aliyun.com/
- WebFlux 响应式编程参考:https://docs.spring.io/spring-framework/reference/web/webflux.html
- Sentinel 官方文档:https://sentinelguard.io/zh-cn/docs/
✏️ 练习与自测
- 设计题:用 Gateway 配置一个"按 userId 取模 1% 流量到 v2"的灰度路由,写出
Route+ 自定义KeyResolver关键配置/代码,并说明如何在不重启的情况下动态调比例。 - 辨析题:令牌桶与漏桶分别适合什么场景?如果下游是严格配额 100 QPS 的第三方接口,你选哪个?为什么网关限流必须基于 Redis(集群维度)而非本地内存?
- 排查题:线上的 Gateway suddenly CPU 100%、响应全慢。你怀疑是某个 Filter 写了阻塞代码。如何用理论定位是"响应式链路被阻塞"而非"下游慢"?给出排查思路与改造方向。
- 设计题:设计 BFF 层与网关的职责划分图,并说明"商品详情页聚合 5 个服务"这类需求放在哪一层、为什么不放网关。