Apache APISIX 在雪球双活架构演进中的生产与实践
本文整理自雪球基础组件团队在 Apache APISIX Summit ASIA 2022 上的分享
一.背景
雪球的愿景是做「中国人首选的在线财富管理平台」,为投资者提供优质内容、实时行情、交易工具、财富管理等多种服务。其中实时行情服务对接了多种上游数据源,通过数据流式计算、存储、分发,为投资者提供稳定的数据服务。实时行情一直是雪球业务系统中的资源消耗大户,持续在高水位运行。雪球内部一项重要的工作就是持续进行稳定性建设,其中包括对行情服务进行性能优化。即便如此,在偶然发生极端行情的情况下,部分系统仍会因为数据量激增发生响应变慢,甚至是不可用的情况,从而影响用户体验。
在此背景下,雪球为了向广大投资者提供稳定优质的服务,启动了服务双活改造。雪球基础设施团队为业务团队提供微服务基础设施:framework / metric / log / trace,中间件适配,C端流量调度,鉴权,CI/CD 等一系列解决方案。
Apache APISIX能极大地简化双活架构的实施复杂度。同时APISIX自身的 dynamic
/ real-time
/ cloud-native
特性、丰富的社区生态和插件,为雪球未来云原生架构演进打下了良好的基础。本文总结描述了 Apache APISIX 助力雪球双活架构演进。
二.问题描述
上图是雪球单机房时期的简单架构描述,用户流量从云端入口( SLB )进来后,经网关做简单的公共性逻辑后,向后端服务转发。后端服务会通过 SDK 方式集成在服务中的鉴权模块向雪球用户中心发起用户鉴权,通过后进入后续业务处理流程。
双活改造痛点
- SDK 鉴权模块
在双活改造实施过程中,微服务的提供方和消费方无法完全同步的部署上线。当行情服务首先在云端上线,而雪球用户中心还不具备云端服务能力时,便会出现跨机房调用的情况。根据用户中心的统计,其 RPC 调用量约日均数十亿,峰值 50k 的 QPS,在行情高 QPS 场景下会带来较高延迟。
雪球鉴权业务复杂度也较高,除 OAuth2.0 / JWT 协议外,还需要兼顾客户端版本、雪球旗下多个 APP 等多种因素。由于鉴权模块嵌入了服务内,升级变得较为困难。
- OpenResty 网关
雪球一直在使用的是 OpenResty 作为网关,其自身特性比 APISIX 略有不足。将 OpenResty 集成雪球现有监控体系需要一定的工作量,同时扩展流程繁琐,需要运维人员添加自定义脚本。
- 依赖自研注册中心
目前雪球的 HTTP 服务注册是在后端服务启动时,请求注册中心将自身注册到网关,服务停止时请求注册中心进行服务节点摘除,注册中心会定期轮询服务节点进行健康检查。但自研的服务无法依靠开源社区的力量进行升级迭代,维护成本较高。
双活改造原则
- 不引入过多的变量,尽量对业务方透明和最小化改动
- 统一在基础设施层面解决问题
- 尽量将鉴权在本机房完成
综上考虑决定将鉴权移至 API 网关完成。
三.选型及实施
网关选型
基于在业务实践场景中逐渐显现的痛点,雪球基础设施团队开始了针对网关产品的调研。通过内部诉求和目前市场中网关产品的对比,最终选择了基于 Apache APISIX 进行后续架构的调整与使用。
调整后架构
如上图是目前雪球行情双活架构。左侧展示的是在原机房里对应的架构,并没有进行太多改动;右侧展示的则是上云之后基于多 Region 设计的多活架构。
上述架构主要基于 APISIX 进行了如下调整:
- 将鉴权模块统一调整到网关层,利用 APISIX 网关进行统一鉴权方式。其中涉及到 JWT 类型的可以直接利用 APISIX
jwt-auth
插件进行本地鉴权
- 兼容 OAuth 2.0 协议,利用 APISIX 统一调用雪球用户中心进行处理
- 对接雪球后端rpc服务注册中心,用于 JWT 鉴权失败时使用雪球后端服务来鉴权。
应用场景展示
在后端服务接入 APISIX 时,主要在网关鉴权和可观测性等层面进行了一些实践。
场景一:网关鉴权
在前文中提到过,雪球之前架构模式中的鉴权方式并不统一。一种需要依赖于内部的应用端,通过 SDK 形式去调用用户中心实现鉴权,另一种则使用 JWT 鉴权。当两种鉴权方式共存时,会带来扩展性和维护性较差的问题。
接入 APISIX 作为网关之后,在鉴权方案的改造上则是通过 APISIX 网关层来统一管理。基于官方插件 jwt-auth
去替代原有的 JWT 鉴权方式;同时结合雪球内部自身的业务要求,使用 APISIX grpc-transcode
插件调用鉴权服务,来处理 OAuth 2.0 相关的鉴权方式。
jwt-auth
插件的配置使用较为简单,在 Dashboard 中将路由和上下游等相关信息配置齐全即可开启使用。这里主要描述下雪球内部是如何利用 APISIX 调用 gRPC 来实现鉴权。
在实现调用之前,雪球内部考虑了三种解决方案。
- 方案一:Lua 直接调用 gRPC。由于此方案在执行中,需要去考虑负载均衡和 Dynamic Upstream 等相关实现。
- 方案二:Lua 协程回调 Golang。
- 方案三:Lua 进行 HTTP 调用,gRPC 接口采用 APISIX 的
grpc-transcode
插件进行实现。得益于 APISIX 社区对插件优化迭代快的前提,最终选择了方案三去实现 gRPC 调用。
在执行过程中,目前仍需要对 protocol buffers 文件进行手动同步。因为如果用户中心修改了该 protocol buffers 文件,但是与 APISIX 保存的 protocol buffers 文件不一致的话,会导致鉴权出现问题。
场景二:可观测性下的多维监控
雪球的日常使用场景中,通常在网站上线后是需要监控很多指标的,重点主要是以下三部分:
- NGINX 连接状态和进出口流量
- HTTP 错误状态码速率(用于排查 Service 或上下游问题)
- APISIX 请求延迟耗时( APISIX 进行转发时逻辑执行带来的耗时)
比如 APISIX 的延迟指标会在某些情况下,出现指标非常高的现象(如下图所示),这种其实是跟该延迟指标的计算逻辑相关。目前 APISIX 延迟指标的计算逻辑是:单条 HTTP 请求在 NGINX 上的耗时时间-这条请求路由到上游的延迟。两个耗时之间的差数值即为 APISIX 延迟指标数据。
使用 APISIX 后,在新增或修改一些插件时会导致一些逻辑的变更,变更之后可能会导致耗时相关的数据出现偏差。为了避免出现混淆数据真实性的现象发生,雪球在监控层面还增加了基于插件级别的耗时监控。在保证各数据监测的准确性下,还方便了后续进行插件级的业务改造时,提前通过耗时定位一些问题,从而方便排查。
同时利用 APISIX 的可观测性能力,还可以收集到 Access 日志信息。通过格式化统一收集到流量大盘中进行视图汇总,更方便地从多角度提前了解整体趋势,发现潜在问题并及时进行处理。
场景三:扩展 ZooKeeper 注册中心
目前,雪球 gRPC 服务调用是基于 Zookeeper 注册中心进行注册和发现。在雪球鉴权过程中,API 网关在本地 JWT 校验失败时,需要访问雪球用户中心的 gRPC 服务进行鉴权,这就要求 API 网关能够从注册中心获取后端 gRPC 服务地址列表。APISIX 官方插件 apisix-seed
可以去集成 ZooKeeper 进行服务发现。但结合雪球自身使用场景需求,在 APISIX 上进行了相关拓展。
具体实现主要是在 APISIX 的一个内容节点上,当 Worker 进程启动时去轮询像下图中的 ZK-Rest 集群,然后定时去拉取整个服务的源数据信息以及实际信息,更新到 Worker 进程内的本地缓存,用于服务列表的更新。
通过上图也可以看到,ZK-Rest 集群相当于通过 Rest 的形式进行访问 ZooKeeper 的数据。所以整个过程其实实现的功能比较少(主要是基于自身业务场景需求),只需要增加它的一个实例就可以实现高可用特性,免去一些复杂操作。
但这样操作也会带来一个比较明显的缺点。当需要定时去轮询 ZK-Rest 集群时,可能会导致服务列表在更新上出现延迟。所以这里也是提供给大家一个思路,仅供参考。
四.总结及展望
目前,Apache APISIX 在雪球内部作为网关层运行良好。具体表现在:
- 实现了在网关层统一鉴权、熔断与限流等功能;
- 降低了整体系统的耦合度,提高了双机房场景下的服务质量;
- 借助于 APISIX 监控体系,完善了从网关到服务的统一监控方案;
- 对全链路排查起到了很好的支撑;
- 对 gRPC 协议的转换与服务管理都提供了比较优雅的实现方式。
在后续的使用中,雪球将:
- 使用 APISIX Ingress Controller应用于K8s集群;
- 利用
grpc-transcode
插件进行 HTTP / gRPC 协议转换,达到后端统一接口形式;
- 利用
traffic-spilt
插件进行流量打标、对接nacos注册中心,实现全链路灰度等服务治理
雪球计划用 Apache APISIX 去替代现有的OpenResty,最终实现全域南北流量治理。