喜马拉雅百亿次日调用Netty网关架构演进路线
喜马拉雅用户超过 6 亿,Web 服务超过 500 个,网关每天处理超过 200 亿次调用,单机 QPS 峰值可达 4 万以上。网关从 Tomcat NIO + Async Servlet 演进到 Netty + 全异步,核心是解决代理场景中的阻塞、对象池、连接池和超时管理问题。
一、第一版:Tomcat NIO + Async Servlet
第一版网关使用 Tomcat NIO 和 Async Servlet,并实现了一个单独的 Push 层与后端服务通信。功能上已经支持黑白名单、流量控制、身份验证和 API 发布。
但 Tomcat 作为接入端很快成为瓶颈:
- 缓存过多,对象池在内存有限、流量增大时容易触发 GC;
- 堆内存与 Netty 后端堆外内存之间存在多次 Copy;
- Tomcat 的 NIO 读 Body 是阻塞的;
- 默认缓存大量
requestProcessor,每个都关联 Request; - Servlet 3.0 异步实现可能导致内存泄漏。
当单机 QPS 达到 5000 时,Full GC 频繁发生。
二、第二版:Netty + 全异步
Netty 版本构建了全异步、无锁、分层的架构:
接入层
Netty IO 线程负责 HTTP 编解码和协议异常监控。相比 Tomcat 将请求行和请求头一起限制 8K,Netty 可以分别限制大小。请求超过阈值时,优化后只取正常大小部分并标记解析失败,业务层可以进一步判断问题来源。
业务逻辑层
业务逻辑层采用责任链模式,不进行 IO 操作。支持的流量治理能力包括:
- 用户认证和登录验证;
- 黑白名单;
- 令牌桶限流;
- 智能熔断;
- 灰度发布和慢启动预热;
- 统一降级;
- 流量调度;
- 流量 Copy;
- 请求日志采样。
规则元数据在网关启动时全部初始化,运行时规则变化通过独立线程刷新,避免 IO 操作影响业务线程。
服务调用层
服务调用层必须异步。每个请求创建上下文,发送请求后把 Context 绑定到连接上,收到响应后再取出 Context 写回客户端。
HTTP 连接是独占的,释放时必须等响应完成。需要关闭连接的场景包括Connection: close、空闲超时、读超时、写超时、FIN、Reset。
后端 Tomcat 默认连接重用 100 次,达到限制后会返回Connection: close,因此长连接场景需要主动移除 close 头。
三、全链路超时机制
超时不能只在响应阶段计算,而要覆盖整个链路:
写超时的计时点很重要。如果从writeAndFlush开始计时,会包含 HTTP 编码和队列发送时间,对后端不公平。因此应在 flush 成功回调后启动超时任务。大 POST 请求可能分多次 flush,若回调迟迟不完成,写超时可以及时关闭连接。
四、监控报警
网关监控分为协议层和应用层:
- 协议层:攻击性请求、Line/Head/Body 过大、只发头不发 Body;
- 应用层:慢请求、超时、TP99/TP999、OPS、带宽、响应码、连接状态、失败请求、流量抖动。
监控数据定时上传到管理系统,汇总后存储到 InfluxDB,实现秒级报警。
五、性能优化
对象池
复用线程池任务、StringBuffer 等频繁创建的对象,降低内存分配和 GC 压力。
上下文切换
网关业务逻辑处理采用 Netty IO 编解码线程异步方式,一方面防止业务代码阻塞,另一方面在日志激增等突发情况下允许使用 Netty IO 线程作为替代,减少上下文切换。
GC 优化
采用对象池和堆外内存,尽量让对象在年轻代回收。但监控发现老年代仍缓慢增长。原因在于每个后端连接创建的 socket 中,AbstractPlainSocketImpl重写了finalize():
/** * Cleans up if the user forgets to close it. */protectedvoidfinalize()throwsIOException{close();}finalize由优先级不高的 Finalizer 线程处理,对象需要等待 finalize 完成并在下次 GC 时才能回收,因此进入老年代。这说明主动、确定性地关闭连接比依赖 finalize 更重要。
日志
Log4j ConsoleAppender 默认immediateFlush=true,每次同步写盘。AsyncAppender 的 buffer 默认 128 且会阻塞,当 Dispatcher 刷盘变慢时可能阻塞 Netty IO 线程。因此高并发场景必须精简日志。
六、未来规划
HTTP/2 在连接层实现多路复用,一个连接可以发送多个请求,解决 HTTP/1 连接无法复用导致的重复建连和慢启动开销。喜马拉雅正基于 Netty 升级 HTTP/2,同时继续完善监控报警和统一降级能力。
七、总结
喜马拉雅网关的核心经验是:代理网关最不能接受的是阻塞。第一版证明了 Tomcat 异步 Servlet 在功能上可行但性能上受限,第二版通过 Netty 全异步、无锁分层、连接池、超时管理和 GC 优化解决了高吞吐问题。监控和日志也不是辅助功能,而是高并发网关能否稳定运行的一部分。