2. 网关架构基础:核心组件与请求流生命周期

做内部开发者平台,网关是第一道关口。很多团队把网关当成简单的“反向代理+路由”,觉得用 Nginx 就完事了。但说实话,当平台需要支持多团队、多协议、灰度发布、限流熔断这些能力时,这种理解远远不够。

我个人习惯把网关拆成三个层面去看:接入层、转发层、治理层。每一层负责的事情不同,但合在一起就构成了一个完整的请求生命周期。你想想看,一个 HTTP 请求从客户端发起,到后端服务响应,中间经过了多少环节?

2.1 网关的核心组件

先别急着画大图,我们把组件一个个拆开说。

组件职责典型实现
协议适配器接收各种协议(HTTP/HTTPS/gRPC/WebSocket)并统一转换为内部模型Netty、Tomcat、Vert.x
路由引擎根据请求头、路径、参数匹配到目标服务Spring Cloud Gateway、Kong、APISIX
过滤器链按序执行鉴权、限流、日志、重试等横切逻辑责任链模式、插件机制
负载均衡器在多个上游实例中选择目标节点一致性哈希、加权轮询、最小连接数
连接管理器管理上下游连接池、保持长连接、处理半关闭连接池、keep-alive、连接复用
缓存/降级模块缓存静态响应或降级逻辑,防止雪崩本地缓存、Redis、Hystrix
监控与遥测采集请求延迟、错误率、流量指标Prometheus metrics、OpenTelemetry

这些组件里,最容易出问题的是连接管理器。我在项目中遇到过,因为默认连接池太小,双十一流量一上来,网关自己先挂了。所以搭建网关前,一定要把连接池参数(最大连接、等待队列、超时)理解透。

💡 小经验: 协议适配器最好单独做一层,别跟路由混在一起。我见过有人把所有逻辑写在一个 Filter 里,结果后面想升级 gRPC 支持,重构了整整两周。

2.2 请求流生命周期

一个请求从进网关到出网关,大致走 9 个步骤。我给你画了张图,先看总体流程。

网关请求流生命周期 网关请求流生命周期 ① 协议解析 ② 路由匹配 ③ 前置过滤 ④ 负载均衡 ⑤ 上游请求 ⑥ 上游处理 ⑦ 后置过滤 ⑧ 响应缓存 ⑨ 响应发送 & 遥测上报 说明: • 前置过滤器链执行顺序:鉴权→限流→参数校验→请求转发 • 后置过滤器链执行顺序:响应转换→日志记录→错误包装→响应发送

图里画了 9 个阶段,但实际生产中你会看到更细的切分。比如在我的团队,我们会在前置过滤里做「鉴权、限流、黑白名单、请求头改写」;后置过滤里做「响应压缩、错误码统一、跨域头注入」。

请求流的核心原则: 前置过滤只处理请求相关的逻辑,后置过滤只处理响应相关的逻辑。别把响应逻辑放前置,也别在前置里修改上游返回的数据。我见过有人在前置 filter 里直接读 upstream 的响应流……导致后置 filter 读不到数据,排查了半天。

