本文由金山办公中台部门 SRE 网络负责人张强介绍了金山办公如何使用 Apache APISIX 应对百万级 QPS 业务,同时基于 Apache APISIX 更新与改进网关实践层面的内容。
背景介绍
金山办公是目前国内最大的办公软件厂商,旗下产品涉及 WPS、金山文档、稻壳等。在业务层面上由数千个业务以容器化部署在内部云原生平台,目前 Apache APISIX 在金山办公主要负责为中台部门业务(百万级 QPS )提供相关网关服务。
金山办公的网关演进
在 1.0 阶段时,我们对于 API Gateway 的特性没有什么强需求,只是想解决运维问题,所以基于 OpenResty 与 Lua 进行了自研,实现了动态 Upstream、黑名单、waf 等功能。 虽然自研成功,但在功能上却遗留了一些问题,比如:
- 动态化只做到到 Upstream 维度
- 需要 Reload 才能带出新域名
- 底层设计简单,功能扩展能力不强
后续我们对 API Gateway 功能有了强需求后,开始去调研相关的开源网关产品。
为什么选择了 Apache APISIX
实际上 2019 年年底开始调研网关产品时,Kong 算是一个比较流行的选择。
但后续经过测试发现,Kong 的性能不太能满足我们的需求,同时我们认为 Kong 的架构不是很优秀:因为其配置中心选用 PostgreSQL,所以 Kong 只能利用非事件驱动去更新路由,依赖每个节点去刷新路由。
进一步调研时,我们发现了 Apache APISIX。首先 Apache APISIX 的性能比 Kong 强,在 Apache APISIX 的 GitHub Readme 中有个非常详细的对比图,列出了两者的性能测试差距,这与我们自己测试下来的数据基本一致。
在架构方面,Apache APISIX 的 etcd 配置对我们而言是一项更优的选择。
当然,最主要的原因是我们觉得社区也很重要。社区如果活跃,在版本更新迭代、问题解决和功能优化上的速度就会很快。从 GitHub 和平时的邮件反馈中我们看到了 Apache APISIX 社区的活跃,为产品功能和稳定性提供了强有力的保证。
网关平滑迁移经验分享
大部分朋友在开始接触 Apache APISIX 时,都会用 CLI 去生成配置并起实例。但在我们做平滑迁移的过程中,并没有使用 CLI 去生成配置。
主要原因是 Apache APISIX 在 OpenResty 中会生效一些 Phase,比如初始化 init、init_worker、HTTP 和 Upstream 相关 Phase 等。对应到 Apache APISIX 的配置后我们发现,这些都可以脱离 CLI 而存在。
所以基于上述原因,我们最终采取了如下行动进行平滑迁移:
- 不使用 Apache APISIX 的 CLI 生成配置
- 引入 Apache APISIX 的 Package Path 并将 Apache APISIX 作为 Default Server
- 保留其它静态配置中的域名,由于新域名未在静态配置中,将 Fallback 到 Apache APISIX
- 最终将静态配置逐渐迁移到 Apache APISIX 中
当然,除了上述方法,我们也给大家推荐一种「轻混模式」,即使用静态配置配合 Apache APISIX 作为 Location,引入前边提到的一些 Phase 或 Lua 代码进行配置即可。这样做可以在静态配置中引入一些特殊配置,实现动态化等效果。
基于 Apache APISIX 的 Shared State 改进
首先在我个人看来,「转发效率一定不是问题,而 Shared State 是影响稳定性的最大因素」,为什么这么说?
因为转发效率可以通过横向扩容去解决,但 Shared State 是所有的节点共享的,所以是至关重要的模块。
所以在使用 Apache APISIX 后,我们主要针对 Shared State 层面进行了一些调整与优化。
优化一:多台机器监听下的 etcd 架构优化
一般公司网关架构中,都会涉及多台机器,有的可能多至几百台,同时每台机器还要顾及 worker 数量。所以当多台机器监控相同 Key 时,etcd 的压力就会比较大,因为 etcd 的其中一个机制是为了保证数据一致性,需要所有事件返回给监听请求后才能处理新请求,当发送缓冲满了后就会丢弃请求。所以当多台机器同时监听时就会导致 etcd 超时运行,提示 Overload 报错等状况。
针对上述问题,我们使用了自研的 etcd Proxy。之前 Apache APISIX 与 etcd 的连接关系如下图左侧所示,每个节点均与 etcd 连接。所以作为一个大规模入口时,连接数量会特别大,对 etcd 造成压力。
既然是监听相同的 Key,我们做了一个代理来进行统一监听,当有结果反馈时,再返回给 Apache APISIX。具体架构如上图右侧所示,在 Apache APISIX 和 etcd 中间放置了 etcd Proxy 组件来监控 Key 值的变化。
优化二:解决路由生效过程中的性能问题
随着公司规模提升,路由数量的增长也会随之而来。我们在实践过程中发现在每次路由更新时,Apache APISIX 都会重建用来匹配路由的前缀树。这个主要是由于 table.sort
性能不足所导致的。
在实践过程中,我们观察到路由频繁更新时,网关 CPU 升高、丢包率升高,进一步排查后发现丢包率升高的主要原因为 Listen overflow 所造成。
在 CPU 升高现象上,通过火焰图可以明显看到大部分 CPU 的时间都是划在 auxsort
上,它是由 FUNCC 触发。而 FUNCC 的触发也指明了一个问题,就是证明相关数据没有经过 LuaJIT,只有图中最右侧的一小部分处理了正常请求。
出现这种现象的原因主要是 LuaJIT 的 table.sort
不是完全依靠 JIT 模式,这点可以在 LuaJIT 官网 wiki 中看到相关说明,所以在 Lua 代码环境中使用 table.sort
效率是比较低的。
针对这个问题,我们自己使用纯 Lua 代码实现了针对上述场景的 sort 配置进行了解决,但其实 Apache APISIX 在之后的版本更新中已经修复了这项问题,具体思路也跟我们理解的类似。
更多 Shared State 使用经验
- 在修改 Apache APISIX 或者自己进行插件开发时,确保做好 Schema 校验,包含判空,尤其是在匹配部分。因为在匹配部分出问题的话,会造成整体性的影响。
- 做好业务拆分规划。根据业务量去规划好相关 etcd Prefix 和 IP 数量,部署更稳固的集群,把系统性风险降到最低
开源话题讨论
稳定性与功能层面的取舍
目前金山办公使用 Apache APISIX 已经快两年了,作为产品用户,我认为 Apache APISIX 确实是一款稳定可信的开源产品,在绝大多数情况下,都会及时地与社区最新版本保持一致。
但是一般接触并应用过开源产品的公司应该都有体会,升级版本会有一些新功能的出现,但同时也会带来一些稳定性上的问题,所以在升级版本和稳定性中我们应该如何取舍。
这个问题肯定没有统一的答案,但是我个人觉得针对 Apache APISIX 这项产品,尽量与官网版本保持一致。
就金山办公而言,我们目前因为大规模使用到 Apache APISIX,所以对稳定性有极致追求。之前跟不上官方更新进度时也对我们的使用造成了一定程度的影响,所以推荐大家尽量与官方版本保持一致。
如果说你像我们一样,有时候可能跟不上官方版本,至少也应该做到每周查阅 GitHub 的 Master Change Log 等相关文档,时刻关注产品变化。
基于 Apache APISIX 产品化经验
我们基于 Apache APISIX 包装了很多产品化功能,比如多机房应用比例切量、一键封禁路由等。在实践应用过程中,我们认识到 Apache APISIX 是一个极其灵活强大的产品,所以在进行产品化改造时我们就应该明白一个点:强大 = 避免不了的复杂和危险。
这点在 Apache APISIX 本身的代码设计上也有很多的体现,比如一些插件的改造可能就需要自己去编译,因为毕竟各自应用起来时场景没有办法做到统一。
最后,基于我们前边提到的实践经验,也建议大家在进行 Apache APISIX 项目产品化时,提前规划好网关共享的颗粒度,减少后续使用问题。