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

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 分享了 SOFARegistry 在面对海量数据处理中的服务优化方式。

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

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


本文为《剖析 | SOFARegistry 框架》第二篇,本篇作者尚彧,是 SOFARegistry 开源负责人。《剖析 | SOFARegistry 框架》系列由 SOFA 团队和源码爱好者们出品

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

概述

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

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

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

服务发现分类

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

[](#c41c6ce0)服务端发现和客户端发现

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

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

image.png

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


image.png

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


image.png

[](#66891e89)服务发现的推、拉模型

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

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

image.png

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

image.png

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

image.png

[](#b39ac758)SOFARegistry 服务发现模式

[](#5021af80)数据分层

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

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


image.png

[](#c9923aea)触发服务推送的场景

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

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

[](#d12df402)服务推送性能优化

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

  • 服务发布方频繁变更优化:在所有业务集群启动初期,每次对于一个相同的服务,会有很多服务提供方并发不停的新增,如果对于每次新增的提供方都进行一次推送显然不合理,我们对这个情况进行服务提供方的合并,即每个服务推送前进行一定延迟等待所有pub新增到一定时间进行一次推送。这个处理极大的减少推送的频率,提升推送效率。

image.png

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

image.png

[](#e449cf10)异常处理

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

  • 重试机制:很显然推送失败的客户端订阅依然还在,或者对应的链接还存在,这个失败的推送必须进行重试,重试机制定义十分重要。

    • 目前对于上述首次启动主动获取数据进行推送的重试进行了有限次重试,并且每次重试之前进行网络监测和新版本变更检测,此外进行了时间延迟间隔,保证网络故障重试的成功几率。
    • 这个延迟重试,最初我们采用简单的 sleep 方式,终止当前线程然后再发起推送请求。这个方式对于资源消耗巨大,如果出现大量的任务重试,会产生大量的线程停止占用内存,同时 sleep 方式对于恢复运行也不是很准确,完全取决于系统调度时间。后续我们对重试任务进行时间轮算法分片进行,对于所有重试任务进行了时间片定义,时间轮询执行对应时间片重试任务执行,效率极大提升,并且占用资源很小。

image.png

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

数据处理分阶段

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

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

[](#b39ac758)总结

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

SOFARegistry 源码解析





本文为《剖析 | SOFARegistry  实现原理》第二篇,分享了 SOFARegistry 在面对海量数据处理中的服务优化方式。


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

欢迎提交 issue 和 PR:
SOFARegistry:https://github.com/sofastack/sofa-registry

相关文章
|
12天前
|
SQL 关系型数据库 MySQL
深入解析MySQL的EXPLAIN:指标详解与索引优化
MySQL 中的 `EXPLAIN` 语句用于分析和优化 SQL 查询,帮助你了解查询优化器的执行计划。本文详细介绍了 `EXPLAIN` 输出的各项指标,如 `id`、`select_type`、`table`、`type`、`key` 等,并提供了如何利用这些指标优化索引结构和 SQL 语句的具体方法。通过实战案例,展示了如何通过创建合适索引和调整查询语句来提升查询性能。
99 9
|
26天前
|
机器学习/深度学习 人工智能 PyTorch
Transformer模型变长序列优化:解析PyTorch上的FlashAttention2与xFormers
本文探讨了Transformer模型中变长输入序列的优化策略,旨在解决深度学习中常见的计算效率问题。文章首先介绍了批处理变长输入的技术挑战,特别是填充方法导致的资源浪费。随后,提出了多种优化技术,包括动态填充、PyTorch NestedTensors、FlashAttention2和XFormers的memory_efficient_attention。这些技术通过减少冗余计算、优化内存管理和改进计算模式,显著提升了模型的性能。实验结果显示,使用FlashAttention2和无填充策略的组合可以将步骤时间减少至323毫秒,相比未优化版本提升了约2.5倍。
43 3
Transformer模型变长序列优化:解析PyTorch上的FlashAttention2与xFormers
|
11天前
|
域名解析 弹性计算 安全
阿里云服务器租用、注册域名、备案及域名解析完整流程参考(图文教程)
对于很多初次建站的用户来说,选购云服务器和注册应及备案和域名解析步骤必须了解的,目前轻量云服务器2核2G68元一年,2核4G4M服务器298元一年,域名注册方面,阿里云推出域名1元购买活动,新用户注册com和cn域名2年首年仅需0元,xyz和top等域名首年仅需1元。对于建站的用户来说,购买完云服务器并注册好域名之后,下一步还需要操作备案和域名绑定。本文为大家展示阿里云服务器的购买流程,域名注册、绑定以及备案的完整流程,全文以图文教程形式为大家展示具体细节及注意事项,以供新手用户参考。
|
23天前
|
前端开发 UED
React 文本区域组件 Textarea:深入解析与优化
本文介绍了 React 中 Textarea 组件的基础用法、常见问题及优化方法,包括状态绑定、初始值设置、样式自定义、性能优化和跨浏览器兼容性处理,并提供了代码案例。
51 8
|
28天前
|
缓存 NoSQL Java
千万级电商线上无阻塞双buffer缓冲优化ID生成机制深度解析
【11月更文挑战第30天】在千万级电商系统中,ID生成机制是核心基础设施之一。一个高效、可靠的ID生成系统对于保障系统的稳定性和性能至关重要。本文将深入探讨一种在千万级电商线上广泛应用的ID生成机制——无阻塞双buffer缓冲优化方案。本文从概述、功能点、背景、业务点、底层原理等多个维度进行解析,并通过Java语言实现多个示例,指出各自实践的优缺点。希望给需要的同学提供一些参考。
47 7
|
26天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
1月前
|
前端开发 Android开发 UED
移动应用与系统:从开发到优化的全面解析####
本文深入探讨了移动应用开发的全过程,从最初的构思到最终的发布,并详细阐述了移动操作系统对应用性能和用户体验的影响。通过分析当前主流移动操作系统的特性及差异,本文旨在为开发者提供一套全面的开发与优化指南,确保应用在不同平台上均能实现最佳表现。 ####
29 0
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
5天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
5天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多