2.3 各阶段的避坑指南

  • 协议解析阶段: 注意 HTTP/2 的流控制。我曾经遇到过 HTTP/2 的 SETTINGS_MAX_CONCURRENT_STREAMS 设置太小,导致并发请求被主动拒绝。
  • 路由匹配时: 路径匹配要区分精确匹配与前缀匹配。我建议优先使用精确路径,避免 /api/{version}/users/* 这种模糊匹配导致路径穿越漏洞。
  • 前置过滤: 鉴权失败后务必立即返回 401/403,不要再继续往下走。曾经有团队在鉴权失败后还继续执行限流,浪费了 。
  • 负载均衡: 对于需要会话保持的场景,别用轮询,用一致性哈希。不然用户刚登上去,下一个请求就跳到别的实例了。
  • 上游请求阶段: 设置合理的超时时间(连接超时、读超时、写超时)。我在一个项目中用过 30s 的读超时,结果有个慢 SQL 把整个网关线程池拖垮了。
  • 后置过滤: 不要在后置 filter 里做阻塞操作。比如写日志,可以异步写或扔消息队列。
  • 响应缓存: 只缓存 GET 请求的幂等响应,千万别缓存 POST 或动态数据。

⚠️ 一个血的教训: 我曾经接手一个项目,网关在上游请求阶段做了一次 DNS 解析缓存,而且缓存时间设成了 5 分钟。结果上游服务发生故障扩容,新 IP 迟迟无法被识别,导致部分请求一直打到已下线的老机器上。从那以后我养成了习惯——网关层尽量少做 DNS 缓存,实在要做,TTL 不要超过 30 秒。

2.4 谈谈请求上下文的传递

你想想看,一个请求进来之后,鉴权通过后获取的用户信息、原始请求 IP、traceId 这些数据,怎么让后续的 filter 和上游服务都能拿到?

我建议用请求上下文(Request Context) 对象,在接口层创建,贯穿整个生命周期。Spring Cloud Gateway 里的 ServerWebExchange 就是一个典型的上下文容器。你可以向它里边塞 attribute,然后在各个 filter 里读取。

// 伪代码示例:在鉴权 filter 中设置用户信息
public class AuthFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 伪代码:解析 
        String userId = parseToken(exchange.getRequest());
        exchange.getAttributes().put("userId", userId);
        return chain.filter(exchange);
    }
}

// 在路由转发时,将 userId 放入请求头传给上游
public class ForwardFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 从上下文取出 userId
        String userId = exchange.getAttribute("userId");
        ServerHttpRequest modifiedRequest = exchange.getRequest().mutate()
                .header("X-User-Id", userId)
                .build();
        ServerWebExchange modifiedExchange = exchange.mutate().request(modifiedRequest).build();
        return chain.filter(modifiedExchange);
    }
}

这样上游服务只需要读取请求头 X-User-Id 就能拿到用户身份,不用再自己解析 了。我个人特别推崇这种「网关做一次鉴权,下游只管用」的模式。

2.5 连接管理与线程模型

网关的性能瓶颈往往不在 CPU,而在连接数和线程切换。你可以做个简单测试:用 wrk 并发 1000 打一个 NIO 网关和一个 BIO 网关,看谁先撑不住。

现代网关通常用 Reactor 模型(如 Netty)。事件循环线程(EventLoop)不能阻塞,一旦阻塞整个网关吞吐量急剧下降。我在优化一个网关时发现,某个 filter 里调了一个同步的 Redis 查询,导致 EventLoop 被占住,QPS 从 5 万跌到了 3000。

常见线程模型对比
模型优点缺点使用场景
BIO (每个连接一个线程)编程简单线程开销大,连接数受限内部管理工具,并发低
NIO (Reactor)高并发,少线程编程复杂,须避免阻塞生产级网关(APISIX、Kong、Spring Cloud Gateway)
协程 (Kotlin Coroutine / Loom)极轻量,易编程生态不够成熟实验性项目,未来趋势

说白了,如果你是 Java 技术栈,推荐用 NIO 模型(Spring Cloud Gateway 底层就是 Reactor Netty)。但一定要记住:不要在 filter 里做任何同步阻塞操作。实在避免不了,那就把阻塞操作扔到单独的线程池里,用 subscribeOn(Schedulers.boundedElastic()) 这种办法隔离。

一个常用技巧: 网关层做日志时,千万别写同步的 log4j 追加器。用异步 Appender 或者直接上报到消息队列,这样写入磁盘的时间不会阻塞请求通路。

2.6 监控与可观测性

网关是流量入口,出了问题影响面是全平台的。所以我建议从第一天就把可观测性做进去,不要等出事故再补。

  • 延迟拆解: 监控每个阶段的耗时(协议解析、路由、鉴权、上游请求、响应处理)。方便定位瓶颈。
  • 错误分类: 区分 4xx 业务错误与 5xx 系统错误。超过 5% 的 5xx 就应该告警。
  • 全链路追踪: 在网关层生成 traceId,放入请求头传给下游。这样从客户端到服务端到数据库,整个链路都能串起来。

我见过一个团队,网关没有做任何监控,直到用户投诉“页面打不开了”才发现网关 OOM 了。其实提前加几个指标就能预警,比如连接数、线程阻塞数、GC 频率。

好,关于网关架构基础的核心组件和请求流生命周期,就先聊到这儿。这些底层知识是后续讲网关扩展(插件开发、自定义 filter、多协议支持)的基石。理解透了,你就能在内部开发者平台上搭建一个既稳定又好扩展的网关层。