nacos架构和原理(六)——服务发现模块之注册中心的设计原理
创始人
2025-05-28 20:29:01
0

nacos架构和原理(六)——服务发现模块之注册中心的设计原理

  • 数据模型
  • 数据⼀致性
  • 负载均衡
  • 健康检查
  • 性能与容量
  • 集群扩展性


数据模型

注册中心的核心数据是服务的名字和它对应的网络地址,当服务注册了多个实例时,我们需要对不 健康的实例进行过滤或者针对实例的⼀些特征进行流量的分配,那么就需要在实例上存储⼀些例如 健康状态、权重等属性。随着服务规模的扩大,渐渐的又需要在整个服务级别设定⼀些权限规则、 以及对所有实例都生效的⼀些开关,于是在服务级别又会设立⼀些属性。再往后,我们又发现单个 服务的实例又会有划分为多个子集的需求,例如⼀个服务是多机房部署的,那么可能需要对每个机 房的实例做不同的配置,这样又需要在服务和实例之间再设定⼀个数据级别。

Zookeeper 没有针对服务发现设计数据模型,它的数据是以⼀种更加抽象的树形 K-V 组织的,因 此理论上可以存储任何语义的数据。而 Eureka 或者 Consul 都是做到了实例级别的数据扩展,这 可以满足大部分的场景,不过无法满足大规模和多环境的服务数据存储。Nacos 在经过内部多年生 产经验后提炼出的数据模型,则是⼀种服务-集群-实例的三层模型。如上文所说,这样基本可以满足 服务在所有场景下的数据存储和管理。

在这里插入图片描述
Nacos 的数据模型虽然相对复杂,但是它并不强制你使用它里面的所有数据,在大多数场景下,你 可以选择忽略这些数据属性,此时可以降维成和 Eureka 和 Consul ⼀样的数据模型。

另外⼀个需要考虑的是数据的隔离模型,作为⼀个共享服务型的组件,需要能够在多个用户或者业 务方使用的情况下,保证数据的隔离和安全,这在稍微大⼀点的业务场景中非常常见。另⼀方面服 务注册中心往往会支持云上部署,此时就要求服务注册中心的数据模型能够适配云上的通用模型。 Zookeeper、Consul 和 Eureka 在开源层面都没有很明确的针对服务隔离的模型,Nacos 则在⼀ 开始就考虑到如何让用户能够以多种维度进行数据隔离,同时能够平滑的迁移到阿里云上对应的商 业化产品。

在这里插入图片描述
Nacos 提供了四层的数据逻辑隔离模型,用户账号对应的可能是⼀个企业或者独立的个体,这个数 据⼀般情况下不会透传到服务注册中心。⼀个用户账号可以新建多个命名空间,每个命名空间对应 ⼀个客户端实例,这个命名空间对应的注册中心物理集群是可以根据规则进行路由的,这样可以让 注册中心内部的升级和迁移对用户是无感知的,同时可以根据用户的级别,为用户提供不同服务级 别的物理集群。再往下是服务分组和服务名组成的二维服务标识,可以满足接口级别的服务隔离。

Nacos 1.0.0 介绍的另外⼀个新特性是:临时实例和持久化实例。在定义上区分临时实例和持久化 实例的关键是健康检查的方式。临时实例使用客户端上报模式,而持久化实例使用服务端反向探测 模式。临时实例需要能够自动摘除不健康实例,而且无需持久化存储实例,那么这种实例就适用于 类 Gossip 的协议。右边的持久化实例使用服务端探测的健康检查方式,因为客户端不会上报心跳, 那么自然就不能去自动摘除下线的实例。

在这里插入图片描述
在大中型的公司里,这两种类型的服务往往都有。⼀些基础的组件例如数据库、缓存等,这些往往 不能上报心跳,这种类型的服务在注册时,就需要作为持久化实例注册。而上层的业务服务,例如 微服务或者 Dubbo 服务,服务的 Provider 端支持添加汇报心跳的逻辑,此时就可以使用动态服 务的注册方式。

