稳定性大幅度提升:SOFARegistry v6 新特性介绍

2021/05/13 16:19
阅读数 51
SOFARegistry 是蚂蚁集团内部在使用的服务注册中心,它支撑起了蚂蚁集团海量规模的业务服务。但随着蚂蚁集团业务规模的日渐庞大,SOFARegistry 在资源、容量、运维等方面也遇到了一些挑战,针对这一系列的挑战,我们对 SOFARegistry 进行了大量的改造,开发了 v6 版本的 SOFARegistry。

背景

1、v5 架构介绍

首先介绍一下我们的注册中心——SOFARegistry 现有的架构设计:采用双层存储架构,外加一个负责内部元数据管理的  Meta 组件;Session 为连接层,用于承载客户端的长连接,接收并转发客户端的注册信息到 Data 上;Data 为数据存储层,采用一致性 Hash 的数据分片方式实现水平扩容,同一个服务的 Publisher 会汇总到同一台 Data 上,同时采用多副本的方式实现高可用;Meta 为注册中心内部的管理组件,用于内部各个节点之间的互相发现。

SOFARegistry 有以下几大特点:
  • 支持海量数据:采用一致性 Hash 的数据分片方式,每台 DataServer 只存储一部分的分片数据,随数据规模的增长,只要扩容 ;DataServer 服务器即可。
  • 支持海量客户端:连接层的 SessionServer 只负责跟 Client 打交道,SessionServer 之间没有任何通信或数据复制,所以随着业务规模的增长,SessionServer 可以较轻量地扩容,不会对集群造成额外负担。
  • 秒级服务上下线通知:对于服务的上下线变化,SOFARegistry 使用推送机制,快速地实现端到端的传达。同时基于连接 + 心跳的探活设计,使得服务下线能更加及时的被发现。

2、一致性 Hash 数据分片

这边简单介绍一下 v5 版本 Data 做数据分片的方案:SOFARegistry 支持海量数据的存储,并使用了一致性 Hash 作为数据分片方案。每台服务器被虚拟成多个节点落在 Hash 环上,每个数据根据  Hash  算法计算出一个值,落到环上后顺时针命中的第一个虚拟节点,即负责存储该数据的主节点,后续命中的 N (N 为数据副本数)个不同节点作为存储该数据的副本节点。

当 Data 节点发生宕机时,比如图中 A 节点发生了宕机,原先存储在 A 上的数据,就会顺次命中顺时针后续节点,也就是该数据的副本节点,以此机制实现 Data 层的高可用。

在虚拟节点足够多的情况下,数据分片在每台节点上是非常分散均匀的,并且增加或减少节点的数量,还是能维持数据的平衡(SOFARegistry 默认虚拟点数为 1000)。


面临的挑战

在蚂蚁集团超大的业务规模下,现有的架构也遇到了很多的挑战。

1、超大规模集群

存储数据量巨大,数据回放耗时长,注册中心推送压力巨大。蚂蚁集团的单个机房,已经存在 900w Pub,2000w Sub 的注册中心集群,而且随着业务的快速发展,新增的服务和拆分的微服务个数也越来越多,注册中心内的数据量每年都在快速增长,相比 18 年,Pub/Sub 的量几乎翻倍。超大的数据量带来的是超大规模的注册中心集群,单集群超过 60 台 Data,120 台 Session,对于应急场景,注册中心的重建和重启都有很大的挑战。当注册中心进行重启,业务 Pod 大量的注册和订阅信息回放到注册中心上,经常触碰到 Session 和 Data 之间的通讯瓶颈,大集群数据回放耗时超过了 5 分钟,这影响着整个集群的快速恢复。
庞大的集群带来的还有巨大的推送压力,部分大型应用发布了上百数量的服务,本身又有数千的 Pods,同时下游订阅它 的服务的应用也很多,当这些大型应用进行批量重启期间,比如大促之前的场景,注册中心的推送任务数暴涨,推送延迟会有显著的增长。
还有就是 DataServer 的灰度能力偏弱,变更的节点对于影响范围难以确定。一致性 Hash 数据分片方式有一个特点就是当发生了扩容或者宕机时,虽然只变换了少量的节点,但是会影响到其他大多数节点的数据分布,每台节点都会因为自身负责的数据分片范围发生了变化而要进行数据迁移,而且这个变换和迁移的过程很难实现灰度,比如:我们只迁移少量数据分片到新节点上的灰度试点。

