海量数据下的注册中心 - SOFARegistry 架构介绍

简介:SOFARegistry 是蚂蚁金服开源的具有承载海量服务注册和订阅能力的、高可用的服务注册中心,最早源自于淘宝的初版 ConfigServer,在支付宝/蚂蚁金服的业务发展驱动下,近十年间已经演进至第五代。

SOFAStack

ScalableOpenFinancialArchitecture Stack 是蚂蚁金服自主研发的金融级分布式架构,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。

SOFARegistry 是蚂蚁金服开源的具有承载海量服务注册和订阅能力的、高可用的服务注册中心,最早源自于淘宝的初版 ConfigServer,在支付宝/蚂蚁金服的业务发展驱动下,近十年间已经演进至第五代。

GitHub 地址:https://github.com/sofastack/sofa-registry 

3 月 31 日,蚂蚁金服正式开源了内部演进了近 10 年的注册中心产品-SOFARegistry。先前的文章介绍了 SOFARegistry 的演进之路,而本文主要对 SOFARegistry 整体架构设计进行剖析,并着重介绍一些关键的设计特点,期望能帮助读者对 SOFARegistry 有更直接的认识。

服务注册中心是什么

不可免俗地,先介绍一下服务注册中心的概念。对此概念已经了解的读者,可选择跳过本节。

海量数据下的注册中心 - SOFARegistry 架构介绍_第1张图片

如上图,服务注册中心最常见的应用场景是用于 RPC 调用的服务寻址,在 RPC 远程过程调用中,存在 2 个角色,一个服务发布者(Publisher)、另一个是服务订阅者(Subscriber)。Publisher 需要把服务注册到服务注册中心(Registry),发布的内容通常是该 Publisher 的 IP 地址、端口、调用方式 (协议、序列化方式)等。而 Subscriber 在第一次调用服务时,会通过 Registry 找到相应的服务的 IP 地址列表,通过负载均衡算法从 IP 列表中取一个服务提供者的服务器调用服务。同时 Subscriber 会将 Publisher 的服务列表数据缓存到本地,供后续使用。当 Subscriber 后续再调用 Publisher 时,优先使用缓存的地址列表,不需要再去请求Registry。

海量数据下的注册中心 - SOFARegistry 架构介绍_第2张图片

如上图,Subscriber 还需要能感知到 Publisher 的动态变化。比如当有 Publisher 服务下线时, Registry 会将其摘除,随后 Subscriber 感知到新的服务地址列表后,不会再调用该已下线的 Publisher。同理,当有新的 Publisher 上线时,Subscriber 也会感知到这个新的 Publisher。

初步认识

海量数据下的注册中心 - SOFARegistry 架构介绍_第3张图片

在理解了常见的服务注册中心的概念之后,我们来看看蚂蚁金服的 SOFARegistry 长什么样子。如上图,SOFARegistry 包含 4 个角色:

Client

提供应用接入服务注册中心的基本 API 能力,应用系统通过依赖客户端 JAR 包,通过编程方式调用服务注册中心的服务订阅和服务发布能力。

SessionServer

会话服务器,负责接受 Client 的服务发布和服务订阅请求,并作为一个中间层将写操作转发 DataServer 层。SessionServer 这一层可随业务机器数的规模的增长而扩容。

DataServer

数据服务器,负责存储具体的服务数据,数据按 dataInfoId 进行一致性 Hash 分片存储,支持多副本备份,保证数据高可用。这一层可随服务数据量的规模的增长而扩容。

MetaServer

元数据服务器,负责维护集群 SessionServer 和 DataServer 的一致列表,作为 SOFARegistry 集群内部的地址发现服务,在 SessionServer 或 DataServer 节点变更时可以通知到整个集群。

产品特点

海量数据下的注册中心 - SOFARegistry 架构介绍_第4张图片

