Gateway系统第二部曲

开头咱们先说说题目,是不是第二部,套用沈腾的一句经典台词:都这时候了,你还较那三部二部的真,有意义吗?😂😂🤣 从某个方面这题目也还行,就暂且这样吧。

  • 网关在架构中扮演着"流量枢纽"与"治理中枢"的角色,其核心本质是反向代理层协议转换器
  • 也可以说网关本质上是在协调"客户端到网关"和"网关到下游"两段连接

一、 深层次原理:网关究竟在做什么?

在 OSI 七层模型中,传统的路由器(Router)只工作在 L3(网络层) ,它只关心 IP 地址,只要 IP 对了,就直接把数据包扔过去。而网关(Gateway)工作在 L7(应用层),它不仅要看 IP,还要"拆开信封看里面的信",然后再"换个信封重新寄出去"。

复制代码
[客户端] 
   │ 
   │ 1. 发送 HTTP 请求 (TCP/IP 数据包)
   ▼
[网关 Gateway] (L7 应用层)
   ├─ 2. 拆包 (Unpack):剥离 TCP/IP 头部,提取 HTTP Payload (如 JSON)
   ├─ 3. 策略引擎 (Policy Engine):
   │     ├─ 鉴权:解析 JSON 中的 JWT Token 是否合法
   │     ├─ 限流:基于 Redis 令牌桶判断是否放行
   │     └─ 路由:根据 URL 决定转发给哪个后端服务
   ├─ 4. 状态转换与重写 (Rewrite):
   │     ├─ 添加 Header:塞入 `X-User-ID: 10086`
   │     └─ 路径重写:`/api/v1/user` 改为 `/user`
   └─ 5. 重新打包与转发 (Pack & Forward):
         ├─ 建立与后端服务的 TCP 连接(复用连接池)
         └─ 发送新的 HTTP 请求
   ▼
[后端微服务]
  1. 全层感知与协议解析:网关接收数据包后,不仅剥离网络层头部,还会深入应用层(如 HTTP、gRPC、MQTT)解析 Payload。
  2. 策略引擎(Policy Engine):基于解析出的内容,执行鉴权(JWT 校验)、限流(令牌桶/漏桶算法)、熔断降级等逻辑。
  3. 状态转换与重写:修改请求头(Header)、路径重写(Path Rewriting),甚至进行跨协议转换(如 HTTP 转 gRPC)。
  4. 高性能转发与连接池:将处理后的请求通过内部连接池路由至后端微服务,并处理响应缓冲与编码解码。

二、 源码级剖析:以 Spring Cloud Gateway 为例

在主流 Java 微服务架构中,Spring Cloud Gateway 是典型代表。它的核心机制依赖于 WebFlux + Netty 的非阻塞 I/O 模型。

