一文为你介绍ServiceComb Service-Center三大高性能优化点

背景

随着业务规模不断增长,传统单体结构变得越来越臃肿,在此背景下,单体应用在演进的过程中,进行微服务化拆分改造,成为主流的演进方向,逐步衍生出了微服务架构。

在微服务架构中,注册中心是最核心的组件之一,它承担着数量庞大的不同微服务之间的动态寻址问题。如下图:

注册中心背景.PNG

注册中心架构按CAP定理可以分为APCP两种架构,现在市面上的注册中心CP架构的注册中心有zk,etcd,nacos1.0等,AP架构的有:eurekanacos2.0等。

ServiceComb Service-Center简介

ServiceComb项目是华为开源的微服务框架,于 2017 11 月捐赠给 Apache 社区并启动孵化。作为一站式微服务解决方案,ServiceComb包含 java-chassis service-centerkie pack等多个子项目,Service-Center就是其注册中心组件。

Service-Center 是一个高可用、无状态的微服务注册中心,基于Go 语言实现,可提供服务实例注册、发现、依赖管理、黑白名单控制等管理能力。

随着业务规模扩大,我们在实践中发现Service-Center 1.X版本因其CP架构设计,存在性能天花板,只能达到万级别的实例管理能力。据此,我们开展了一系列性能优化的探索,例如切换底层存储(etcd换成Mongo DB)、异步批量注册、心跳长连接等等,在实践中性能有了显著的提升,下面将详细展开介绍。

Service-Center 1.X版本架构以及问题

首先我们来看一下service-center 1.X版本的架构,并结合测试分析存在的几个关键问题。

双层架构

etcd双层架构.PNG

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的变化,同步获取所有实例信息。

问题发现与分析

在测试中我们发现了几个问题:

1.0架构问题分析.PNG

心跳请求频繁

实例通过心跳续约,每30s上报一次心跳,当服务/实例规模上升时,心跳请求轮询数量众多。心跳请求又是基于HTTP短连接模型的,每次客户请求都会创建和销毁TCP连接,而销毁释放TCP连接需要一定的时间,导致有很多TIME_WAIT状态的连接,这样一来业务处理的时间小于连接建立销毁的时间,导致资源空耗严重。

逐条写数据库

注册数据逐条写到etcd,每一个注册请求都会对应一条写DB的操作,随着规模的增长,对etcd的压力显著增大,这时写etcd 就成为瓶颈点。

etcd强一致性

etcd集群数据同步采用RAFT强一致算法,根据CAP理论,保证了强一致性,系统在可用性上就需要做出牺牲。etcd在写操作存在性能上限,导致服务实例注册速度慢;读操作虽然SC层有缓存机制,请求量过大时,会造成对etcd读请求压力过大及service center内存不够问题。

Service-Center 2.0版本高性能优化

双层架构

2.0性能架构优化.PNG

该架构兼容了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接口,最终通过mongoclientmongo DB交互。

存储切换优化.PNG

Cache和同步实现

缓存以及多个SC之间的数据同步是通过SCmongo DBlist watch机制实现的。SC启动时,会触发list watch定时任务,list操作即调用查询所有资源接口,list操作会定时执行,时间可以通过配置文件配置(默认120s);而watch操作用到了MongoDBChange Stream机制,下面详细介绍一下。

Mongo DBChange Stream机制,是指通过一次Change Stream API 调用,即可从MongoDB侧获取增量修改。通过 resume token 来标识拉取位置,只需在 API 调用时,带上上次结果的 resume token,即可从上次的位置接着订阅,这样可以保证数据不丢失。

SC启动doWatch流程后,传入resume tokenmongo与建立watch长连接,这期间mongo DB会实时推送变更数据给SC,并更新resume token。在异常情况下,因mongo DB故障或其他原因导致watch失败或断连,SC则记录故障前的resume token,下次watch重新建立连接时传入resume tokenSC就可以接着故障前的记录订阅,保证了不丢数据,也保证了可用性。

Dao层封装

Mongo DB是文档存储结构,在DB里创建了service instancedependency等集合来存储服务、实例、依赖关系的信息。然后封装了每个集合的CRUD操作命令,并且封装了一些公共的过滤条件用于不同的文档操作(查询,更新,删除)。在实践中还发现了DB慢查询问题,为了解决该问题,我们设计了合理的索引,然后SC在启动流程中加上了建立索引的流程,保证有效高效的进行数据库操作。

当然在DB操作方面还有一定的优化空间,后面我们也会继续去分析优化方法,保证高效的数据库操作。

3.优化成果

切换Mongo DB之后与使用etcdSC接口性能进行了对比测试,SC8u16g*2+Mongo DB(16u32g*2)的规格下,Mongo DB注册服务接口TPS达到5w,相比etcd只有6k,提升了8倍,Mongo DB注册实例接口TPS达到3w,相比etcd版本的4k,提升了7.5倍。

心跳长连接

1.选型调研

因为心跳HTTP请求对资源空耗过多,于是我们分析并尝试使用长连接来解决问题。对grpcthriftwebsocket进行了一个简单的测试。简单定义了传输消息的protobuf文件,在24u8g的机子上分别部署serverclient。下表是相关的测试数据(每组数据分别测了10次取了平均值):

长链接测试.PNG

通过分析可知,相同条件下,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 DBTTL索引机制,来保证SC故障场景没有数据残留,后面文章会详细说明。

心跳建立.PNG

实例正常下线时候,会主动调用注销接口的,但不排除有些实例可能意外下线了,还没来得及调用注销接口,这时候SC发送ping请求给客户端,就无法得到响应了,这样SC识别到心跳超时,就会主动去删除DB中存储的实例信息,保证实例及时下线。