Nacos 2.0 中继续沿用了持久化及非持久化的设定,但是有了⼀些调整。Nacos 1.0 中持久化及非 持久化的属性是作为实例的⼀个元数据进行存储和识别。这导致同⼀个服务下可以同时存在持久化 实例和非持久化实例。但是在实际使用中,我们发现这种模式会给运维人员带来极大的困惑和运维 复杂度;与此同时,从系统架构来看,⼀个服务同时存在持久化及非持久化实例的场景也是存在⼀ 定矛盾的。这就导致该能力事实上并未被广泛使用。为了简化 Nacos 的服务数据模型,降低运维 人员的复杂度,提升 Nacos 的易用性,在 Nacos2.0 中我们将是否持久化的数据抽象至服务级别, 且不再允许⼀个服务同时存在持久化实例和非持久化实例,实例的持久化属性继承自服务的持久化 属性。

数据⼀致性

数据⼀致性是分布式系统永恒的话题,Paxos 协议的复杂更让数据⼀致性成为程序员大牛们吹水的常见话题。不过从协议层面上看,⼀致性的选型已经很长时间没有新的成员加入了。目前来看基本 可以归为两家:⼀种是基于 Leader 的非对等部署的单点写⼀致性,⼀种是对等部署的多写⼀致性。 当我们选用服务注册中心的时候,并没有⼀种协议能够覆盖所有场景,例如当注册的服务节点不会 定时发送心跳到注册中心时,强⼀致协议看起来是唯⼀的选择,因为无法通过心跳来进行数据的补 偿注册,第⼀次注册就必须保证数据不会丢失。而当客户端会定时发送心跳来汇报健康状态时,第 ⼀次的注册的成功率并不是非常关键(当然也很关键,只是相对来说我们容忍数据的少量写失败), 因为后续还可以通过心跳再把数据补偿上来,此时 Paxos 协议的单点瓶颈就会不太划算了,这也是 Eureka 为什么不采用 Paxos 协议而采用自定义的 Renew 机制的原因。

这两种数据⼀致性协议有各自的使用场景,对服务注册的需求不同,就会导致使用不同的协议。在 这里可以发现,Zookeeper 在 Dubbo 体系下表现出的行为,其实采用 Eureka 的 Renew 机制更 加合适,因为 Dubbo 服务往 Zookeeper 注册的就是临时节点,需要定时发心跳到 Zookeeper 来续约节点,并允许服务下线时,将 Zookeeper 上相应的节点摘除。Zookeeper 使用 ZAB 协议 虽然保证了数据的强⼀致,但是它的机房容灾能力的缺乏,无法适应⼀些大型场景。

Nacos 因为要支持多种服务类型的注册,并能够具有机房容灾、集群扩展等必不可少的能力,在 1.0.0 正式支持 AP 和 CP 两种⼀致性协议并存。1.0.0 重构了数据的读写和同步逻辑,将与业务相 关的 CRUD 与底层的⼀致性同步逻辑进行了分层隔离。然后将业务的读写(主要是写,因为读会直 接使用业务层的缓存)抽象为 Nacos 定义的数据类型,调用⼀致性服务进行数据同步。在决定使 用 CP 还是 AP ⼀致性时,使用⼀个代理,通过可控制的规则进行转发。

目前的⼀致性协议实现,⼀个是基于简化的 Raft 的 CP ⼀致性,⼀个是基于自研协议 Distro 的 AP ⼀致性。Raft 协议不必多言,基于 Leader 进行写入,其 CP 也并不是严格的,只是能保证⼀ 半所见⼀致,以及数据的丢失概率较小。Distro 协议则是参考了内部 ConfigServer 和开源 Eureka, 在不借助第三方存储的情况下,实现基本大同小异。Distro 重点是做了⼀些逻辑的优化和性能的调 优。

在这里插入图片描述

负载均衡

负载均衡严格的来说,并不算是传统注册中心的功能。⼀般来说服务发现的完整流程应该是先从注 册中心获取到服务的实例列表,然后再根据自身的需求,来选择其中的部分实例或者按照⼀定的流 量分配机制来访问不同的服务提供者,因此注册中心本身⼀般不限定服务消费者的访问策略。 Eureka、Zookeeper 包括 Consul,本身都没有去实现可配置及可扩展的负载均衡机制,Eureka 的 负载均衡是由 ribbon 来完成的,而 Consul 则是由 Fabio 做负载均衡。