1. 核心组件架构:

  • DispatcherHandler 请求分发器:网关的核心入口类似于 Spring MVC 的 DispatcherServlet,所有 HTTP 请求进入网关后的第一站,负责将请求分发给具体的 MappingHandlerMapping
    *

    复制代码
      public Mono<Void> handle(ServerWebExchange exchange) {
          // 1. 遍历所有的 HandlerMapping,寻找能处理当前请求的 Handler
          return Flux.fromIterable(this.handlerMappings)
              .concatMap(mapping -> mapping.getHandler(exchange)) // 遍历mapping调用getHandler异步获取
              .next() // 只要找到第一个匹配的 Handler 就停止遍历
              .switchIfEmpty(createNotFoundError()) // 找不到则返回 404
              // 2. 找到后,异步调用对应的 Handler 处理业务逻辑
              .flatMap(handler -> invokeHandler(exchange, handler))
              // 3. 异步,处理 Handler 返回的结果
              .flatMap(result -> handleResult(exchange, result));
      }
    • Flux:异步响应流、异步序列完成时发出完成/错误信号;与list不同他是惰性且非阻塞的
      • 数据可以源源不断的来,订阅者处理完一个再要下一个
      • 并且根据上下游自动调整数据流的传输速率(借助队列而不是阻塞)(这就是大佬们说的backpressure背压机制了)
  • RouteLocator :路由定位器,负责根据配置(各种数据源如 YAML 配置文件、Nacos 动态配置)获取路由定义,解析出路由规则(Predicates 和 Filters)转换为可执行route对象
    *

    复制代码
      protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
          return this.routeLocator.getRoutes() // 1. 获取所有路由(返回响应式流 Flux<Route>)
              .concatMap(route -> Mono.just(route)
                  // 2. 核心:使用 AsyncPredicate 异步断言进行匹配
                  //简单来说:咱们配置文件断言指定了请求的url、method、header,命中则匹配上
                  .filterWhen(r -> r.getPredicate().apply(exchange)) 
              )
              .next() // 3. 取第一个匹配成功的路由
              .map(route -> {
                  validateRoute(route, exchange); // 校验路由合法性
                  return route;
              });
      }
    • Flux<Route>包含多个路由对象的异步数据流:系统动态加载(大量)路由,请求发起,框架逐步匹配,当匹配上某条件后由下游handler处理。
    • SO RouteLocator支持动态响应式流(Flux): 当在 Nacos 中修改了路由配置,RouteLocator 会感知到变化并刷新内存中的路由表。匹配过程使用的是 AsyncPredicate,不会阻塞 Netty 的 I/O 线程。
  • FilteringWebHandler :核心处理器,负责构建过滤器链(Filter Chain)。

    • DispatcherHandler 拿到了 FilteringWebHandler 后,真正的业务逻辑(鉴权、限流、路由转发)才开始执行。FilteringWebHandler 的核心职责是合并过滤器并构建责任链
    复制代码
      public Mono<Void> handle(ServerWebExchange exchange) {
          // 1. 从 Exchange 中获取刚才匹配到的路由信息
          Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
          
          // 2. 获取该路由下配置的局部过滤器
          List<GatewayFilter> gatewayFilters = route.getFilters();
          
          // 3. 【核心】合并全局过滤器(GlobalFilter)和局部过滤器
          List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
          combined.addAll(gatewayFilters);
          
          // 4. 按照 @Order 注解或 Ordered 接口进行排序
          AnnotationAwareOrderComparator.sort(combined);
          
          // 5. 创建过滤器链并触发执行:依次调用每个 Filter 的 filter(exchange, chain) 
          return new DefaultGatewayFilterChain(combined).filter(exchange);
      }
    • NettyRoutingFilter是背后真正的大佬:网关执行请求转发的核心组件:Reactor Netty 的非阻塞 I/O 模型
      *

      复制代码
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 1. 获取目标 URI
            URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
            // 2. 获取 Reactor Netty 的 HttpClient
            HttpClient httpClient = getHttpClient();
            // 3. 异步发起下游请求
            Mono<HttpClientResponse> responseMono = httpClient
                .request(HttpMethod.valueOf(exchange.getRequest().getMethodValue()))
                .uri(requestUrl)
                .send((req, nettyOutbound) -> nettyOutbound.send(exchange.getRequest().getBody()))
                .response();
            // 4. 响应返回后,继续执行后置过滤器链
            return responseMono.then(chain.filter(exchange));
        }

      内部使用 Reactor Netty 提供的 ConnectionProvider(连接池),所以网关与下游微服务之间维持长连接(Keep-Alive),避免了高并发下频繁进行 TCP 三次握手和四次挥手的巨大开销。

    • 如果链中某个filter抛出异常,mono.defer捕获异常将其转成Mono.error(ex)传播,中断后续过滤器的执行

    复制代码
      /**
      * 全局兜底异常处理
      * filter异常无法被@controllerAdvice捕获
      * 所以提供了WebExceptionHandler机制全局兜底
      */
      @Component
      @Order(-1) // 高优先级异常处理器
      public class GatewayExceptionHandler implements WebExceptionHandler {
          @Override
          public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
              // 1. 记录异常日志
              logger.error("Gateway exception occurred", ex);
              // 2. 根据异常类型映射 HTTP 状态码
              if (ex instanceof UnauthorizedException) {
                  exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
              } else {
                  exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
              }
              // 3. 设置响应完成,返回给客户端
              return exchange.getResponse().setComplete();
          }
      }

所以一次请求的底层串联我们就可以大致看到:

  1. 客户端发起请求,Netty 的 Worker 线程接收数据。
  2. 数据交给 DispatcherHandler,它遍历寻找匹配的 Handler。
  3. RoutePredicateHandlerMapping 调用 RouteLocator ,通过异步断言找到匹配的路由,并返回 FilteringWebHandler
  4. DispatcherHandler 调用 FilteringWebHandler 的 handle 方法。
  5. FilteringWebHandler 组装全局和局部过滤器,构建 DefaultGatewayFilterChain ,依次执行鉴权、限流等逻辑,最后由 NettyRoutingFilter 将请求转发给下游微服务。

2. 过滤器链(Filter Chain)执行原理:

网关的核心扩展能力来自过滤器。源码中通过 GlobalFilter(全局)和 GatewayFilter(局部)实现。