(图片改编自https://luyiisme.github.io/2017/04/22/spring-cloud-service-discovery-products)

首先放一张常见的服务注册中心的特性对比,可以看出,在这些 Feature 方面,SOFARegistry 并不占任何优势。那么,我们为什么还开源这样的一个系统?SOFARegistry 开源的优势是什么?下面将着重介绍 SOFARegistry 的特点。

支持海量数据

大部分的服务注册中心系统,每台服务器都是存储着全量的服务注册数据,服务器之间依靠一致性协议(如 Paxos/Raft/2PC 等)实现数据的复制,或者只保证最终一致性的异步数据复制。“每台服务器都存储着全量的服务注册数据”,在一般规模下是没问题的。但是在蚂蚁金服庞大的业务规模下,服务注册的数据总量早就超过了单台服务器的容量瓶颈。

SOFARegistry 基于一致性 Hash 做了数据分片,每台 DataServer 只存储一部分的分片数据,随数据规模的增长,只要扩容 DataServer 服务器即可。这是相对服务发现领域的其他竞品来说最大的特点,详细介绍见后面《如何支持海量数据》一节。

支持海量客户端

SOFARegistry 集群内部使用分层的架构,分别为连接会话层(SessionServer)和数据存储层(DataServer)。SessionServer 功能很纯粹,只负责跟 Client 打交道,SessionServer 之间没有任何通信或数据复制,所以随着业务规模(即 Client 数量)的增长,SessionServer 可以很轻量地扩容,不会对集群造成额外负担。

相比之下,其他大多数的服务发现组件,如 eureka,每台服务器都存储着全量的数据,依靠 eurekaServer 之间的数据复制来传播到整个集群,所以每扩容 1 台 eurekaServer,集群内部相互复制的数据量就会增多一份。再如 Zookeeper 和 Etcd 等强一致性的系统,它们的复制协议(Zab/Raft)要求所有写操作被复制到大多数服务器后才能返回成功,那么增加服务器还会影响写操作的效率。

秒级的服务上下线通知

对于服务的上下线变化,SOFARegistry 使用推送机制,快速地实现端到端的传达。详细介绍见后面《秒级服务上下线通知》一节。

接下来,我将围绕这些特点,讲解 SOFARegistry 的关键架构设计原理。

高可用

各个角色都有 failover 机制:

MetaServer 集群部署,内部基于 Raft 协议选举和复制,只要不超过 1/2 节点宕机,就可以对外服务。

DataServer 集群部署,基于一致性 Hash 承担不同的数据分片,数据分片拥有多个副本,一个主副本和多个备副本。如果 DataServer 宕机,MetaServer 能感知,并通知所有 DataServer 和 SessionServer,数据分片可 failover 到其他副本,同时 DataServer 集群内部会进行分片数据的迁移。

SessionServer 集群部署,任何一台 SessionServer 宕机时 Client 会自动 failover 到其他 SessionServer,并且 Client 会拿到最新的 SessionServer 列表,后续不会再连接这台宕机的 SessionServer。

数据模型

模型介绍

海量数据下的注册中心 - SOFARegistry 架构介绍_第5张图片

注意:这里只列出核心的模型和字段,实际的代码中不止这些字段,但对于读者来说,只要理解上述模型即可。

服务发布模型(PublisherRegister)

dataInfoId:服务唯一标识,由、<分组 group>和<租户 instanceId>构成,例如在 SOFARPC 的场景下,一个 dataInfoId 通常是 _com.sofastack.sofa.rpc.example.HelloService#@#SOFA#@#00001_,其中SOFA 是 group 名称,00001 是租户 id。group 和 instance 主要是方便对服务数据做逻辑上的切分,使不同 group 和 instance 的服务数据在逻辑上完全独立。模型里有 group 和 instanceId 字段,但这里不额外列出来,读者只要理解 dataInfoId 的含义即可。

zone:是一种单元化架构下的概念,代表一个机房内的逻辑单元,通常一个物理机房(Datacenter)包含多个逻辑单元(zone),更多内容可参考异地多活单元化架构解决方案。在服务发现场景下,发布服务时需指定逻辑单元(zone),而订阅服务者可以订阅逻辑单元(zone)维度的服务数据,也可以订阅物理机房(datacenter)维度的服务数据,即订阅该 datacenter 下的所有 zone 的服务数据。

dataList:服务注册数据,通常包含“协议”、“地址”和“额外的配置参数”,例如 SOFARPC 所发布的数据类似“_bolt://192.168.1.100:8080?timeout=2000_”。这里使用 dataList,表示一个 PublisherRegister 可以允许同时发布多个服务数据(但是通常我们只会发布一个)。

服务订阅模型(SubscriberRegister)

dataInfoId:服务唯一标识,上面已经解释过了。

scope: 订阅维度,共有 3 种订阅维度:zone、dataCenter 和 global。zone 和 datacenter 的意义,在上述有关“zone”的介绍里已经解释。global 维度涉及到机房间数据同步的特性,目前暂未开源。

关于“zone”和“scope”的概念理解,这里再举个例子。如下图所示,物理机房内有 ZoneA 和 ZoneB 两个单元,PublisherA 处于 ZoneA 里,所以发布服务时指定了 zone=ZoneA,PublisherB 处于 ZoneB 里,所以发布服务时指定了 zone=ZoneB;此时 Subscriber 订阅时指定了 scope=datacenter 级别,因此它可以获取到 PublisherA 和 PublisherB (如果 Subscriber 订阅时指定了 scope=zone 级别,那么它只能获取到 PublisherA)。

海量数据下的注册中心 - SOFARegistry 架构介绍_第6张图片

服务注册和订阅的示例代码如下 (详细可参看官网的《客户端使用》文档):

// 构造发布者注册表,主要是指定dataInfoId和zone

PublisherRegistration registration = new PublisherRegistration("com.sofastack.test.demo.service");

registration.setZone("ZoneA");

// 发布服务数据,dataList内容是 "10.10.1.1:12200?xx=yy",即只有一个服务数据

registryClient.register(registration, "10.10.1.1:12200?xx=yy");

发布服务数据的代码示例

// 构造订阅者,主要是指定dataInfoId,并实现回调接口

SubscriberRegistration registration = new SubscriberRegistration("com.sofastack.test.demo.service",

                (dataId, userData) -> System.out

                        .println("receive data success, dataId: " + dataId + ", data: " + userData));

// 设置订阅维度,ScopeEnum 共有三种级别 zone, dataCenter, global

registration.setScopeEnum(ScopeEnum.dataCenter);

// 将注册表注册进客户端并订阅数据,订阅到的数据会以回调的方式通知

registryClient.register(registration);

订阅服务数据的代码示例

SOFARegistry 服务端在接收到“服务发布(PublisherRegister)”和“服务订阅(SubscriberRegister)”之后,在内部会汇总成这样的一个逻辑视图。

海量数据下的注册中心 - SOFARegistry 架构介绍_第7张图片

注意,这个树形图只是逻辑上存在,实际物理上 publisherList 和 subscriberList 并不是在同一台服务器上,publisherList 是存储在 DataServer 里,subscriberList 是存储在 SessionServer 里。

业界产品对比

可以看出来,SOFARegistry 的模型是非常简单的,大部分服务注册中心产品的模型也就这么简单。比如 eureka 的核心模型是应用(Application)和实例(InstanceInfo),如下图,1 个 Application 可以包含多个 InstanceInfo。eureka 和 SOFARegistry 在模型上的主要区别是,eureka 在语义上是以应用(Application)粒度来定义服务的,而SOFARegistry 则是以 dataInfoId 为粒度,由于 dataInfoId 实际上没有强语义,粗粒度的话可以作为应用来使用,细粒度的话则可以作为 service 来使用。基于以上区别,SOFARegistry 能支持以接口为粒度的 SOFARPC 和 Dubbo,也支持以应用为粒度的 SpringCloud,而 eureka 由于主要面向应用粒度,因此最多的场景是在springCloud 中使用,而 Dubbo 和 SOFAPRC 目前均未支持 eureka。

另外,eureka 不支持 watch 机制(只能定期 fetch),因此不需要像 SOFARegistry 这样的 Subscriber 模型。

海量数据下的注册中心 - SOFARegistry 架构介绍_第8张图片

(图片摘自https://www.jianshu.com/p/0356b7e9bc42)

最后再展示一下 SOFARPC 基于 Zookeeper 作为服务注册中心时,在 Zookeeper 中的数据结构(如下图),Provider/Consumer 和 SOFARegistry 的 Publisher/Subscriber 类似,最大的区别是 SOFARegistry 在订阅的维度上支持 scope(zone/datacenter),即订阅范围。

海量数据下的注册中心 - SOFARegistry 架构介绍_第9张图片

如何支持海量客户端

从前面的架构介绍中我们知道,SOFARegistry 存在数据服务器(DataServer)和会话服务器(SessionServer)这 2 个角色。为了突破单机容量瓶颈,DataServer 基于一致性 Hash 存储着不同的数据分片,从而能支持蚂蚁金服海量的服务数据,这是易于理解的。但 SessionServer 存在的意义是什么?我们先来看看,如果没有SessionServer的话,SOFARegistry 的架构长什么样子:

海量数据下的注册中心 - SOFARegistry 架构介绍_第10张图片

如上图,所有 Client 在注册和订阅数据时,根据 dataInfoId 做一致性 Hash,计算出应该访问哪一台 DataServer,然后与该 DataServer 建立长连接。由于每个 Client 通常都会注册和订阅比较多的 dataInfoId 数据,因此我们可以预见每个 Client 均会与好几台 DataServer 建立连接。这个架构存在的问题是:“每台 DataServer 承载的连接数会随 Client 数量的增长而增长,每台 Client 极端的情况下需要与每台 DataServer 都建连,因此通过 DataServer 的扩容并不能线性的分摊 Client 连接数”。

讲到这里读者们可能会想到,基于数据分片存储的系统有很多,比如 Memcached、Dynamo、Cassandra、Tair 等,这些系统都也是类似上述的架构,它们是怎么考虑连接数问题的?其实业界也给出了答案,比如 twemproxy,twitter 开发的一个 memcached/redis 的分片代理,目的是将分片逻辑放到 twemproxy 这一层,所有 Client 都直接和 twemproxy 连接,而 twemproxy 负责对接所有的 memcached/Redis,从而减少 Client 直接对memcached/redis 的连接数。twemproxy 官网也强调了这一点:“It was built primarily to reduce the number of connections to the caching servers on the backend”,如下图,展示的是基于 twemproxy 的 redis 集群部署架构。类似 twemproxy 的还有 codis,关于 twemproxy 和 codis 的区别,主要是分片机制不一样,下节会再谈及。

海量数据下的注册中心 - SOFARegistry 架构介绍_第11张图片

(图片摘自http://www.hanzhicaoa.com/1.php?s=twemproxy redis)

当然也有一些分布式 KV 存储系统,没有任何连接代理层。比如 Tair (Alibaba 开源的分布式 KV 存储系统),只有 Client、DataServer、ConfigServer 这 3 个角色,Client 直接根据数据分片连接多台 DataServer。但蚂蚁金服内部在使用 Tair 时本身会按业务功能垂直划分出不同的 Tair 集群,所部署的机器配置也比较高,而且 Tair 的 Client 与 data server 的长连接通常在空闲一段时间后会关闭,这些都有助于缓解连接数的问题。当然,即便如此,Tair 的运维团队也在时刻监控着连接数的总量。

经过上面的分析,我们明白了为数据分片层(DataServer)专门设计一个连接代理层的重要性,所以 SOFARegistry 就有了 SessionServer 这一层。如图,随着 Client 数量的增长,可以通过扩容 SessionServer 就解决了单机的连接数瓶颈问题。

海量数据下的注册中心 - SOFARegistry 架构介绍_第12张图片

如何支持海量数据

面对海量数据,想突破单机的存储瓶颈,唯一的办法是将数据分片,接下来将介绍常见的有 2 种数据分片方式。

传统的一致性 Hash 分片

传统的一致性 Hash 算法,每台服务器被虚拟成 N 个节点,如下图所示(简单起见虚拟份数 N 设为 2 )。每个数据根据 Hash 算法计算出一个值,落到环上后顺时针命中的第一个虚拟节点,即负责存储该数据。业界使用一致性 Hash 的代表项目有 Memcached、Twemproxy 等。

想看完整文章内容:点击这里

原文出处:阿里云大学开发者社区

文中涉及到的相关链接

SOFARegistry:https://github.com/sofastack/sofa-registry 

蚂蚁金服开源服务注册中心 SOFARegistry | SOFA 开源一周年献礼:https://mp.weixin.qq.com/s/NYq2iMU_GlP-4DnK7C8ynQ

异地多活单元化架构解决方案:https://tech.antfin.com/solutions/multiregionldc

Tair:https://github.com/alibaba/tair

SOFABolt:https://github.com/sofastack/sofa-bolt

欢迎加入,参与 SOFARegistry 源码解析 

海量数据下的注册中心 - SOFARegistry 架构介绍_第13张图片

本文为 SOFARegistry 的架构介绍,希望大家对 SOFARegistry 有一个初步的认识和了解。同时,我们开启了《剖析 | SOFARegistry  实现原理》系列,会逐步详细介绍各个部分的代码设计和实现

如果有同学对以上某个主题特别感兴趣的,可以留言讨论,我们会适当根据大家的反馈调整文章的顺序,谢谢大家关注 SOFA ,关注 SOFARegistry,我们会一直与大家一起成长。

欢迎提交 issue 和 PR:

SOFARegistry:https://github.com/sofastack/sofa-registry 

你可能感兴趣的:(海量数据下的注册中心 - SOFARegistry 架构介绍)