2、部署站点极多,建站和发布成本高

在蚂蚁集团内部,站点非常之多,各种租户,很多城市的数据中心都需要部署注册中心。同时注册中心本身的发布和运维是一个对整个集群影响非常大的动作,这就造成了注册中心的发布和建站人力成本巨大。


SOFARegistry v6 版本改造

针对现有的挑战,SOFARegistry v6 给出了对应的解决方案

  • 通过应用级服务发现改造,减少注册中心存储的数据量,减少推送数据量。
  • 基于 Slot 分配的数据分片存储,支持无损发布和灰度发布,减少 Data 宕机期间的影响。
  • nightly build,自动化部署流程,减少运维人力成本。

    1、应用级服务发现改造

    服务发现分类

    从服务发现的模型上,可以分为两大类:

    (1)服务维度:实例的每一个服务都注册一个 Publisher,调用端使用服务名称寻址 IP 列表。

    优点:服务的变更对调用方透明,针对新服务上线和微服务拆分的场景,可以做到调用方无感知。

    缺点: Publisher 数目是 Pod 数目乘以应用发布的服务数目,数据量巨大而且较难规划容量。
    (2)实例维度:每个实例只发布实例级的 Publisher,调用端使用应用名称寻址 IP 列表。

    优点:数据量只与 IP 数目成正比,整体角度可以预估压力。
    缺点: 丢失了部分信息,比如当前 IP 发布了哪些服务,这就导致了新服务上线和微服务拆分都需要调用端感知。

    数据模型改造

    我们参考了 Dubbo 3.0 的应用级服务发现设计方案,同时结合蚂蚁集团的 Service Mesh 产品,设计了一套对存量应用平滑迁移到应用级服务发现 + v6 版本注册中心的迁移方案。先简单介绍一下应用级服务发现的实现方案,下面是一份服务级的发布数据。
    com.alipay.testapp1.FooService:1.0@DEFAULT11.34.200.88:8080?v=4.0&_TIMEOUT=3000&_HOSTNAME=testapp1-85-5595&p1&app_name=testapp1&_SERIALIZETYPE=4&tls=false&mosn_version_none&mosn=true
    com.alipay.testapp1.BarService:1.0@DEFAULT11.34.200.88:8080?v=4.0&_TIMEOUT=3000&_HOSTNAME=testapp1-85-5595&p1&app_name=testapp1&_SERIALIZETYPE=4&tls=false&mosn_version=version_none&mosn=true

    从原理上讲,主要是把一个应用中和代码版本相关的参数抽取为一份元数据,比如服务名称、协议、版本等等。这些参数一个应用的大多节点都是一致的,只有在版本进行发布的时候才会存在两份元数据,对于这些重复率很高的元数据,会对元数据本身计算一次 Hash,得到一个 Revision 字符串。其他的参数,比如 IP、端口等等,大多是只和所在节点相关的参数,那么这些多个服务下相同参数都可以汇聚成一个实例级的 Publisher,引用元数据上的 Revision,这样从整个集群来看,原先服务级 Publisher 中大部分数据都可以被抽取为一份元数据,可以节省大量的存储空间。

    元数据 :
    {  "application": "testapp1",  "revision": "testapp1-594f803b380a41396ed63dca39503542",  "clientVersion": "v1.1.0",  "baseParams": {    "__SERIALIZETYPE":{"values":["4"]},    "app_name":{"values":["testapp1"]},    "_TIMEOUT":{"values":["3000"]},    "tls":{"values":["false"]},  },  "services": {    "com.alipay.testapp1.FooService:1.0@DEFAULT": {      "id": "0",    },    "com.alipay.testapp1.BarService:1.0@DEFAULT": {      "id": "1",    }  }  

    实例 Publisher

    {  "addr": "11.34.200.88:8080",  "rev": "testapp1-594f803b380a41396ed63dca39503542",  "mv": "v1.1.0",  "bps": {    "_HOSTNAME": ["testapp1-85-5595"],    "mosn_version":{"values":["version_none"]},    "mosn":{"values":["true"]},  }}

    如上元数据和 Publisher,结合两份数据,调用方就能还原这个节点发布了哪些接口以及有哪些参数,从而拼接出服务级的 URL。

    平滑迁移

    应用级服务发现改造有个难题就是存量应用的逐步迁移,以及会不会存在部分应用永远无法迁移。在蚂蚁集团内部,绝大部分应用都已经接入了 MOSN,所以我们整套方案都基于 MOSN 建立,借助 MOSN 实现应用级服务发现的平滑迁移和全站快速铺开;应用依然采用服务级 Pub/Sub 的方式和 MOSN 进行交互,MOSN 内部的应用级服务发现模块会转化为应用级的发布订阅,并通过动态配置开关的方式逐步开启应用级服务发现;为了平滑上线应用级服务发现,SOFARegistry v6 的上线采用对 v5 和 v6 两个版本的注册中心双订阅双发布的方式逐步灰度切换,确保在切换过程中,没有升级接入 MOSN 或者没有打开开关的应用完全不受影响。

    在完成绝大多数应用的应用级迁移后,全部的应用都已经到了 v6 版本的注册中心上,但仍然存在很少部分应用因为没有接入 MOSN,然后以服务级订阅发布和注册中心交互,为了确保已经改造完的应用和这部分没有改造的应用能够互相订阅,我们做了一些额外的支持:

    • 服务级订阅端是无法直接订阅应用级发布数据的,所以我们实现了一个应用级 Publisher 转接口级 Publisher 的模块,可以按需转化出指定服务的 Publisher,没有接入 MOSN 的应用可以顺利的订阅到这部分数据,因为只有很少部分应用没有接入 MOSN,所以需要进行转化的服务数目同样也很少。
    • 应用级订阅端在订阅的时候会额外发起一个服务级的订阅,用于订阅没有接入 MOSN 应用的发布数据,由于这部分应用非常少,实际绝大多数的服务级订阅都不会有推送任务。

    改造效果

    上图是我们压测数据,对两个注册中心进行完全相同的操作,一个进行应用级发布订阅,一个进行服务级发布订阅,同时监控各种事件的发生频率。我们模拟的测试应用,每个应用发布了 100 个服务,订阅了 200 个服务。应用级改造的优化非常明显:

    • Pub 注册的次数大幅度下降,下降了两个数量。
    • Sub 注册小幅度增长,因为如前面提到的那样,会同时发起服务级的 Sub,但大多场景这些额外的订阅是不会有推送的,结合推送次数的下降可以得出实际推送任务下降了几倍的结论。

    2、数据分片改造

    自定义 Slot 分配算法

    我们从逻辑上将数据范围划分成 N 个大小相等的  Slot,并且 Slot 数量后续不可再修改。然后,还需要引进“路由表”SlotTable 的概念,SlotTable 负责存放这每个节点和 N 个 Slot 的映射关系,并保证尽量把所有 Slot 均匀地分配给每个节点。
    Session 在进行路由的时候,根据 DataInfoId 的 Hash 值确定数据所在 Slot,再从路由表中拿到数据对应的 Data 节点进行数据拉取,每个 Slot 都会有一个主节点和多个副本节点,从而实现主节点宕机的时候,副本节点能快速提升为主节点。
    分配算法的主要逻辑是:
    • 主节点和副本节点不能分配在同一个 Data 上;
    • Slot 对应主节点 Data 宕机时,优先提升副本节点为主节点,减少不可服务时间;
    • 新节点先作为副本节点进行数据同步;主要目标在于减少节点变更时尽可能缩短注册中心数据的不可用时长。

    数据完整度校验

    Slot 数据完整度校验的作用在于当一个 Slot 数据所有副本所在节点全部宕机,数据发生丢失时,只能通过 Session 上的缓存数据进行数据回放。但在完全回放完成时,Slot 是不可对外提供读取服务的,以此避免推空或者推错。

    数据同步架构

    • Data 和 Session 通过心跳的机制在 Meta 上进行续约,当 Data 发生节点变更的时候,Meta 此时会重新进行分配,生成新的 SlotTable,并通过广播和心跳的方式返回所有的节点,Session 就会用这份 SlotTable 寻址新的 Data 节点。存在一个时刻,集群中存在两份 SlotTable,分裂时间最大(1s)。

    • Session 上会缓存 Client 的 Pub 和 Sub 数据作为基准数据,在增量发送给 Data 的同时,Slot 的 leader 节点会定时和 Session 进行数据比对,Slot 的 follower 和 leader 也会定时进行数据对比,这样做到整个集群数据能快速达到最终一致,减少异常场景的不可服务时间。

    无损发布

    无损发布的整体流程是 Data 上的 Slot 逐步迁移,期间仍然对外提供服务。

    迁移完成后主动从列表中剔除并设置节点黑名单并下线。

    以图为例,假设要下线的节点是 DataServer-1:将 Data-server 添加到注册中心迁移名单中;再将 Slot-2 对应的 follower(DataServer-2)提升为 Leader;等待 Data 节点达到稳定;继续将 Slot-3 对应的 follower(DataServer-3) 提升为 Leader;按照以上步骤分配掉剩余的 follower Slots;Slot 迁移完毕,Data-server 从 Data 列表中剔除,同时添加为黑名单;从迁移名单中设置为 succeed。

    灰度发布

    通过建立一个 DataId 到 Slot 的映射关系覆盖采用 Hash 的方式计算所属 Slot,同时暴露接口给外部的管控系统来实现,某一个特定服务的数据调度到新的 DataServer 来实现 DataServer 的灰度功能。

    3、新版本性能优化

    SOFARegistry 的 v6 版本相比于 v5 版本性能也有较大的提升:压测采用的机器都是 4c8g, Session*8, Data*3。

    从压测数据可以看到 v6 版本相对 v5 版本性能有较大提升;v5 版本在此压力场景下已经触碰到瓶颈,推送次数开始下降;v6 推送延迟也有显著下降。这边提一下推送延迟,指的是从发布端进行变更操作到订阅端接收到对应的数据的时长。结合断链感知和服务端推送的机制,v6 版本的数据变更能在较短时间以内广播到集群中的所有订阅端。

    4、nightly build 

    结合前面的改造,通过 Slot 分配算法实现无损发布和灰度发布,以及应用级服务发现改造减少数据量。可以使得注册中心的整个发布过程变得更加的自动化。

    一个版本的注册中心合入主分支后自动产出新的 Docker 镜像部署到测试集群,通过集测、单测的结果,还有高压混沌测试的测试报告,验证该版本后推进到线下、预发和灰度集群的发布。预发和灰度集群运行正常后发布至生产集群,生产发布的过程中会检查注册中心相关指标和数据检查进行变更防御。

    sofa-registry-chaos

    nightly build 中有两个重要的步骤,门禁中的高压混沌测试,以及变更防御中的数据检查。我们开发了一套可以对注册中心进行功能验证、压测、混沌测试的测试平台 sofa-registry-chaos,用来完成门禁系统中高压混沌测试这项,以及变更防御中的采样数据检查。图示是我们一个专门用于注册中心 7*24 测试的压测集群,从中能观察到:
    • 注册中心在无故障期间的推送延迟是否稳定,新版本上线是否造成推送延迟的上涨;
    • 故障发生后数据是否能快速恢复最终一致性;
    • 故障发生期间,容许注册中心数据推送延后一段时间,但不会推少或者推空。


    未来规划

    SOFARegitry v6 还在内部上线阶段,目前还没有开源,未来会持续增强作为服务注册中心方面的能力,比如多语言,多机房等等,同时会开源周边的配套组件和工具,也会更加贴合目前社区云原生的趋势,支持 Kubernetes, Istio 等等,预计功能的排期:

    • 21/07 SOFARegistry v6 版本开源发布;
    • 21/08 支持多语言客户端和多机房数据互通;
    • 21/09 混沌测试平台 sofa-registry-chaos 开源。

     延伸阅读


    本文分享自微信公众号 - 金融级分布式架构(Antfin_SOFA)。
    如有侵权,请联系 support@oschina.cn 删除。
    本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

    展开阅读全文
    加载中

    作者的其它热门文章

    打赏
    0
    0 收藏
    分享
    打赏
    0 评论
    0 收藏
    0
    分享
    返回顶部
    顶部