复制代码
@Component
public class ArchitectureGatewayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 【L7 内容感知 1】:提取应用层数据(如 Header)
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        
        // 【策略引擎】:鉴权逻辑
        if (StringUtils.isBlank(token) || !JwtUtil.isValid(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete(); // 直接拦截,不再往下走
        }

        // 【L7 内容感知 2】:状态转换与重写
        // 1. 路径重写
        ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
                .path("/user-service" + exchange.getRequest().getPath())
                // 2. Header 重写:把解析出的 UserID 塞进 Header,透传给下游
                .header("X-User-ID", JwtUtil.getUserId(token)) 
                .build();

        // 将修改后的请求传递给下一个过滤器或后端服务
        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }

    @Override
    public int getOrder() {
        return -100; // 优先级最高
    }
}
  • 网关不能像传统 Tomcat 那样"一个请求一个线程",否则高并发下线程会瞬间耗尽。
  • SCG 底层使用 NettyRoutingFilter 进行转发。它通过 Netty 的 Reactor 线程模型 ,使用极少的线程处理成千上万的连接。
    • 事件驱动与非阻塞
    • BossGroup(主线程组):只负责一件事------监听和接收客户端的 TCP 连接(Accept 事件),接收完后把连接丢给 WorkerGroup
    • WorkerGroup(工作线程组):负责处理连接上的 I/O 读写事件(Read/Write)。
    • 业务处理:Worker 线程通过 Selector 监听事件,一旦有数据可读,就分发给对应的 Handler 处理。如业务逻辑耗时会丢给独立的业务线程池,处理完再交回 Worker 线程发送响应。
  • 同时,网关内部维护了一个连接池,与后端服务保持长连接(Keep-Alive),避免了每次转发都进行 TCP 三次握手的巨大开销。就这样双管齐下~

3. 高性能限流原理:

网关通常结合 Redis 实现分布式限流。底层采用 令牌桶算法(Token Bucket) 。例如配置 replenishRate: 10(每秒生成10个令牌),burstCapacity: 20(最大突发20)。网关在转发前通过 Lua 脚本原子性地扣减 Redis 中的令牌,若失败则直接返回 429 状态码,避免后端被打垮。这个咱们刚写了令牌和漏斗,这就不多说了;

三、 同类网关对比与选型

维度 Spring Cloud Gateway Kong Nginx / OpenResty
底层语言 Java (WebFlux/Netty) Go / Lua C / Lua
生态契合度 Spring Cloud / Alibaba 生态无缝集成 云原生、多语言、跨平台 传统微服务、静态资源代理
性能表现 优秀(单机数万QPS) 极高(超高性能,支持多语言插件) 极致性能(百万级并发)
动态能力 支持配置中心(如Nacos)热更新 强大的 Admin API,动态路由极强 需借助 OpenResty 或外部模块
适用场景 Java 微服务生态,需要复杂业务逻辑(如动态鉴权、灰度) 超大规模集群、跨语言微服务、K8s 环境,需要丰富的插件生态 边缘计算、极致性能要求、极高并发场景、静态资源、边缘节点、传统架构

四、 落地:脚着地啦

  1. 统一安全与鉴权:将 JWT 校验、OAuth2、IP 黑名单等逻辑全部收敛在网关层。后端微服务只需关注业务,无需重复造轮子。
  2. 灰度发布与 A/B 测试 :通过网关解析请求头(如 X-User-Tag: beta),将特定流量路由到新版本的服务集群,实现无感发布。
  3. 边缘计算与协议转换:在工业物联网场景中,边缘网关负责将 Modbus、OPC UA 等异构协议转换为 MQTT/HTTP,并在本地进行数据脱敏与 AI 推理,降低云端带宽消耗。
  4. 统一异常与日志监控:在网关层统一拦截异常,返回标准化的 JSON 错误体;同时记录全链路 TraceID,对接 Prometheus+Grafana 实现可观测性。
实例

1、动态路由配置:看着简单底层做了一些事情,上面咱们也讲到了(配合注册中心如nacos)