在这里插入图片描述
在阿里巴巴集团内部,却是使用的相反的思路。服务消费者往往并不关心所访问的服务提供者的负 载均衡,它们只关心以最高效和正确的访问服务提供者的服务。而服务提供者,则非常关注自身被 访问的流量的调配,这其中的第⼀个原因是,阿里巴巴集团内部服务访问流量巨大,稍有不慎就会 导致流量异常压垮服务提供者的服务。因此服务提供者需要能够完全掌控服务的流量调配,并可以 动态调整。

服务端的负载均衡,给服务提供者更强的流量控制权,但是无法满足不同的消费者希望使用不同负 载均衡策略的需求。而不同负载均衡策略的场景,确实是存在的。而客户端的负载均衡则提供了这 种灵活性,并对用户扩展提供更加友好的支持。但是客户端负载均衡策略如果配置不当,可能会导 致服务提供者出现热点,或者压根就拿不到任何服务提供者。

在这里插入图片描述
抛开负载均衡到底是在服务提供者实现还是在服务消费者实现,我们看到目前的负载均衡有基于权 重、服务提供者负载、响应时间、标签等策略。其中 Ribbon 设计的客户端负载均衡机制,主要是 选择合适现有的 IRule、ServerListFilter 等接口实现,或者自己继承这些接口,实现自己的过滤逻 辑。这里 Ribbon 采用的是两步负载均衡,第⼀步是先过滤掉不会采用的服务提供者实例,第二步 是在过滤后的服务提供者实例里,实施负载均衡策略。Ribbon 内置的几种负载均衡策略功能还是比 较强大的,同时又因为允许用户去扩展,这可以说是⼀种比较好的设计。

基于标签的负载均衡策略可以做到非常灵活,Kubernetes 和 Fabio 都已经将标签运用到了对资源 的过滤中,使用标签几乎可以实现任意比例和权重的服务流量调配。但是标签本身需要单独的存储 以及读写功能,不管是放在注册中心本身或者对接第三方的 CMDB。

