蚂蚁金服服务注册中心 SOFARegistry 解析 | 服务发现优化之路

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

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

本文为《剖析 | SOFARegistry 框架》第二篇,本篇做者尚彧,是 SOFARegistry 开源负责人。《剖析 | SOFARegistry 框架》系列由 SOFA 团队和源码爱好者们出品,项目代号:SOFA:RegistryLab/,文末附共建列表,欢迎领取共建~算法

GitHub 地址:github.com/sofastack/s…缓存

概述

不管传统的 SOA 仍是目前的微服务架构,都离不开分布式的特性,既然服务是分布的就必须解决服务寻址的问题。服务注册中心是这个过程最主要的组件,经过服务注册和服务发现特性收集服务供求关系,解耦服务消费方对服务提供方的服务定位问题。性能优化

服务注册中心的最主要能力是服务注册和服务发现两个过程。服务注册的过程最重要是对服务发布的信息进行存储,服务发现的过程是把服务发布端的全部变化(包括节点变化和服务信息变化)及时准确的通知到订阅方的过程。服务器

本文详细描述服务注册中心 SOFARegistry 对于服务发现的实现和技术演进过程,主要涉及 SOFARegistry 的服务发现实现模式以及服务数据变化后及时推送到海量客户端感知的优化过程。网络

服务发现分类

分布式理论最重要的一个理论是 CAP 原理。关于注册中心的解决方案,根据存储数据一致性维度划分业界有不少实现,好比最有表明性的强一致性 CP 系统 ZooKeeper 和最终一致性 AP 系统 Eureka。SOFARegistry 在数据存储层面采用了相似 Eureka 的最终一致性的过程,可是存储内容上和 Eureka 在每一个节点存储相同内容特性不一样,采用每一个节点上的内容按照一致性 Hash 数据分片来达到数据容量无限水平扩展能力。架构

服务端发现和客户端发现

抛开数据存储的一致性,咱们从服务发现的实现维度考虑服务注册中心的分类,业界也按照服务地址选择发生主体和负载均衡策略实现主体分为客户端服务发现和服务端服务发现。并发

  • 客户端服务发现:即由客户端负责决定可用的服务实例的"位置"以及与其相关的负载均衡策略,就是服务发现的地址列表在客户端缓存后由客户端本身根据负载均衡策略进行选址完成最终调用,地址列表按期进行刷新或服务端主动通知变动。最主要的缺点是须要有客户端实现,对于各类异构系统不一样语言不一样结构的实现必需要进行对应的客户端开发,不够灵活,成本较高。

客户端服务发现

  • 服务端服务发现:在服务端引入了专门的负载均衡层,将客户端与服务发现相关的逻辑搬离到了负载均衡层来作。客户端全部的请求只会经过负载均衡模块,其并不须要知会微服务实例在哪里,地址是多少。负载均衡模块会查询服务注册中心,并将客户端的请求路由到相关可用的微服务实例上。这样能够解决大量不一样实现应用对客户端的依赖,只要对服务端的负载均衡模块发请求就能够了,由负载均衡层获取服务发现的地址列表并最终肯定目标地址进行调用。

服务端服务发现

  • SOFARegistry 服务发现模式:以客户端服务发现模式为主。这样的模式实现比较直接,由于在同一个公司内部实践面对的绝大多数应用基本上都是同一个语言实现的,客户端实现也只须要肯定一套,每一个客户端经过业务内嵌依赖方式部署,而且能够根据业务需求进行定制负载均衡策略进行选址调用。固然也会遇到特殊的异构系统,这个随着微服务架构 RPC 调用等通讯能力下沉到 Mesh 执行也获得解决,能够在 Mesh 层进行特定的服务注册中心客户端嵌入,选择路由都在这里统一进行,对不一样语言实现的系统进行无感知。

SOFARegistry 服务发现模式

服务发现的推、拉模型

服务发现最重要的过程是获取服务发布方地址列表的过程,这个过程能够分为两种实现模式:客户端主动获取的拉模式和服务端主动变动通知的推送模式:负载均衡

  • 拉模式主要是在客户端按照订阅关系发起主动拉取过程。客户端在首次订阅能够进行一次相关服务 ID 的服务列表查询,并拉取到本地缓存,后续经过长轮询按期进行服务端服务 ID 的版本变动检测,若是有新版本变动则及时拉取更新本地缓存达到和服务端一致。这种模式在服务端能够不进行订阅关系的存储,只须要存储和更新服务发布数据。由客户端主动发起的数据获取过程,对于客户端实现较重,须要主动获取和定时轮训,服务端只须要关注服务注册信息的变动和健康状况及时更新内存。这个过程因为存在轮训周期,对于时效性要求不高的状况比较适用。