复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: order-service # 订单服务路由
          uri: lb://order-service # lb:// 前缀表示开启负载均衡
          predicates:#这就是咱们上面说的断言,RouteLocator
            - Path=/api/order/** # 匹配 /api/order/ 开头的请求
          filters:
            - StripPrefix=1       # 转发时去掉第一层路径 (/api/order/create -> /order/create)
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1000   # 每秒生成*个令牌,写到配置中心
                redis-rate-limiter.burstCapacity: 2000   # 最大允许突发*个请求,写到配置中心
                key-resolver: "#{@userIdKeyResolver}"  # 按用户ID或IP进行限流
  • 为防止恶意爬虫或 DDoS 攻击打垮后端,网关通常会结合 Redis 实现基于"令牌桶算法"的分布式限流。老朋友了
    *

    复制代码
      #以按 IP 限流为例:
      @Bean
      public KeyResolver ipKeyResolver() {
          return exchange -> Mono.just(
              exchange.getRequest().getRemoteAddress().getHostString()
          );
      }
    
      #以按用户 ID 限流为例,自定义也可以结合起来,自定义嘛
      @Bean
      public KeyResolver userKeyResolver() {
          return exchange -> Mono.just(
              exchange.getRequest().getQueryParams().getFirst("userId")
          );
      }
  • 当某个用户或 IP 的请求频率超过设定的阈值时,网关会直接返回 429 Too Many Requests 状态码,从而保护下游的核心业务服务不被流量洪峰压垮。

2、自定义的全局过滤器,会被**FilteringWebHandler** 处理,加载到链中,很标准的过滤器

复制代码
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        // 1. 放行公开接口(如登录、健康检查)
        if (path.startsWith("/api/public/") || path.equals("/actuator/health")) {
            return chain.filter(exchange);
        }
        
        // 2. 拦截受保护接口,校验 Token
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !isValidToken(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete(); // 鉴权失败,直接返回 401
        }
        
        // 3. 鉴权通过,继续往下走
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -100; // 设置高优先级,确保鉴权最先执行
    }
}

五、 "避坑指南"(反例子)

当前离开业务一切都是空谈,如果你量到位那各种策略也是要到位的,如果咱没什么并发也不需要整这么费劲,业务与数据为王。

  1. ❌ 在网关中编写复杂业务逻辑 :咱就是薄薄的一层
    • 反面案例 :在网关的 Filter 中写数据库查询、复杂的订单组装逻辑。这会导致网关吞吐量断崖式下跌,原本单机几万 QPS 的网关瞬间被打满。
      • 网关基于 WebFlux 响应式非阻塞模型,核心优势是高并发下的低资源消耗。如在 Filter 中执行同步的数据库查询、RPC 调用或复杂的 JSON 解析,会直接阻塞 Reactor 的事件循环线程(EventLoop)。
    • 正解 :网关是"守门人"不是"业务处理机"。它只负责路由、代理、鉴权和限流(如 JWT 验签、路径重写、IP 黑白名单)。业务逻辑必须下沉到具体的微服务中。
      • 如果必须在网关层做少量复杂逻辑(如动态查库获取白名单),必须使用响应式客户端(如Redisson),并通过 .subscribeOn(Schedulers.boundedElastic()) 将阻塞操作隔离到独立线程池,绝不能污染 Netty 的 IO 线程。
  2. ❌ 忽略熔断与超时配置
    • 反面案例 :后端服务响应慢,网关一直傻等,网关的 HTTP 客户端连接池(默认 500)会被迅速耗尽,后续所有请求都会卡在 pendingAcquireTimeout 上排队,导致整个网关瘫痪,引发全局雪崩。
    • 正解: 必须在网关层配置全局或路由级的 response-timeout (如 3s),绝不能无限等待。必须配置熔断降低策略:集成 Resilience4j 或 Sentinel ,配置基于慢调用比例 (如 P99 > 2s 且占比超 50%)或异常比例 (如 5xx 超 30%)的熔断规则。必须针对不同核心域的服务(如订单、支付)配置独立的线程池或信号量,防止某个非核心服务拖垮全局连接池。
  3. ❌ 路由规则划分不合理
    • 反面案例:一个网关实例代理了上百个微服务,单点故障风险极高。
    • 正解 :按业务域(BFF 模式)拆分网关。例如分为"用户网关"、"交易网关",避免单网关成为性能瓶颈。核心网关与非核心网关必须独立部署,甚至使用不同的集群,做到真正的故障域隔离。因材施教、多少年的经典
  4. ❌ 忽视安全防护
    • 反面案例:网关未配置 CORS(跨域),或未对敏感接口做防重放、防注入处理,导致被恶意刷接口。或者未限制请求体大小,被攻击者利用超大 Payload 耗尽网关内存。
    • 正解 :在网关层统一配置安全防护策略,拦截非法请求。
      • 如果网关已配置 globalcors,后端微服务必须关闭 自身的跨域配置,否则浏览器会因收到两个 Access-Control-Allow-Origin 头而直接拦截请求。
      • 必须配置 spring.cloud.gateway.httpclient.max-request-size(如 50MB),从网络层直接拒绝超大请求,防止恶意 POST 请求打满网关内存。
      • 在全局 Filter 中统一移除或覆盖敏感 Header(如 X-Forwarded-For、内部服务间调用的 Auth-Token),防止客户端伪造内部调用凭证。

总结

网关的设计哲学是**"极简与防御"**。它不是万能的,任何超出"路由、鉴权、限流、熔断"范畴的逻辑,都是对网关高并发特性的破坏。在评审网关代码时,我们要像审查"核心中间件"一样严苛,关注其底层的线程模型、连接池状态和故障隔离能力。