心跳断开.PNG

当所有SC实例故障时,websocket连接断连,注销接口也无法调用,这时DB中的实例就可能永远无法下线了,为了解决这个问题我们用到了Mongo DBTTL索引机制。

TTL全称是(Time To Live), TTL索引能对一个单列配置过期属性来实现对文档的自动过期删除,我们对实例的modTimestamp列创建TTL索引,默认设置expireAfterSeconds300s, 这样 Mongo DB会自动去清理超过300s没有报心跳的实例。

SC故障.PNG

3.优化成果

实现心跳长连接之前,SC8u16g)规格下实例数量到3w时, SC节点因为HTTP连接建立断开的空耗,开始出现注册阶段CPU过高的情况,优化为长连接之后,实例注册节点运行更加稳定,CPU消耗减少一半,总体支持的数量也明显提升。

异步批量注册

  1. 前期测试

发现逐条注册对DB的压力问题之后,通过调研和测试发现批量写相比逐条写TPS能提升8倍,所以我们决定实现一套异步批量注册流程,来满足大规模、高性能的场景。

  1. 实现细节

左边是实例异步批量注册的流程图,右边是实例逐条注册的流程图:

异步注册.PNG

默认使用逐条注册流程,如果规模大,对高性能有要求的场景可以开启异步批量注册。

正常情况:

异步注册收到实例注册请求后,把实例注册信息放入队列,最终通过定时任务批量注册到MongoDB中(默认100ms)。

异常情况下:

如果Mongo和服务中心的连接断开,注册失败,实例会被放入失败队列重新注册;

如果连续3次注册失败,熔断器会控制暂停5s,注册成功后恢复;

单个实例注册失败超过一定次数(默认500次),该实例将被丢弃,当心跳发现该实例不存在时SDK会重新注册。

  1. 测试结果

通过异步注册,提升了注册速度,降低了mongo DB的消耗,SC8u16g*2+Mongo DB(16u32g*2)的规格下异步注册实例接口TPS3w,上升到9w,性能提高三倍。

Service-Center 整体性能测试

测试方法

我们进行了模拟真实场景进行测试,按1:1部署providerconsumer服务实例,consumerSC订阅若干个服务,然后调用provider

部署模式,如下图所示:

性能测试部署模式.PNG

Client端部署

通过K8S集群部署provider deploymentconsumer deployment,来模拟实例上下线,通过修改deployment的副本数来增加pod个数,每个Pod里面部署200个实例进程,可以通过部署100200400pod来模拟2w4w8w实例的注册。

Service-Center部署

总资源为24u48g,两台4u8g虚机,两台8u16g虚机。

SC采用双节点部署,部署在4u8g节点。

Mongo DB采用副本集模式,主从节点分别部署在两台8u16g节点上, 因为mongo DB的仲裁节点,资源消耗较低,所以跟SC其中一个节点部署在一起。

测试方法:

providerconsumer, 按1:1的比例部署,先创建provider,再部署consumer,分别测试每个consumer订阅2510provider服务的情况。

性能测试报告

24u48g的资源下,测试报告如下表:

SC 24u48g性能测试报告.PNG

24u48g规格下Service-Center能支持的最大实例数量为8w。此时SC内存达到了80%,继续往上注册实例SC节点在注册阶段容易出现OOM,导致节点崩溃。

consumer订阅数量上涨的时候,最大能支撑的服务数量略有下降,订阅2个服务时,最大能稳定支撑8w实例,而订阅5个和10个服务的时候,分别最大能支撑到7w6w实例。

把资源扩大到32u64g后,Service-Center最大能支撑16w实例。

SC 32u64g性能测试报告.PNG

相比其他注册中心性能

同样在24u48g的资源下,我们测试了国内某流行开源注册中心的性能,结果如下表:

其他注册中心24u48g性能测试数据.PNG

24u48g规格下,该注册中心最大支撑实例数为4w, 当测试注册5w6w实例注册的时候系统就不稳定了,该注册中心注册阶段的CPU使用率接近100%,会出现集群节点崩溃的情况。

随着订阅数量上涨,最大支持的服务数量也会下降,性能瓶颈主要在于注册接的阶段和稳定阶段的CPU,实例数量上去CPU压力太大导致注册时候系统无法达到稳定状态,系统会崩溃。

测试过程中也参考了该注册中心官方的测试方法和数据,如下:

https://nacos.io/zh-cn/blog/performance-compare.html

Service-Center 性能测试结论

Service-Center24u48g的规格下能稳定支撑8w实例,32u64g规格下,能稳定支撑16w实例;当前主要瓶颈在于SC4u8g)内存不够, 横向扩展SC,或者扩大SC内存规格,支持实例的数量还有进一步上涨的空间。

跟某开源注册中心产品相比性能稍有优势,同等规格下能稳定支持的实例数量更多,注册阶段系统更加稳定。

ServiceComb Service-Center未来规划

通过持续的分析优化,我们也发现了当前架构还存在的一些问题,识别到可以持续改进的优化点,抽象复用:如Mongo/etcd代码持续抽象,保证只有数据库对接层代码不同,其他流程都能共用;架构设计:如缓存结构优化、心跳更新批量处理、长连接优化、Mongo DB分片、SDK亲和性调度优化等等;文档方面也可以逐步更新、细化官方文档细节内容,促进讨论与贡献。争取在持续优化后,在真实场景下,能支持百万级别实例管理。

作者简介:

傅紫叶:目前就职于华为云HCS实验室,从事云计算PaaS领域异地多活、注册中心等方向的研究与开发
罗健文:目前就职于华为云HCS实验室,从事云计算PaaS领域微服务架构、微服务治理等方向的研究与开发

(完)