一文为你介绍 ServiceComb Service-Center 三大高性能优化点
摘要:ServiceComb 项目是华为开源的微服务框架,于 2017 年 11 月捐赠给 Apache 社区并启动孵化。
注册中心背景
随着业务规模不断增长,传统单体结构变得越来越臃肿,在此背景下,单体应用在演进的过程中,进行微服务化拆分改造,成为主流的演进方向,逐步衍生出了微服务架构。
在微服务架构中,注册中心是最核心的组件之一,它承担着数量庞大的不同微服务之间的动态寻址问题。如下图:
注册中心架构按 CAP 定理可以分为 AP 和 CP 两种架构,现在市面上的注册中心 CP 架构的注册中心有 zk,etcd,nacos1.0 等,AP 架构的有:eureka,nacos2.0 等。
ServiceComb Service-Center 简介
ServiceComb 项目是华为开源的微服务框架,于 2017 年 11 月捐赠给 Apache 社区并启动孵化。作为一站式微服务解决方案,ServiceComb 包含 java-chassis, service-center,kie, pack 等多个子项目,Service-Center就是其注册中心组件。
Service-Center 是一个高可用、无状态的微服务注册中心,基于 Go 语言实现,可提供服务实例注册、发现、依赖管理、黑白名单控制等管理能力。
随着业务规模扩大,我们在实践中发现 Service-Center 1.X 版本因其 CP 架构设计,存在性能天花板,只能达到万级别的实例管理能力。据此,我们开展了一系列性能优化的探索,例如切换底层存储(etcd 换成 Mongo DB)、异步批量注册、心跳长连接等等,在实践中性能有了显著的提升,下面将详细展开介绍。
Service-Center 1.X 版本架构以及问题
首先我们来看一下 service-center 1.X 版本的架构,并结合测试分析存在的几个关键问题。
双层架构
Service-Center 是双层架构,分为 session 层(图中 SC 节点)和 data 层(图中 etcd 节点)。SC 主要用于跟客户端交互,接收注册请求、推送实例信息等,etcd 中持久化了服务信息、实例信息,同时将所有数据同步给了所有 SC,充当了一个同步器。
Service-Center 客户端通过 HTTP 请求发送注册请求到 SC 节点,然后 SC 把注册信息存储到 etcd 数据库里,并且缓存到本地内存中。
provider 实例客户端会向 SC 层发送注册请求和心跳请求,两种都是 HTTP 请求;consumer 实例客户端会向 SC 层发送订阅请求,基于 websocket 协议,建立 websocket 长连接,通过 listwatch 机制,获取 SC 中新注册的实例。
Service-Center 的实例信息最终会存储到 etcd 数据库中,并且其他 SC 通过监听 etcd 的变化,同步获取所有实例信息。
问题发现与分析
在测试中我们发现了几个问题:
心跳请求频繁
实例通过心跳续约,每 30s 上报一次心跳,当服务/实例规模上升时,心跳请求轮询数量众多。心跳请求又是基于 HTTP 短连接模型的,每次客户请求都会创建和销毁 TCP 连接,而销毁释放 TCP 连接需要一定的时间,导致有很多 TIME_WAIT 状态的连接,这样一来业务处理的时间小于连接建立销毁的时间,导致资源空耗严重。
逐条写数据库
注册数据逐条写到 etcd,每一个注册请求都会对应一条写 DB 的操作,随着规模的增长,对 etcd 的压力显著增大,这时写 etcd 就成为瓶颈点。
etcd 强一致性
etcd 集群数据同步采用 RAFT 强一致算法,根据 CAP 理论,保证了强一致性,系统在可用性上就需要做出牺牲。etcd 在写操作存在性能上限,导致服务实例注册速度慢;读操作虽然 SC 层有缓存机制,请求量过大时,会造成对 etcd 读请求压力过大及 service center 内存不够问题。
Service-Center 2.0 版本高性能优化
双层架构
该架构兼容了 1.X 架构的功能,并且新增了 DB 存储对接 Mongo DB 的功能、websocket 心跳长连接的功能、异步批量注册等功能(具体的选型过程可见后面章节)。
优化点
存储切换
1.选型调研
对于 etcd 的性能限制的问题,我们对底层数据库进行了选型,需要一个满足最终一致性、批量操作、横向扩展、订阅、高可用、TTL 等功能的 DB,经筛选发现 MongoDB 是一个适合的数据库,同时还具有文档结构存储方式,方便获取数据,完全能满足性能要求。
2.实现细节
实现过程中,首先我们保证功能向前兼容,可以通过配置来选择存储使用 etcd 还是 MongoDB,
做到了客户端和服务端交互无感知。
SC 实现了一套流程来对接 mongo DB,如下图。API、订阅等流程复用了 1.X 中的逻辑,数据缓存因为跟 etcd 的存储结构存在差异,单独实现了对 mongo DB 结构的适配,Dao 层封装了 service\instance\Dependency 等资源的 CRUD 接口,最终通过 mongo 的 client 与 mongo DB 交互。
Cache 和同步实现
缓存以及多个 SC 之间的数据同步是通过 SC 与 mongo DB 的 list watch 机制实现的。SC 启动时,会触发 list watch 定时任务,list 操作即调用查询所有资源接口,list 操作会定时执行,时间可以通过配置文件配置(默认 120s);而 watch 操作用到了 MongoDB 的 Change Stream 机制,下面详细介绍一下。
Mongo DB 的 Change Stream 机制,是指通过一次 Change Stream API 调用,即可从 MongoDB 侧获取增量修改。通过 resume token 来标识拉取位置,只需在 API 调用时,带上上次结果的 resume token,即可从上次的位置接着订阅,这样可以保证数据不丢失。
SC 启动 doWatch 流程后,传入 resume token 与 mongo 与建立 watch 长连接,这期间 mongo DB 会实时推送变更数据给 SC,并更新 resume token。在异常情况下,因 mongo DB 故障或其他原因导致 watch 失败或断连,SC 则记录故障前的 resume token,下次 watch 重新建立连接时传入 resume token,SC 就可以接着故障前的记录订阅,保证了不丢数据,也保证了可用性。
Dao 层封装
Mongo DB 是文档存储结构,在 DB 里创建了 service 、instance、dependency 等集合来存储服务、实例、依赖关系的信息。然后封装了每个集合的 CRUD 操作命令,并且封装了一些公共的过滤条件用于不同的文档操作(查询,更新,删除)。在实践中还发现了 DB 慢查询问题,为了解决该问题,我们设计了合理的索引,然后 SC 在启动流程中加上了建立索引的流程,保证有效高效的进行数据库操作。
当然在 DB 操作方面还有一定的优化空间,后面我们也会继续去分析优化方法,保证高效的数据库操作。
3.优化成果
切换 Mongo DB 之后与使用 etcd 的 SC 接口性能进行了对比测试,SC(8u16g*2)+Mongo DB(16u32g*2)的规格下,Mongo DB 注册服务接口 TPS 达到 5w,相比 etcd 只有 6k,提升了 8 倍,Mongo DB 注册实例接口 TPS 达到 3w,相比 etcd 版本的 4k,提升了 7.5 倍。
心跳长连接
1.选型调研
因为心跳 HTTP 请求对资源空耗过多,于是我们分析并尝试使用长连接来解决问题。对 grpc、thrift、websocket 进行了一个简单的测试。简单定义了传输消息的 protobuf 文件,在 2 台 4u8g 的机子上分别部署 server 和 client。下表是相关的测试数据(每组数据分别测了 10 次取了平均值):
通过分析可知,相同条件下,thrift 的资源消耗率最低,但是吞吐率最低;websocket 的吞吐率最高,资源消耗也适中。我们最终选择了 websocket,是因为原有 1.X 架构里 SC 订阅推送用的就是 websocket 长连接,考虑到长连接复用问题。其实 grpc 也是很好的备选项,它能额外能支持 stream,适合传输一些大数据,以及服务端和客户端长时间的数据交互,我们后期不排除会支持 grpc。
websocket 长连接复用了 HTTP 的握手通道,客户端通过 HTTP 请求与 websocket 服务端协商升级协议,协议升级完成后,后续的数据交换则遵照 websocket 协议。
2.实现细节
在实例注册完之后,客户端发送 HTTP 心跳请求,SC 接收到心跳请求后把 HTTP 协议升级为 websocket 协议, 然后 SC 通过 websocket 长连接每 30s 发送一次 ping 请求给客户端,客户端则返回 pong 请求给 SC,这样 SC 就能知道实例是否还在线。
SC 收到心跳之后,还会去更新 DB 中实例的 modTimestamp 列,这是为了配合 Mongo DB 的 TTL 索引机制,来保证 SC 故障场景没有数据残留,后面文章会详细说明。
实例正常下线时候,会主动调用注销接口的,但不排除有些实例可能意外下线了,还没来得及调用注销接口,这时候 SC 发送 ping 请求给客户端,就无法得到响应了,这样 SC 识别到心跳超时,就会主动去删除 DB 中存储的实例信息,保证实例及时下线。
当所有 SC 实例故障时,websocket 连接断连,注销接口也无法调用,这时 DB 中的实例就可能永远无法下线了,为了解决这个问题我们用到了 Mongo DB 的 TTL 索引机制。
TTL 全称是(Time To Live), TTL 索引能对一个单列配置过期属性来实现对文档的自动过期删除,我们对实例的 modTimestamp 列创建 TTL 索引,默认设置 expireAfterSeconds 为 300s, 这样 Mongo DB 会自动去清理超过 300s 没有报心跳的实例。
3.优化成果
实现心跳长连接之前,SC(8u16g)规格下实例数量到 3w 时, SC 节点因为 HTTP 连接建立断开的空耗,开始出现注册阶段 CPU 过高的情况,优化为长连接之后,实例注册节点运行更加稳定,CPU 消耗减少一半,总体支持的数量也明显提升。
异步批量注册
1.前期测试
发现逐条注册对 DB 的压力问题之后,通过调研和测试发现批量写相比逐条写 TPS 能提升 8 倍,所以我们决定实现一套异步批量注册流程,来满足大规模、高性能的场景。
2.实现细节
左边是实例异步批量注册的流程图,右边是实例逐条注册的流程图:
默认使用逐条注册流程,如果规模大,对高性能有要求的场景可以开启异步批量注册。
正常情况:
异步注册收到实例注册请求后,把实例注册信息放入队列,最终通过定时任务批量注册到 MongoDB 中(默认 100ms)。
异常情况下:
如果 Mongo 和服务中心的连接断开,注册失败,实例会被放入失败队列重新注册;
如果连续 3 次注册失败,熔断器会控制暂停 5s,注册成功后恢复;
单个实例注册失败超过一定次数(默认 500 次),该实例将被丢弃,当心跳发现该实例不存在时 SDK 会重新注册。
1. 测试结果
通过异步注册,提升了注册速度,降低了 mongo DB 的消耗,SC(8u16g*2)+Mongo DB(16u32g*2)的规格下异步注册实例接口 TPS 由 3w,上升到 9w,性能提高三倍。
Service-Center 整体性能测试
测试方法
我们进行了模拟真实场景进行测试,按 1:1 部署 provider 和 consumer 服务实例,consumer 向 SC 订阅若干个服务,然后调用 provider。
部署模式,如下图所示:
Client 端部署
通过 K8S 集群部署 provider deployment 和 consumer deployment,来模拟实例上下线,通过修改 deployment 的副本数来增加 pod 个数,每个 Pod 里面部署 200 个实例进程,可以通过部署 100、200、400 个 pod 来模拟 2w、4w、8w 实例的注册。
Service-Center 部署
总资源为 24u48g,两台 4u8g 虚机,两台 8u16g 虚机。
SC 采用双节点部署,部署在 4u8g 节点。
Mongo DB 采用副本集模式,主从节点分别部署在两台 8u16g 节点上, 因为 mongo DB 的仲裁节点,资源消耗较低,所以跟 SC 其中一个节点部署在一起。
测试方法:
provider 和 consumer, 按 1:1 的比例部署,先创建 provider,再部署 consumer,分别测试每个 consumer 订阅 2、5、10 个 provider 服务的情况。
性能测试报告
在 24u48g 的资源下,测试报告如下表:
24u48g 规格下 Service-Center 能支持的最大实例数量为 8w。此时 SC 内存达到了 80%,继续往上注册实例 SC 节点在注册阶段容易出现 OOM,导致节点崩溃。
当 consumer 订阅数量上涨的时候,最大能支撑的服务数量略有下降,订阅 2 个服务时,最大能稳定支撑 8w 实例,而订阅 5 个和 10 个服务的时候,分别最大能支撑到 7w、6w 实例。
把资源扩大到 32u64g 后,Service-Center 最大能支撑 16w 实例。
相比其他注册中心性能
同样在 24u48g 的资源下,我们测试了国内某流行开源注册中心的性能,结果如下表:
24u48g 规格下,该注册中心最大支撑实例数为 4w, 当测试注册 5w、6w 实例注册的时候系统就不稳定了,该注册中心注册阶段的 CPU 使用率接近 100%,会出现集群节点崩溃的情况。
随着订阅数量上涨,最大支持的服务数量也会下降,性能瓶颈主要在于注册接的阶段和稳定阶段的 CPU,实例数量上去 CPU 压力太大导致注册时候系统无法达到稳定状态,系统会崩溃。
测试过程中也参考了该注册中心官方的测试方法和数据,如下:
https://nacos.io/zh-cn/blog/performance-compare.html
Service-Center 性能测试结论
Service-Center 在 24u48g 的规格下能稳定支撑 8w 实例,32u64g 规格下,能稳定支撑 16w 实例;当前主要瓶颈在于 SC(4u8g)内存不够, 横向扩展 SC,或者扩大 SC 内存规格,支持实例的数量还有进一步上涨的空间。
跟某开源注册中心产品相比性能稍有优势,同等规格下能稳定支持的实例数量更多,注册阶段系统更加稳定。
ServiceComb Service-Center 未来规划
通过持续的分析优化,我们也发现了当前架构还存在的一些问题,识别到可以持续改进的优化点,抽象复用:如 Mongo/etcd 代码持续抽象,保证只有数据库对接层代码不同,其他流程都能共用;架构设计:如缓存结构优化、心跳更新批量处理、长连接优化、Mongo DB 分片、SDK 亲和性调度优化等等;文档方面也可以逐步更新、细化官方文档细节内容,促进讨论与贡献。争取在持续优化后,在真实场景下,能支持百万级别实例管理。
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/c402d62a1cd95457a9a0f0621】。文章转载请联系作者。
评论