拉模式

  • 推模式主要是从服务端发起的主动变动推送。这个模式主要数据压力集中在服务端,对于服务注册数据的变动和提供方,节点每一次变动状况都须要及时准确的推送到客户端,更新客户端缓存。这个数据推送量较大,在数据发布频繁变动的过程,对于大量订阅方的大量数据推送频繁执行,数据压力巨大,可是数据变动信息及时,对于每次变动都准确反映到客户端。

推模式

  • **SOFARegistry 服务发现模式采用的是推拉结合方式。**客户端订阅信息发布到服务端时能够进行一次地址列表查询,获取到全量数据,而且把对应的服务 ID 版本信息存储在 Session 回话层,后续若是服务端发布数据变动,经过服务 ID 版本变动通知回话层 Session,Session 由于存储客户端订阅关系,了解哪些客户端须要这个服务信息,再根据版本号大小决定是否须要推送给这个版本较旧的订阅者,客户端也经过版本比较肯定是否更新本次推送的结果覆盖内存。此外,为了不某次变动通知获取失败,按期还会进行版本号差别比较,按期去拉取版本低的订阅者所需的数据进行推送保证数据最终一致。

推拉结合方式

SOFARegistry 服务发现模式

数据分层

前面的文章介绍过 SOFARegistry 内部进行了数据分层,在服务注册中心的服务端由于每一个存储节点对应的客户端的连接数据量有限,必须进行特殊的一层划分用于专门收敛无限扩充的客户端链接,而后在透传相应的请求到存储层,这一层是一个无数据状态的代理层,咱们称之为 Session 层。

此外,Session 还承载了服务数据的订阅关系,由于 SOFARegistry 的服务发现须要较高的时效性,对外表现为主动推送变动到客户端,因此推送的主体实现也集中在 Session 层,内部的推拉结合主要是经过 Data 存储层的数据版本变动推送到全部 Session 节点,各个 Session 节点根据存储的订阅关系和首次订阅获取的数据版本信息进行比对,最终肯定推送给那些服务消费方客户端。

数据分层

触发服务推送的场景

直观上服务推送既然是主动的,必然发生在主动获取服务时刻和服务提供方变动时刻:

  • 主动获取:服务订阅信息注册到服务端时,须要查询全部的服务提供方地址,而且须要将查询结果推送到客户端。这个主动查询而且拉取的过程,推送端是一个固定的客户端订阅方,不涉及服务 ID 版本信息断定,直接获取列表进行推送便可,主要发生在订阅方应用刚启动时刻,这个时期可能没有服务发布方发布数据,会查询到空列表给客户端,这个过程基本上相似一个同步过程,体现为客户端一次查询结果的同步返回。
  • 版本变动:为了肯定服务发布数据的变动,咱们对于一个服务不只定义了服务 ID,还对一个服务 ID 定义了对应的版本信息。服务发布数据变动主动通知到 Session 时,Session 对服务 ID 版本变动比较,高版本覆盖低版本数据,而后进行推送。此次推送是比较大面积的推送,由于对于这个服务 ID 感兴趣的全部客户端订阅方都须要推送,而且须要按照不一样订阅维度和不一样类型的客户端进行数据组装,进行推送。这个过程数据量较大,而且须要全部订阅方都推送成功才能更新当前存储服务 ID 版本,须要版本更新确认,因为性能要求必须并发执行而且异步肯定推送成功。
  • 按期轮训:由于有了服务 ID 的版本号,Session 能够按期发起版本号比较,若是Session 存储的的服务 ID 版本号高于dataServer存储的 ,Session再次拉取新版本数据进行推送,这样避免了某次变动通知没有通知到全部订阅方的状况。

服务推送性能优化

服务订阅方的数量决定了数据推送一次的数量,对于一台 Session 机器来讲目前咱们存储 sub 数量达到60w+,若是服务发布方频繁变动,对于每次变动推送量是巨大的,故咱们对整个推送的过程进行优化处理:

  • 服务发布方频繁变动优化:在全部业务集群启动初期,每次对于一个相同的服务,会有不少服务提供方并发不停的新增,若是对于每次新增的提供方都进行一次推送显然不合理,咱们对这个状况进行服务提供方的合并,即每一个服务推送前进行必定延迟等待全部pub新增到必定时间进行一次推送。这个处理极大的减小推送的频率,提高推送效率。

