后端

Spring Cloud Gateway路由网关的请求接收与跳转机制详解

TRAE AI 编程助手

01|Spring Cloud Gateway:请求大门的「智能导航员」

在微服务时代,每一次 API 调用都像快递小哥找门牌号——地址对了,还得看门禁、称重、安检。Spring Cloud Gateway 就是那位 7×24 小时值班的「智能导航员」:先认路、再安检、最后把包裹精准投递到后台小哥手里。今天我们把它的"认路—安检—投递"全过程拆开揉碎,让你一次看懂。


02|架构鸟瞰:三大件 + 一条链

Gateway 的核心只有三张王牌:

组件职责类比
Route(路由)定义「哪个地址→哪个服务」门牌号映射表
Predicate(断言)判断「这条请求是否归我管」门禁人脸识别
Filter(过滤器)对请求/响应做「加工」安检 X 光机

三大件被串成一条 Filter Chain,请求进来后按序执行,最后把 ServerHttpRequest 变成 ServerHttpResponse 原路返回。整个流程异步、非阻塞,基于 WebFlux + Netty,天生给高并发场景开绿灯。


03|请求接收:Netty 如何先把第一道关

  1. Netty 监听端口
    Gateway 启动时自动注册 NettyReactiveWebServerFactory,默认监听 8080(可改 server.port)。

  2. Reactor 线程模型
    BossGroup 接收 TCP 连接 → WorkerGroup 进行 IO 读写 → 事件派发到 WebFlux 的 DispatcherHandler

  3. 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 完成代理:

  1. ServerHttpRequest 转成 Netty 的 FullHttpRequest
  2. 通过 LoadBalancerClient 解析 lb://service-name 拿到真实 IP 列表;
  3. 使用 Netty HttpClient 发送请求,支持 HTTP/1.1 与 HTTP/2;
  4. 收到响应后再封装成 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, 80

7.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%

  1. WebFlux 线程池
    默认 reactor.netty.ioWorkerCount = CPU 核数 × 2;若容器核数被 cgroup 限制,显式设置 -Dreactor.netty.ioWorkerCount=8

  2. 连接池复用
    全局配置:

    spring:
      cloud:
        gateway:
          httpclient:
            pool:
              maxConnections: 512        # 全局最大连接
              acquireTimeout: 3s
            compression: true              # 开启 gzip
  3. 禁用不必要的编解码器
    如果只做反向代理,不需要视图解析,把 spring.main.web-application-type=reactive 并排除 spring-boot-starter-thymeleaf

  4. 响应缓存
    对读多写少接口,用 LocalResponseCache 过滤器把 200 响应按 URI 缓存 5s,可降 50% QPS 到下游。

  5. Netty 零拷贝
    文件上传场景开启 file-region=true,大文件直接走 DefaultFileRegion,避免堆内拷贝。


09|常见翻车现场 & 排查清单

症状可能原因定位工具
504 Timeout下游慢 / 连接池耗尽logging.level.org.springframework.cloud.gateway=DEBUG
404 但服务正常Predicate 顺序 / StripPrefix 多截curl -v + /actuator/gateway/routes
限流失效KeyResolver 拿到的是代理 IPX-Forwarded-For 里取第一个非私有地址
高 CPU线程池打满 + 大量 SSL 握手jstackreactor-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 辅助生成,仅供参考)