日200亿次调用,喜马拉雅网关的架构设计

本文目录- 说在前面
- 喜马拉雅自研亿级API网关技术实践
- 1、第1版:Tomcat NIO+Async Servlet
- 2、第2版?.NETty+全异步
  - 2.1 接入层
  - 2.2 业务逻辑层
  - 2.3 服务调用层
    - 2.3.1 异步 Push
    - 2.3.2 连接池
    - 2.3.3 Connection:close
    - 2.3.4 写超时
- 3、全链路超时机制
- 4、监控报警
- 5、性能优化实践
  - 5.1 对象池技术
  - 5.2 上下文切换
  - 5.3 GC优化
  - 5.4 日志
- 6、未来规划
- 说在最后:有问题可以找老架构取经
- 部分历史案例
?喜马拉雅自研亿级API网关技术实践网关作为一种发展较为完善的产品 , 各大互联网公司普遍采用它作为中间件 , 以应对公共业务需求的不断浮现 , 并能迅速迭代更新 。
如果没有网关 , 要更新一个公共特性 , 就得推动所有业务方都进行更新和发布 , 这无疑是效率极低的 。然而 , 有了网关之后 , 这一切都不再是问题 。
喜马拉雅也如此 , 用户数量已增长到 6 亿级别 , Web 服务数量超过 500 个 , 目前我们的网关每天处理超过 200 亿次的调用 , 单机 QPS 峰值可达 4w+ 。
除了实现基本的反向代理功能 , 网关还具备许多公共特性 , 如黑白名单、流量控制、身份验证、熔断、API 发布、监控和报警等 。根据业务方的需求 , 我们还实现了流量调度、流量复制、预发布、智能升级、流量预热等相关功能 。
从技术上来说 , 喜马拉雅API网关的技术演进路线图大致如下:

日200亿次调用,喜马拉雅网关的架构设计

文章插图
注意:请点击图像以查看清晰的视图!
本文将介绍在喜马拉雅 API 网关面临亿级流量的情况下 , 我们如何进行技术演进 , 以及我们的实践经验总结 。
1、第1版:Tomcat NIO+Async Servlet在架构设计中 , 网关的关键之处在于接收到请求并调用后端服务时 , 不能发生阻塞(Block) , 否则网关的处理能力将受到限制 。
这是因为最耗时的操作就是远程调用后端服务这个过程 。
如果此处发生阻塞 , Tomcat 的工作线程会被全部 block 住了 , 等待后端服务响应的过程中无法处理其他请求 , 因此这里必须采用异步处理 。
架构图如下:

日200亿次调用,喜马拉雅网关的架构设计

文章插图
注意:请点击图像以查看清晰的视图!
在这个版本中 , 我们实现了一个单独的 Push 层 , 用于在网关接收到响应后 , 响应客户端 , 并通过此层实现与后端服务的通信 。
该层使用的是 HttpNioClient , 支持业务功能包括黑白名单、流量控制、身份验证、API 发布等 。
然而 , 这个版本仅在功能上满足了网关的要求 , 处理能力很快成为瓶颈 。当单机 QPS 达到 5K 时 , 会频繁发生 Full GC 。
通过分析线上堆 , 我们发现问题在于 Tomcat 缓存了大量 HTTP 请求 。因为 Tomcat 默认会缓存 200 个 requestProcessor , 每个处理器都关联一个 request 。
另外 , Servlet 3.0 的 Tomcat 异步实现可能会导致内存泄漏 。后来我们通过减少这个配置 , 效果明显 。
然而 , 这种调整会导致性能下降 。总结一下 , 基于 Tomcat 作为接入端存在以下问题:
Tomcat 自身的问题:
  • 【日200亿次调用,喜马拉雅网关的架构设计】1)缓存过多 , Tomcat 使用了许多对象池技术 , 在有限内存的情况下 , 流量增大时很容易触发 GC;
  • 2)内存 Copy , Tomcat 的默认内存使用堆内存 , 因此数据需要从堆内读取 , 而后端服务是 Netty , 使用堆外内存 , 需要经过多次 Copy;
  • 3)Tomcat 还有个问题是读 body 是阻塞的, Tomcat 的 NIO 模型和 reactor 模型不同 , 读 body 是 block 的 。
这里再分享一张 Tomcat buffer 的关系图:

日200亿次调用,喜马拉雅网关的架构设计


推荐阅读