喜马拉雅自研网关技术实践
《喜马拉雅自研网关技术实践》共 0.5 万字,通篇围绕 监控、架构、并发、缓存 展开,归在「后端与系统架构」这一层。
适合给 AI 系统搭底座、要管得住的人
目录骨架(8 节)
- 业务背景
- 架构1.0 Tomcat nio + AsyncServlet
- 架构2.0 Netty + 全异步
- 全链路超时机制
- 监控报警
- 总体架构
- 性能优化实践
- 架构3.0 Netty + Http2(未来规划)
分享内容
- 业务背景
- 架构1.0 之 Tomcat nio + AsyncServlet
- 架构2.0 之 Netty + 全异步
- 架构3.0 之 Netty + Http2
- 总结
业务背景
网关是一个比较成熟了的产品,基本上各大互联网公司都会有网关这个中间件,来解决一些公有业务的上浮,而且能快速的更新迭代,如果没有网关,要更新一个公有特性,就要推动所有业务方都更新和发布,那是效率极低的事,有网关后,这一切都变得不是问题,喜马拉雅也是一样,用户数增长达到 6 亿多的级别,Web 服务个数达到 600+,基本都接入网关接入,日处理 200 亿加次调用,单机 QPS 高峰达到 4w+。
网关除了要实现最基本的功能反向代理外,还有公有特性,比如黑白名单,流控,鉴权,熔断,API 发布,监控和报警等,我们还根据业务方的需求实现了流量调度,流量 Copy,预发布,智能化升降级,流量预热等相关功能,下面就我们网关在这些方便的一些实践经验以及发展历程,下面是喜马拉雅网关的演化过程:

架构1.0 Tomcat nio + AsyncServlet
网关在架构设计时最为关键点,就是网关在接收到请求,调用后端服务时不能阻塞 Block,否则网关的吞吐量很难上去,因为最耗时的就是调用后端服务这个远程调用过程,如果这里是阻塞的,那你的 tomcat 的工作线程都 block 主了,在等待后端服务响应的过程中,不能去处理其他的请求, 这个地方一定要异步
架构图如下:

这版我们实现单独的 Push 层,作为网关收到响应后,响应客户端时,通过这层实现,和后端服务的通信是 HttpNioClient,对业务的支持黑白名单,流控,鉴权,API 发布等功能,这版只是功能上达到网关的要求,但是处理能力很快就成了瓶颈,单机 qps 到 5k 的时候,就会不停的 full gc,后面通过 dump 线上的堆分析,发现全是 tomcat 缓存了很多 http 的请求,因为 tomcat 默认会缓存 200 个 requestProcessor,每个 prcessor 都关联了一个 request,还有就是 servlet3.0 tomcat 的异步实现会出现内存泄漏,后面通过减少这个配置,效果明显。但性能肯定就下降了,总结了下,基于 tomcat 做为接入端,有如下几个问题:
Tomcat 自身的问题
- 缓存太多,tomcat 用了很多对象池技术,特别是一个 processorCache 默认 200,在并发回收时会存在没有把 jmx 里的对象删除,导致内存泄露,流量一高很容易触发 gc。
- 内存 copy,tomcat 的默认是用堆内存 processorCache 默认 200,以数据需要读到堆内,而我们后端服务是 netty,有堆外内存,需要通过数次 copy。
- tomcat 还有个问题是读 body 是阻塞的, tomcat 的 nio 模型和 reactor 模型不一样,读 body 是 block 的。
HttpNioClient 的问题
- 获取和释放连接都需要加锁,对应网关这样的代理服务场景,会频繁的建连和关闭连接,势必会影响性能。
基于 tomcat 的存在的这些问题,我们后面对接入端做改造,用 Netty 做接入层和服务调用层,也就是我们的第二版,能彻底解决上面的问题,达到理想的性能。
架构2.0 Netty + 全异步
基于 Netty 的优势,我们实现了全异步,无锁,分层的架构
先看下我们基于 Netty 做接入端的架构图

接入层
Netty 的 io 线程,负责 http 协议的编解码工作,同时对协议层面的异常做监控报警
对 http 协议的编解码做了优化,对异常,攻击性请求监控可视化,比如我们对 http 的请求行和请求头大小是有限制的,tomcat 是请求行和请求加在一起,不超过 8k,netty 是分别有大小限制,假如客户端发送了超过阀值的请求,带 cookie 的请求很容易超过,正常情况下,netty 就直接响应 400 给客户端,经过改造后,我们只取正常大小的部分,同时标记协议解析失败,到业务层后,就可以判断出是那个服务出现这类问题,其他的一些攻击性的请求,比如只发请求头,不发 body / 或者发部分这些都需要监控和报警。
业务逻辑层
负责对 API 路由,流量调度等一序列的支持业务的公有逻辑,都在这层实现,采样责任连模式,这层不会有 io 操作。
在业界和一些大厂的网关设计中,业务逻辑层基本都是设计成责任连模式,公有的业务逻辑也在这层实现,我们在这层也是相同的套路,支持了:
- 用户鉴权和登陆校验,支持接口级别配置
- 黑白名单,分全局和应用,以及 ip 维度,参数级别
- 流量控制,支持自动和手动,自动是对超大流量自动拦截,通过令牌桶算法实现
- 智能熔断,在 Hystrix 的基础上做了改进,支持自动升降级,我们是全部自动的,也支持手动配置立即熔断,就是发现服务异常比例达到阀值,就自动触发熔断
- 灰度发布,我对新启动的机器的流量支持类似 tcp 的慢启动机制,给机器一个预热的时间窗口
- 统一降级,我们对所有转发失败的请求都会找统一降级的逻辑,只要业务方配了降级规则,都会降级,我们对降级规则是支持到参数级别的,包含请求头里的值,是非常细粒度的,另外我们还会和 varnish 打通,支持 varnish 的优雅降级