服务发布方频繁变动优化

  • 即便对服务变动进行了合并延迟处理,可是推送任务产生也是巨大的,因此对于瞬间产生的这么大的任务量进行队列缓冲处理是必须的。目前进行全部推送任务会根据服务 ID、推送方 IP 和推送方信息组成惟一任务 ID 进行任务入队处理。队列当中若是是相同的服务变动产生推送任务,则进行任务覆盖,执行最后一次版本变动的任务。此外任务执行进行分批次处理,批次大小能够配置,每一个批次处理完成再获取任务批次进行处理。

队列缓冲处理

异常处理

对于这么大数据量的推送过程必然会由于网络等因素推送失败,对于失败的异常推送场景咱们如何处理:

  • 重试机制:很显然推送失败的客户端订阅依然还在,或者对应的连接还存在,这个失败的推送必须进行重试,重试机制定义十分重要。
    • 目前对于上述首次启动主动获取数据进行推送的重试进行了有限次重试,而且每次重试以前进行网络监测和新版本变动检测,此外进行了时间延迟间隔,保证网络故障重试的成功概率。
    • 这个延迟重试,最初咱们采用简单的 sleep 方式,终止当前线程而后再发起推送请求。这个方式对于资源消耗巨大,若是出现大量的任务重试,会产生大量的线程中止占用内存,同时 sleep 方式对于恢复运行也不是很准确,彻底取决于系统调度时间。后续咱们对重试任务进行时间轮算法分片进行,对于全部重试任务进行了时间片定义,时间轮询执行对应时间片重试任务执行,效率极大提高,而且占用资源很小。

重试机制

  • 补偿措施:对于推送失败以前也说有定时任务进行轮训服务 ID 版本,服务 ID 的版本在全部推送方都接受到这个版本变动推送才进行更新,若是有一个订阅方推送失败,就不更新版本。后续持续检查版本再启动任务,对没有推送成功的订阅方反复执行推送,直到推送成功或者订阅方不存在,这个过程相似于无限重试的过程。

数据处理分阶段

注册中心数据的来源主要来自于两个方向,一个是大量应用客户端新链接上来而且发布和订阅数据并存储在注册中心的阶段,另一个是以前这些发布的服务数据必须按照订阅方的需求推送出去的阶段。这两个阶段数据量都很是巨大,都在首次部署注册中心后发生,若是同时对服务器进行冲击网络和 CPU 都会成为瓶颈,故咱们经过运维模式进行了两个阶段数据的分离处理:

  • 关闭推送开关:咱们在全部注册中心启动初期进行了推送开关关闭的处理,这样在服务注册中心新启动或者新发布初期,由于客户端有本地缓存,在推送关闭的状况下,注册中心的启动只从客户端新注册数据,没有推送新的内容给客户端,作到对现有运行系统最小影响。而且,因为推送关闭,数据只处理新增的内容这样对网络和 CPU 压力减小。
  • 开推送:在关闭推送时刻记录没有推送过的订阅者,全部数据注册完成(主要和发布以前的数据数量比较),没有明显增加状况下,打开推送,对于全部订阅方进行数据推送更新内存。

总结

面对海量的数据进行服务注册和服务推送,SOFARegistry 采用了数据合并、任务合并处理,对于数据注册和数据推送两个大量数据过程进行了分开处理,而且在数据推送失败进行了重试机制优化,以及进行了按期版本号比对机制保证了数据一致性。

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

SOFALab

本文为《剖析 | SOFARegistry  实现原理》第二篇,分享了 SOFARegistry 在面对海量数据处理中的服务优化方式。以后咱们会逐步详细介绍各个部分的代码设计和实现,预计按照以下的目录进行:

  • 【已完成】海量数据下的注册中心 - SOFARegistry 架构介绍
  • 【已完成】SOFARegistry 服务发现优化之路
  • 【已领取】SOFARegistry 如何实现秒级服务上下线通知
  • 【已领取】SOFARegistry MetaServer 功能介绍和实现剖析
  • 【已领取】SOFARegistry 数据分片和同步方案详解
  • 【待领取】SOFARegistry 如何实现 DataServer 平滑扩缩容
  • 【待领取】SOFARegistry 数据推送机制详解

若是有同窗对以上某个主题特别感兴趣的,能够留言讨论,咱们会适当根据你们的反馈调整文章的顺序,谢谢你们关注 SOFAStack ,关注 SOFARegistry,咱们会一直与你们一块儿成长。

领取方式

关注公众号:金融级分布式架构,回复公众号想认领的文章名称,咱们将会主动联系你,确认资质后,便可加入,It's your show time!

除了源码解析,也欢迎提交 issue 和 PR:

SOFARegistry:github.com/sofastack/s…

公众号:金融级分布式架构(Antfin_SOFA)

相关文章
相关标签/搜索