在 Nacos 0.7.0 版本中,我们除了提供基于健康检查和权重的负载均衡方式外,还新提供了基于第 三方 CMDB 的标签负载均衡器,具体可以参考 CMDB 功能介绍文章(https://mp.weixin.qq.com/s/K_CejD_HqRNthWu88ScIGw?from=timeline&isappinstalled=0)。使用基于标签的负载均衡器, 目前可以实现同标签优先访问的流量调度策略,实际的应用场景中,可以用来实现服务的就近访问, 当您的服务部署在多个地域时,这非常有用。使用这个标签负载均衡器,可以支持非常多的场景, 这不是本文要详细介绍的。虽然目前 Nacos 里支持的标签表达式并不丰富,不过我们会逐步扩展它 支持的语法。除此以外,Nacos 定义了 Selector,作为负载均衡的统⼀抽象。关于 Selector,由于 篇幅关系,我们会有单独的文章进行介绍。

理想的负载均衡实现应该是什么样的呢?不同的人会有不同的答案。Nacos 试图做的是将服务端负 载均衡与客户端负载均衡通过某种机制结合起来,提供用户扩展性,并给予用户充分的自主选择权 和轻便的使用方式。负载均衡是⼀个很大的话题,当我们在关注注册中心提供的负载均衡策略时, 需要注意该注册中心是否有我需要的负载均衡方式,使用方式是否复杂。如果没有,那么是否允许 我方便的扩展来实现我需求的负载均衡策略。

健康检查

Zookeeper 和 Eureka 都实现了⼀种 TTL 的机制,就是如果客户端在⼀定时间内没有向注册中心发 送心跳,则会将这个客户端摘除。Eureka 做的更好的⼀点在于它允许在注册服务的时候,自定义检 查自身状态的健康检查方法。这在服务实例能够保持心跳上报的场景下,是⼀种比较好的体验,在 Dubbo 和 SpringCloud 这两大体系内,也被培养成用户心智上的默认行为。Nacos 也支持这种 TTL 机制,不过这与 ConfigServer 在阿里巴巴内部的机制又有⼀些区别。Nacos 目前支持临时实 例使用心跳上报方式维持活性,发送心跳的周期默认是 5 秒,Nacos 服务端会在 15 秒没收到心 跳后将实例设置为不健康,在 30 秒没收到心跳时将这个临时实例摘除。

不过正如前文所说,有⼀些服务无法上报心跳,但是可以提供⼀个检测接口,由外部去探测。这样 的服务也是广泛存在的,而且以我们的经验,这些服务对服务发现和负载均衡的需求同样强烈。服 务端健康检查最常见的方式是 TCP 端口探测和 HTTP 接口返回码探测,这两种探测方式因为其协 议的通用性可以支持绝大多数的健康检查场景。在其他⼀些特殊的场景中,可能还需要执行特殊的 接口才能判断服务是否可用。例如部署了数据库的主备,数据库的主备可能会在某些情况下切换,需要通过服务名对外提供访问,保证当前访问的库是主库。此时的健康检查接口,可能就是⼀个检 查数据库是否是主库的 MYSQL 命令了。

客户端健康检查和服务端健康检查有⼀些不同的关注点。客户端健康检查主要关注客户端上报心跳 的方式、服务端摘除不健康客户端的机制。而服务端健康检查,则关注探测客户端的方式、灵敏度 及设置客户端健康状态的机制。从实现复杂性来说,服务端探测肯定是要更加复杂的,因为需要服 务端根据注册服务配置的健康检查方式,去执行相应的接口,判断相应的返回结果,并做好重试机 制和线程池的管理。这与客户端探测,只需要等待心跳,然后刷新 TTL 是不⼀样的。同时服务端健 康检查无法摘除不健康实例,这意味着只要注册过的服务实例,如果不调用接口主动注销,这些服 务实例都需要去维持健康检查的探测任务,而客户端则可以随时摘除不健康实例,减轻服务端的压 力。

在这里插入图片描述
Nacos 既支持客户端的健康检查,也支持服务端的健康检查,同⼀个服务可以切换健康检查模式。 我们认为这种健康检查方式的多样性非常重要,这样可以支持各种类型的服务,让这些服务都可以 使用到 Nacos 的负载均衡能力。Nacos 下⼀步要做的是实现健康检查方式的用户扩展机制,不管是服务端探测还是客户端探测。这样可以支持用户传入⼀条业务语义的请求,然后由 Nacos 去执 行,做到健康检查的定制。

性能与容量

虽然大部分用户用到的性能不高,但是他们仍然希望选用的产品的性能越高越好。影响读写性能的 因素很多:⼀致性协议、机器的配置、集群的规模、存量数据的规模、数据结构及读写逻辑的设计 等等。在服务发现的场景中,我们认为读写性能都是非常关键的,但是并非性能越高就越好,因为 追求性能往往需要其他方面做出牺牲。Zookeeper 在写性能上似乎能达到上万的 TPS,这得益于 Zookeeper 精巧的设计,不过这显然是因为有⼀系列的前提存在。首先 Zookeeper 的写逻辑就是 进行 K-V 的写入,内部没有聚合;其次 Zookeeper 舍弃了服务发现的基本功能如健康检查、友好 的查询接口,它在支持这些功能的时候,显然需要增加⼀些逻辑,甚至弃用现有的数据结构;最后, Paxos 协议本身就限制了 Zookeeper 集群的规模,3、5 个节点是不能应对大规模的服务订阅和查 询的。

在对容量的评估时,不仅要针对企业现有的服务规模进行评估,也要对未来 3 到 5 年的扩展规模 进行预测。阿里巴巴的中间件在内部支撑着集团百万级别服务实例,在容量上遇到的挑战可以说不 会小于任何互联网公司。这个容量不仅仅意味着整体注册的实例数,也同时包含单个服务的实例数、 整体的订阅者的数目以及查询的 QPS 等。Nacos 在内部淘汰 Zookeeper 和 Eureka 的过程中, 容量是⼀个非常重要的因素。

Zookeeper 的容量,从存储节点数来说,可以达到百万级别。不过如上面所说,这并不代表容量的 全部,当大量的实例上下线时,Zookeeper 的表现并不稳定,同时在推送机制上的缺陷,会引起客 户端的资源占用上升,从而性能急剧下降。

Eureka 在服务实例规模在 5000 左右的时候,就已经出现服务不可用的问题,甚至在压测的过程中, 如果并发的线程数过高,就会造成 Eureka crash。不过如果服务规模在 1000 上下,几乎目前所有 的注册中心都可以满足。毕竟我们看到 Eureka 作为 SpringCloud 的注册中心,在国内也没有看到 很广泛的对于容量或者性能的问题报告。

Nacos 在开源版本中,服务实例注册的支撑量约为 100 万,服务的数量可以达到 10 万以上。在 实际的部署环境中,这个数字还会因为机器、网络的配置与 JVM 参数的不同,可能会有所差别。 图 9 展示了 Nacos 在使用 1.0.0 版本进行压力测试后的结果总结,针对容量、并发、扩展性和延时 等进行了测试和统计。

在这里插入图片描述
完整的测试报告可以参考 Nacos 官网:
https://nacos.io/en-us/docs/nacos-naming-benchmark.html
https://nacos.io/en-us/docs/nacos-config-benchmark.html

集群扩展性

集群扩展性和集群容量以及读写性能关系紧密。当使用⼀个比较小的集群规模就可以支撑远高于现 有数量的服务注册及访问时,集群的扩展能力暂时就不会那么重要。从协议的层面上来说,Zookee per 使用的 ZAB 协议,由于是单点写,在集群扩展性上不具备优势。Eureka 在协议上来说理论上 可以扩展到很大规模,因为都是点对点的数据同步,但是从我们对 Eureka 的运维经验来看, Eureka 集群在扩容之后,性能上有很大问题。

集群扩展性的另⼀个方面是多地域部署和容灾的支持。当讲究集群的高可用和稳定性以及网络上的 跨地域延迟要求能够在每个地域都部署集群的时候,我们现有的方案有多机房容灾、异地多活、多 数据中心等。

在这里插入图片描述

首先是双机房容灾,基于 Leader 写的协议不做改造是无法支持的,这意味着 Zookeeper 不能在 没有人工干预的情况下做到双机房容灾。在单机房断网情况下,使机房内服务可用并不难,难的是 如何在断网恢复后做数据聚合,Zookeeper 的单点写模式就会有断网恢复后的数据对账问题。Eure ka 的部署模式天然支持多机房容灾,因为 Eureka 采用的是纯临时实例的注册模式:不持久化、所 有数据都可以通过客户端心跳上报进行补偿。上面说到,临时实例和持久化实例都有它的应用场景, 为了能够兼容这两种场景,Nacos 支持两种模式的部署,⼀种是和 Eureka ⼀样的 AP 协议的部署, 这种模式只支持临时实例,可以完美替代当前的 Zookeeper、Eureka,并支持机房容灾。另⼀种是 支持持久化实例的 CP 模式,这种情况下不支持双机房容灾。

在谈到异地多活时,很巧的是,很多业务组件的异地多活正是依靠服务注册中心和配置中心来实现 的,这其中包含流量的调度和集群的访问规则的修改等。机房容灾是异地多活的⼀部分,但是要让 业务能够在访问服务注册中心时,动态调整访问的集群节点,这需要第三方的组件来做路由。异地 多活往往是⼀个包含所有产品线的总体方案,很难说单个产品是否支持异地多活。

在谈到异地多活时,很巧的是,很多业务组件的异地多活正是依靠服务注册中心和配置中心来实现 的,这其中包含流量的调度和集群的访问规则的修改等。机房容灾是异地多活的⼀部分,但是要让 业务能够在访问服务注册中心时,动态调整访问的集群节点,这需要第三方的组件来做路由。异地 多活往往是⼀个包含所有产品线的总体方案,很难说单个产品是否支持异地多活。

在这里插入图片描述

相关内容

热门资讯

保存时出现了1个错误,导致这篇... 当保存文章时出现错误时,可以通过以下步骤解决问题:查看错误信息:查看错误提示信息可以帮助我们了解具体...
汇川伺服电机位置控制模式参数配... 1. 基本控制参数设置 1)设置位置控制模式   2)绝对值位置线性模...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
本地主机上的图像未显示 问题描述:在本地主机上显示图像时,图像未能正常显示。解决方法:以下是一些可能的解决方法,具体取决于问...
不一致的条件格式 要解决不一致的条件格式问题,可以按照以下步骤进行:确定条件格式的规则:首先,需要明确条件格式的规则是...
表格列调整大小出现问题 问题描述:表格列调整大小出现问题,无法正常调整列宽。解决方法:检查表格的布局方式是否正确。确保表格使...
表格中数据未显示 当表格中的数据未显示时,可能是由于以下几个原因导致的:HTML代码问题:检查表格的HTML代码是否正...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...