Skip to content

服务网关 ​

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(路由级)。
yaml
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 实现。架构师要记住:网关是统一收口横切逻辑的地方,但它是全量流量的单点,必须独立部署、限流、告警,且细粒度授权要下沉到服务。

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. 设计题:用 Gateway 配置一个"按 userId 取模 1% 流量到 v2"的灰度路由,写出 Route + 自定义 KeyResolver 关键配置/代码,并说明如何在不重启的情况下动态调比例。
  2. 辨析题:令牌桶与漏桶分别适合什么场景?如果下游是严格配额 100 QPS 的第三方接口,你选哪个?为什么网关限流必须基于 Redis(集群维度)而非本地内存?
  3. 排查题:线上的 Gateway suddenly CPU 100%、响应全慢。你怀疑是某个 Filter 写了阻塞代码。如何用理论定位是"响应式链路被阻塞"而非"下游慢"?给出排查思路与改造方向。
  4. 设计题:设计 BFF 层与网关的职责划分图,并说明"商品详情页聚合 5 个服务"这类需求放在哪一层、为什么不放网关。