大纲
1.Nacos的定位和优势
2.Nacos的整体架构
3.Nacos的配置模型
4.Nacos内核设计之一致性协议
5.Nacos内核设计之自研Distro协议
6.Nacos内核设计之通信通道
7.Nacos内核设计之寻址机制
8.服务注册发现模块的注册中心的设计原理
9.服务注册发现模块的注册中心的服务数据模型
10.服务注册发现模块的健康检查机制
11.配置管理模块的配置一致性模型
12.Zookeeper、Eureka和Nacos的对比总结
整理于官方文档
1.Nacos的定位和优势
(1)Nacos的定位
(2)Nacos的优势
(3)Nacos生态
(1)Nacos的定位
Nacos是Dynamic Naming and Configuration Service的首字母简称,⼀个更易于构建云原生应用的服务发现和管理和动态配置管理的平台。
(2)Nacos的优势
一.易⽤
简单的数据模型,标准的restfulAPI,易用的控制台,丰富的使用文档。
二.稳定
99.9%高可用,支持具有数百万服务的大规模场景。
三.实时
数据变更毫秒级推送生效。1w级,SLA承诺1w实例上下线1s,99.9%推送完成。10w级,SLA承诺1w实例上下线3s,99.9%推送完成。100w级别,SLA承诺1w实例上下线9s,99.9%推送完成。
四.规模
十万级服务/配置,百万级连接,具备强大扩展性。
(3)Nacos生态
Nacos几乎支持所有主流语言,比如Java/Python/Golang。这三种语言已经支持Nacos 2.0长链接协议,能最大限度发挥Nacos性能。DNS(Dubbo + Nacos + Spring-Cloud-Alibaba/Seata/Sentinel)最佳实践,是Java微服务生态最佳解决方案。
2.Nacos的整体架构
(1)架构图
(2)用户层
(3)业务层
(4)内核层
(5)插件
(1)架构图
整体架构分为用户层、业务层、内核层和插件。用户层主要解决用户使用的易用性问题,业务层主要解决服务发现和配置管理的功能问题,内核层用来解决分布式系统⼀致性、存储、高可用等核心问题,插件用来解决扩展性问题。
(2)用户层
一.OpenAPI
暴露Rest风格的HTTP接口,简单易用和方便集成。
二.Console
易用控制台,用于服务管理、配置管理等操作。
三.SDK
多语言SDK,目前几乎支持所有主流编程语言。
四.Agent
Sidecar模式运行,通过标准DNS协议与业务解耦。
五.CLI
命令行对产品进行轻量化管理,像git⼀样好用。
(3)业务层
一.服务管理
实现服务CRUD、域名CRUD、服务健康状态检查、服务权重管理等功能。
二.配置管理
实现配置CRUD、版本管理、灰度管理、监听管理、推送轨迹等功能。
三.元数据管理
提供元数据CURD和打标能力,为实现上层流量和服务灰度非常关键。
(4)内核层
一.插件机制
实现三个模块可分可合,实现扩展点SPI机制,用于扩展自己公司定制。
二.事件机制
实现异步化事件通知、SDK数据变化异步通知等逻辑,这是Nacos高性能的关键部分。
三.日志模块
管理日志分类、日志级别、日志移植、日志格式、异常码 + 帮助文档。
四.回调机制
SDK通知数据,通过统⼀的模式回调用户处理,接口和数据结构需要具备可扩展性。
五.寻址机制
解决Server IP直连、域名访问、NameServer寻址、广播等多种寻址模式。
六.传输通道
解决Server与存储、Server间、Server与SDK间高效通信问题。
七.容量管理
管理每个租户、分组下的容量,防止存储被写爆,影响服务可用性。
八.流量管控
按照租户、分组等维度管控请求频率、长链接个数、报文大小等。
九.缓存机制
容灾目录、本地缓存、Server缓存机制,是Nacos高可用的关键。
十.启动模式
按照单机模式、配置模式、服务模式、DNS模式,启动不同的模块。
十一.⼀致性协议
解决对于不同数据的不同⼀致性要求的问题。
十二.存储模块
解决数据持久化、非持久化存储,数据分片问题。
(5)插件
一.NameServer
解决Namespace到ClusterID的路由问题。
解决用户环境与Nacos物理环境映射问题。
二.CMDB
解决元数据存储,与三方CMDB系统对接问题。
解决应用、人、资源关系。
三.Metrics
暴露标准Metrics数据,方便与三方监控系统打通。
四.Trace
暴露标准Trace,方便与SLA系统打通。日志白平化 + 推送轨迹能力,方便和计量计费系统打通。
五.接入管理
相当于阿里云开通服务,分配身份、容量、权限。
六.用户管理
解决用户管理、登录、SSO等问题。
七.权限管理
解决身份识别,访问控制,角色管理等问题。
八.审计系统
扩展接口方便与不同公司审计系统打通。
九.通知系统
核心数据变更、操作,方便打通SMS系统,通知对应人数据发生变更。
3.Nacos的配置模型
(1)修改服务配置的背景
(2)Nacos配置管理中的概念
(3)Nacos的配置模型之基础模型
(4)Nacos的配置模型之配置资源模型
(5)Nacos的配置模型之配置存储模型(ER图)
(1)修改服务配置的背景
在单体架构时,我们可以将配置写在配置文件中,但缺点就是每次修改配置都需要重启服务才能生效。
当应用程序实例比较少的时候还可以维护。如果转向微服务架构成百上千实例,每修改⼀次配置要将全部实例重启,这样不仅增加了系统的不稳定性,也提高了维护的成本。
那么如何能够做到服务不重启就可以修改配置?所以就产生了四个基础诉求:
一.需要支持动态修改配置
二.需要实时的动态变更
三.变更太快如何管控变更风险,如灰度、回滚等
四.敏感配置如何做安全配置
(2)Nacos配置管理中的概念
一.配置(Configuration)
开发中通常会将⼀些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在,让系统更好地适配实际的物理运行环境。
配置管理⼀般在系统部署时进行,由系统管理员或运维人员完成该步骤。配置变更是调整系统运行时的行为的有效手段之⼀。
二.配置管理(Configuration Management)
在Nacos中,系统中所有配置的存储、编辑、删除、灰度管理、历史版本管理、变更审计等所有与配置相关的活动统称为配置管理。
三.配置服务(Configuration Service)
在服务或应用运行中,提供动态配置或元数据以及配置管理的服务提供者。
四.配置项(Configuration Item)
⼀个具体的可配置的参数与其值,通常以"key = value"的形式存在。例如我们常配置系统的日志输出级别"logLevel = INFO"就是⼀个配置项。
五.配置集(Configuration Set)
⼀组相关或者不相关的配置项的集合称为配置集。在系统中,⼀个配置文件通常就是⼀个配置集,包含了系统各方面的配置。例如,⼀个配置集可能包含了数据源、线程池、日志级别等配置项。
六.命名空间(Namespace)
用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的Group或Data ID的配置。Namespace的常用场景之⼀是不同环境的配置的区分隔离。例如开发环境和生产环境的资源(如数据库配置、限流阈值)隔离等。如果在没有指定Namespace的情况下,默认使用public命名空间。
七.配置组(Group)
Nacos中的⼀组配置集,是配置的维度之⼀。通过⼀个有意义的字符串(如ABTest中的实验组、对照组)对配置集分组,从而区分Data ID相同的配置集。
当在Nacos上创建⼀个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用DEFAULT_GROUP。
配置分组的常见场景:不同的应用或组件使用了相同的配置项。如database_url配置和MQ_Topic配置。
八.配置ID(Data ID)
Nacos中的某个配置集的ID,配置集ID是划分配置的维度之⼀。Data ID通常用于划分系统的配置集,⼀个系统或应用可包含多个配置集。每个配置集都可被⼀个有意义的名称标识。Data ID尽量保障全局唯⼀,可以参考Nacos Spring Cloud中的命名规则。
九.配置快照(Configuration Snapshot)
Nacos的客户端SDK会在本地生成配置的快照。当客户端无法连接Nacos时,可使用配置快照显示系统的整体容灾能力。配置快照类似于Git中的本地commit,也类似于缓存。配置快照会在适当的时机更新,但是并没有缓存过期(expiration)的概念。
(3)Nacos的配置模型之基础模型
下图是Nacos配置管理的基础模型:
说明一: Nacos提供了可视化的控制台,通过控制台可以发布配置、更新配置、删除配置、灰度发布、版本管理等。
说明二: Client端也可以发布配置、更新配置、监听配置等。
说明三: Client端会通过gRPC长连接监听配置。
说明四: Server端会对比Client端配置的MD5和本地配置的MD5是否相等,如果不相等,那么Server端就会推送配置变更到Client端。
说明五: Client端会保存配置的快照,当服务端出现问题时则从本地获取。
(4)Nacos的配置模型之配置资源模型
Namespace的设计就是用来进行资源隔离的,我们在进行配置资源时可以从以下两个角度来看。
角度一:从单个租户的角度来看
我们要配置多套环境的配置,可以根据不同的环境来创建Namespace。比如有开发环境、测试环境、线上环境,那么就创建对应的dev、test、prod。Nacos会自动生成对应的Namespace Id。如果同⼀个环境内想配置不相同的配置,可以通过Group来区分。
角度二:从多个租户的角度来看
每个租户都可以有自己的命名空间。我们可以为每个用户创建⼀个命名空间,并给用户分配对应的权限。
比如多个租户:zhangsan、lisi、wangwu。每个租户都想有⼀套自己的多环境配置,即每个租户都想配置多套环境。那么可以给每个租户创建⼀个Namespace:zhangsan、lisi、wangwu。同样会生成对应的Namespace Id,然后使用Group区分不同环境的配置。如下图所示:
(5)Nacos的配置模型之配置存储模型(ER图)
Nacos存储配置有几个比较重要的表:
一.config_info表存储配置信息
里面有dataId、groupId、content、tenantId、encryptedDataKey等数据。
二.config_info_beta表存储灰度测试的配置信息
存储的字段内容和config_info基本相似,但有⼀个beta_ips字段是用于客户端请求配置时,判断是否是灰度的IP的。
三.config_tags_relation表存储配置的标签
在发布配置时如果指定了标签,则会把标签和配置的关联信息存储在该表。
四.his_config_info表存储配置的历史信息
对配置进行的发布、更新、删除等操作都会记录⼀条数据,可以根据这个标实现多版本管理和快速回滚。
4.Nacos内核设计之一致性协议
(1)为什么Nacos需要⼀致性协议
(2)为什么Nacos会同时运行CP协议以及AP协议
(3)为什么Nacos选择Raft以及Distro协议
(4)Nacos早期的⼀致性协议
(5)Nacos当前的⼀致性协议
(6)Nacos如何做到⼀致性协议下沉的
(1)为什么Nacos需要⼀致性协议
首先Nacos需要实现在其内部存储数据,从而可以尽可能减少用户部署以及运维成本,只需⼀个程序包就可以快速以单机模式或集群模式启动Nacos。
单机模式下的内部存储很容易实现,使用简单的内嵌关系型数据库即可。集群模式下的内部存储就要考虑各节点间的数据⼀致性以及数据同步。而要解决这个问题,就不得不引入共识算法,通过共识算法来保障各个节点间的数据⼀致性。
(2)为什么Nacos会同时运行CP协议以及AP协议
这要从Nacos的应用场景出发:由于Nacos是⼀个集服务注册发现以及配置管理于⼀体的组件,因此要将集群模式下各节点间的数据⼀致性保障问题拆分成两方面来看。
一.从服务注册发现来看
服务注册发现中心,在当前微服务体系下,是十分重要的组件。服务间为了感知对方服务当前可正常提供服务的实例信息,需要从服务注册发现中心进行获取。
因此对于服务注册发现中心组件的可用性,提出了很高的要求,需要在任何场景下,尽最大可能保证服务注册发现能力可以对外提供服务。
同时Nacos服务注册发现,实现了基于心跳自动完成服务数据补偿的机制。如果节点出现了数据丢失,节点可以通过该机制快速补偿丢失的数据。
因此为了满足服务注册发现中心的可用性,强⼀致性的共识算法就不合适。因为强⼀致性共识算法能否对外提供服务是有要求的。如果当前集群可用的节点数没有过半的话,整个算法直接"罢工"。
而最终⼀致共识算法会更多保障服务的可用性,并且能保证在⼀定的时间内各节点间的数据能达成⼀致。
上述都是针对Nacos服务注册发现中的非持久化服务实例而言,即需要客户端上报心跳进行服务实例续约。即针对此类型的服务数据,选择最终一致共识算法来保障数据的一致性。
而对于Nacos服务发现注册中的持久化服务实例,因为所有数据都是直接调用Nacos服务端时进行创建的,因此需要由Nacos保障数据在各节点间的强⼀致性。故而针对此类型的服务数据,选择强⼀致性共识算法来保障数据的⼀致性。
二.从配置管理来看
由于配置数据是直接在Nacos服务端进行创建并进行管理的,因此必须保证大部分节点都保存了此配置数据才能认为配置被成功保存了。
否则就会丢失配置的变更,如果出现这种情况,问题是很严重的。如果发布重要配置变更时,出现丢失变更的情况,则可能会引起严重故障。
因此对于配置数据的管理,必须要求集群中大部分的节点是强⼀致的,这里只能使用强⼀致性共识算法。
(3)为什么Nacos选择Raft以及Distro协议
对于强⼀致性共识算法,当前工业生产中,最多使用的就是Raft协议。Raft协议更容易让人理解,并且有很多成熟的工业算法实现。比如JRaft、BRaft、Zookeeper的ZAB、Consul的Raft、Apache的Ratis。
因为Nacos是Java技术栈,所以只能在JRaft、ZAB、Apache Ratis中选择。首先,ZAB和Zookeeper强绑定。然后,JRaft支持多RaftGroup,而且可以让Nacos支持多数据分片。所以Nacos最终选择了JRaft这种强一致性协议。
Distro协议是阿里自研的⼀个最终⼀致性协议。最终⼀致性的协议有很多,比如Gossip、Eureka内的数据同步算法。而Distro协议是集Gossip以及Eureka协议的优点并加以优化出来的。
原生的Gossip协议:由于随机选取节点发送消息,所以存在重复发送消息给同⼀节点的问题。从而增加网络传输压力,也给消息节点带来额外处理负载。
Distro协议引入了权威Server的概念:也就是每个节点负责⼀部分数据以及将自己的数据同步给其他节点,从而有效地避免了消息冗余的问题。
(4)Nacos早期的⼀致性协议
先来看早期的Naocs版本的架构:
在Nacos早期的架构中,服务注册发现和配置管理的⼀致性协议是分开的,并没有下沉到Nacos的内核模块作为通用的能力来进行演进。
服务注册发现的⼀致性协议和服务注册发现的业务逻辑强耦合在⼀起,且充斥着服务注册发现的⼀些概念,使服务注册发现的整体逻辑难以维护。
由于服务注册发现的业务逻辑耦合了⼀致性协议层的数据状态,所以难以做到计算存储彻底分离,以及影响了计算层的无限水平扩容能力。
因此为了解决这个问题,必然要对Nacos的⼀致性协议进行抽象以及下沉,使一致性协议成为内核模块的功能,让服务注册发现模块只充当计算角色,同时为配置模块去外部数据库存储打下架构基础。
(5)Nacos当前的⼀致性协议
接着看当前的Nacos版本的架构:
在新的Nacos架构中,已完成了将⼀致性协议从原先的服务注册发现模块下沉到了内核模块中,并且提供了统⼀的抽象接口。使上层的服务注册发现模块和配置管理模块,不再需要耦合任何⼀致性语义。当解耦抽象分层后,每个模块都能快速演进,并且性能和可用性都实现大幅提升。
(6)Nacos如何做到⼀致性协议下沉的
Nacos如何做到将AP、CP协议下沉到内核模块中?
一.对⼀致性协议进行抽象
所谓⼀致性协议,就是用来保证数据的读写是⼀致的。⼀致性协议最基础的两个方法,就是写入数据的方法和读取数据的方法。
java
public interface ConsistencyProtocol<T extends Config, P extends RequestProcessor> extends CommandOperations {
...
//Obtain data according to the request.
//@param request request
//@return data {@link Response}
//@throws Exception {@link Exception}
Response getData(ReadRequest request) throws Exception;
//Data operation, returning submission results synchronously.
//@param request {@link com.alibaba.nacos.consistency.entity.WriteRequest}
//@return submit operation result {@link Response}
//@throws Exception {@link Exception}
Response write(WriteRequest request) throws Exception;
...
}
任何使用⼀致性协议的组件,都只需要使用getData()方法和write()方法即可。同时,⼀致性协议已经被抽象在了consistency的包中。Nacos对于AP、CP的⼀致性协议接口使用抽象都在里面。并且在实现具体的⼀致性协议时,采用了插件可插拔的形式,进⼀步将⼀致性协议的逻辑,从服务注册发现、配置管理模块中解耦。
ini
public class ProtocolManager extends MemberChangeListener implements DisposableBean {
...
private void initAPProtocol() {
ApplicationUtils.getBeanIfExist(APProtocol.class, protocol -> {
Class configType = ClassUtils.resolveGenericType(protocol.getClass());
Config config = (Config) ApplicationUtils.getBean(configType);
injectMembers4AP(config);
protocol.init((config));
ProtocolManager.this.apProtocol = protocol;
});
}
private void initCPProtocol() {
ApplicationUtils.getBeanIfExist(CPProtocol.class, protocol -> {
Class configType = ClassUtils.resolveGenericType(protocol.getClass());
Config config = (Config) ApplicationUtils.getBean(configType);
injectMembers4CP(config);
protocol.init((config));
ProtocolManager.this.cpProtocol = protocol;
});
}
...
}
其实,仅对⼀致性协议进行抽象是不够的。因为此时服务注册发现、配置管理模块,还要依赖⼀致性协议的接口。服务注册发现、配置管理模块依然要在显式处理⼀致性协议的读写逻辑,以及要自己实现⼀个对接⼀致性协议的存储,这并不优雅。
服务发现以及配置模块,应该专注于数据的使用以及计算,而不是专注于数据如何存储、如何保障数据⼀致性。数据存储以及多节点⼀致的问题则应该交由存储层来保证。
为了降低⼀致性协议出现在服务注册发现、配置管理这两个模块中的频次,以及尽可能让⼀致性协议只在内核模块中感知,Nacos做了另⼀个处理------对数据存储进行抽象。
二.对数据存储进行抽象
⼀致性协议,就是用来保证数据⼀致的。
如果利用⼀致性协议实现存储接口,那么服务注册发现模块和配置管理模块,就由原来的依赖⼀致性协议接口转变为依赖存储接口。
而存储接口的具体实现,就比⼀致性协议要丰富得多了。并且这两个模块也无需为直接依赖⼀致性协议而承担多余的编码工作,比如快照处理、状态机实现、数据同步等编码工作,从而使得这两个模块可以更加专注自己的核心逻辑。
对于数据存储的抽象,以服务注册发现模块为例:
java
public interface KvStorage {
enum KvType {
//Local file storage.
File,
//Local memory storage.
Memory,
//LSMTree storage.
LSMTree,
AP,
CP,
}
//获取⼀个数据
byte[] get(byte[] key) throws KvStorageException;
//存入⼀个数据
void put(byte[] key, byte[] value) throws KvStorageException;
//删除⼀个数据
void delete(byte[] key) throws KvStorageException;
...
}
由于服务注册发现模块的存储,更多是根据key去执行点查的操作,因此Key-Value类型的存储接口最适合不过。而Key-Value的存储接口定义好后,其实就是这个KVStore的具体实现了。
可以直接将KVStore的实现对接Redis,也可以直接对接DB。或者直接根据Nacos内核模块的⼀致性协议,在此基础上实现⼀个内存或者持久化的分布式强(弱)⼀致性KV。
通过功能边界将Nacos进程进⼀步分离为计算逻辑层和存储逻辑层,计算层和存储层之间的交互仅通过⼀层薄薄的数据操作胶水代码来处理。这样就在单个Nacos进程里实现了计算和存储逻辑的彻底分离。
同时,基于存储层的插件化设计:如果中小公司有运维成本要求,可以直接使用Nacos自带的内嵌分布式存储组件来部署⼀套Nacos集群。
如果中大型公司的服务实例数据、配置数据的量级很大,并且本身有⼀套比较好的Paas层服务,则完全可以重写已有的存储组件,从而实现Nacos计算层与存储层的彻底分离。
5.Nacos内核设计之自研Distro协议
(1)Distro协议的背景和设计思想
(2)Distro协议的工作原理之数据初始化
(3)Distro协议的工作原理之数据校验
(4)Distro协议的工作原理之写操作
(5)Distro协议的工作原理之读操作
(6)总结
(1)Distro协议的背景和设计思想
一.Distro协议的背景
Distro协议是Nacos自研的⼀种AP分布式协议,也是面向临时实例设计的⼀种分布式协议。它保证了在某些Nacos节点宕机后,整个临时实例处理系统依旧可正常工作。
作为⼀种有状态的中间件应用的内嵌协议,Distro协议保证了各个Nacos节点对于海量注册请求的统⼀协调和存储。
二.Distro协议的设计思想
设计一:Nacos每个节点是平等的
每个节点都会处理写请求,以及都会把新数据同步到其他节点。
设计二:每个节点只负责部分数据
每个节点都会定时发送自己负责数据的校验值到其他节点来保持数据⼀致性。
设计三:每个节点独立处理读请求
所以每个节点都能够及时从本地发出响应。
(2)Distro协议的工作原理之数据初始化
新加入的Nacos节点会拉取全量数据。具体操作就是轮询所有Nacos节点,向其他节点发送请求来拉取全量数据。
完成拉取全量数据后,Nacos的每个节点都拥有了当前所有注册上来的非持久化实例数据。
(3)Distro协议的工作原理之数据校验
在Nacos集群启动后,各节点间会定期发送心跳。心跳信息主要是当前节点上的所有数据的元信息,使用元信息的原因是要保证在网络中传输的数据量维持在⼀个较低水平。
这种数据校验会以心跳的形式进行。即每个节点会在固定时间间隔,向其他节点发起⼀次数据校验请求。在数据校验过程中,一旦某节点发现其他节点上的数据与本地数据不⼀致,则会发起⼀次全量拉取请求,将数据补齐。
(4)Distro协议的工作原理之写操作
对于⼀个已经启动完成的Nacos集群,在⼀次客户端发起写操作的流程中,当注册非持久化的服务实例的写请求发送到某台Nacos节点时,Nacos集群处理的流程如下:
一.前置的Filter拦截写请求
首先,收到写请求的Nacos节点,其前置Filter会拦截写请求。然后根据请求中包含的IP和Port信息计算其所属的Nacos责任节点,并将该请求转发到所属的Nacos责任节点上。
二.Controller会解析写请求
所属的Nacos责任节点收到写请求后,会让其Controller解析写请求。
三.Distro协议下Nacos节点会定时执行同步任务
定时同步任务会将本机所负责的所有实例信息同步到其他节点上。
(5)Distro协议的工作原理之读操作
由于Nacos的每个节点上都存放了全量数据,因此在每⼀次读操作中,Nacos节点会直接从本地拉取数据进行快速响应。
这种机制保证了Distro协议作为⼀种AP协议,能及时响应读操作的请求。在网络分区情况下,对于所有的读操作也能够正常返回。当网络分区恢复时,各Nacos节点都会把各数据分片的数据进行合并恢复。
(6)总结
Distro协议是Nacos对临时服务实例数据开发的⼀致性协议。其数据存储在缓存中,在启动时进行全量数据同步,并定期进行数据校验。在Distro协议的设计思想下,每个节点都可以接收和处理读写请求。
Distro协议的请求场景主要分为如下三种情况:
情况一: 当节点接收到属于自己负责的服务实例的写请求时,直接写入。
情况二: 当节点接收到不属于自己负责的服务实例的写请求时,首先在集群内部路由,然后转发给对应的节点,从而完成写请求。
情况三: 当节点接收到任何读请求时,都直接在本机查询并返回,因为所有服务实例的数据都被同步到了各机器节点上。
Distro协议作为Nacos临时服务实例的⼀致性协议,保证了分布式环境下各节点上的服务信息状态,都能及时通知其他节点。Distro协议可让Nacos维持数十万量级的服务实例数据的存储和⼀致性。
6.Nacos内核设计之通信通道
(1)现状背景
(2)场景分析
(3)长连接核心诉求
(4)长连接选型对比
(1)现状背景
Nacos 1.x版本的Config/Naming模块,各自的推送通道都是按照自己的设计模型来实现的。配置和服务器模块的数据推送通道不统⼀,HTTP短连接性能压力巨大,未来Nacos需构建能同时支持配置及服务的长连接通道,以标准的通信模型重构推送通道。
(2)场景分析
场景一:配置管理
一.Client和Server之间的通信
客户端Client需要感知服务端Server的节点列表,需要按照某种策略选择其中⼀个Server节点进行连接。当底层连接断开时,客户端Client就需要切换Server进行重连。
客户端基于当前可用的长连接,进行配置的查询、发布、删除、监听等RPC语义的接口通信。
服务端需要将配置变更消息推送给当前监听的客户端。当网络不稳定时,客户端接收失败,就需要支持重推并告警。
服务端需要感知客户端连接断开事件,将连接注销,并且清空连接对应的上下文。
二.Server之间的通信
每个Server都需要能够获取集群所有Server的地址列表,然后为每个Server创建独立的长连接。当长连接断开时,需要进行重连。当节点列表变更时,需要创建新节点的长连接和销毁下线节点的长连接。
Server间需要进行数据同步,包括配置信息、当前连接数信息、系统负载信息、负载调节信息等同步。
场景二:服务注册发现
一.Client和Server之间的通信
客户端Client需要感知服务端Server的节点列表,需要按照某种策略选择其中⼀个Server节点进行连接。当底层连接断开时,客户端Client就需要切换Server进行重连。
客户端基于当前可用的长连接,进行服务的查询、注册、注销、订阅等RPC语义的接口通信。
服务端需要将服务变更消息推送新给客户端,完成推送后客户端需要ACK,方便服务端进行metrics统计和重推判定等。
服务端需要感知客户端连接断开事件,将连接注销,并且清空连接对应的上下文。
二.Server之间的通信
服务端之间需要通过长连接感知对端存活状态,需要通过长连接汇报服务状态(同步RPC能力)。
服务端之间进行AP Distro数据同步,需要异步RPC带ACK能力。
(3)长连接核心诉求
一.功能性诉求
二.性能要求
三.负载均衡
四.连接生命周期
一.功能性诉求
客户端的诉求:
诉求一: 实时感知连接生命周期的能力,包括连接建立、连接断开事件。
诉求二: 客户端调用服务端支持同步阻塞、异步Future、异步CallBack三种模式。
诉求三: 底层连接自动切换能力。
诉求四: 响应服务端连接重置消息进行连接切换。
诉求五: 选址/服务发现。
服务端的诉求:
诉求一: 实时感知连接生命周期的能力,包括连接建立、连接断开事件。
诉求二: 服务端往客户端主动进行数据推送,需要客户端进行ACK返回以支持可靠推送,并且需要进行失败重试。
诉求三: 服务端主动推送负载调节能力。
二.性能要求
性能方面,需要能够满足阿里的生产环境可用性要求,能支持百万级的长连接规模及请求量和推送量,并且要保证足够稳定。
三.负载均衡
说明一:常见的负载均衡策略
随机、Hash、轮询、权重、最小连接数、最快响应速度等。
说明二:短连接和长连接负载均衡的异同
由于短连接会快速建立和销毁,所以"随机、Hash、轮询、权重"四种策略下服务端重启也不会影响均衡,而"最小连接数、最快响应速度",如果数据延时则容易造成堆积问题。
长连接建立连接后,如果没有异常情况,连接会⼀直保持。如果出现断连,需要重新选择⼀个新的服务节点。当服务节点出现重启后,最终会出现连接不均衡的情况。"随机、轮询、权重"的策略在客户端重连切换时可以使用。"最小连接数、最快响应速度",同样会出现数据延时造成堆积问题。
说明三:长连接和短连接的差别
在于在整体连接稳定时,服务端需要⼀个重平衡的机制。将集群视角的连接数重新洗牌分配,趋向另外⼀种稳态。
说明四:客户端随机 + 服务端柔性调整
核心的策略是客户端 + 服务端双向调节策略,也就是客户端随机选择 + 服务端运行时柔性调整。
说明五:什么是客户端随机选择
客户端在启动时获取服务列表,按照随机规则进行节点选择。逻辑比较简单,整体能够保持随机。
说明六:什么是服务端柔性调整
(当前实现版本)人工管控方案:集群视角的系统负载控制台,提供连接数、负载等视图,实现人工调节每个Server节点的连接数,人工触发重平衡,人工削峰填谷。比如展示总节点数量,总长链接数量、平均数量、系统负载信息、每个节点的地址、长链接数量、与平均数量的差值、对高于平均值的节点进行数量调控、设置数量上限(临时和持久化),并且可以指定服务节点进行切换。
(未来终态版本)自动化管控方案:基于每个Server间连接数及负载自动计算节点合理连接数,自动触发Reblance、自动削峰填谷,实现周期长,比较依赖算法准确性。
四.连接生命周期
Nacos需要什么:
第一:低成本快速感知
客户端要在服务端不可用时尽快切换到新的服务节点,降低不可用时间。并且客户端要能够感知底层连接切换事件,重置上下文。服务端需要在客户端断开连接时剔除客户端连接对应的上下文,包括配置监听、服务订阅上下文,且处理客户端连接对应的实例上下线。客户端正常重启:客户端主动关闭连接,服务端实时感知。服务端正常重启:服务端主动关闭连接,客户端实时感知。
第二:防抖
当网络短暂不可用时,客户端需要能接受短暂网络抖动。超过阈值后,需要自动切换Server,但要防止请求风暴。
第三:断网演练
断网场景下以合理的频率进行重试,断网结束时可以快速重连恢复。
(4)长连接选型对比
在上述的备选框架中:从功能的契合度上,RSocket比较贴切Nacos的功能性诉求。RSocket性能上比gRPC要强,社区活跃度相对gRPC要逊色很多。
7.Nacos内核设计之寻址机制
(1)寻址机制的设计抽象
(2)当前Nacos内部实现的几种寻址机制
(1)寻址机制的设计抽象
Nacos支持单机部署以及集群部署。对于单机模式,Nacos只是自己和自己通信。对于集群模式,则集群内的每个Nacos节点都需要相互通信。因此这就带来⼀个问题,应该如何管理集群内的Nacos节点信息。而这,就需要Nacos内部的寻址机制。
单机模式和集群模式的根本区别是Nacos成员节点个数是单个还是多个,而Nacos集群需要能感知节点变更情况:节点是增加了还是减少了。当前最新的成员列表信息是什么,以何种方式去管理成员列表信息,如何快速的支持新的、更优秀的成员列表管理模式等等。针对上述需求点,Nacos抽象出了⼀个MemberLookup接口如下:
csharp
public interface MemberLookup {
//start.
void start() throws NacosException;
//Inject the ServerMemberManager property.
void injectMemberManager(ServerMemberManager memberManager);
//The addressing pattern finds cluster nodes.
void afterLookup(Collection<Member> members);
//Addressing mode closed.
void destroy() throws NacosException;
}
ServerMemberManager存储着本节点所知道的所有成员节点信息、提供了针对成员节点的增删改查操作、维护了⼀个MemberLookup列表。
MemberLookup接口的核心方法是:injectMemberManager()和afterLookup()。前者的作用是将ServerMemberManager注入到MemberLookup中,以方便利用ServerMemberManager 的存储、查询能力。后者则是⼀个事件接口,当MemberLookup需要进行成员节点信息更新时,会将当前最新的成员节点信息通知给ServerMemberManager,具体的节点管理方式,则是隐藏到具体的MemberLookup实现中。
(2)当前Nacos内部实现的几种寻址机制
一.单机寻址
二.文件寻址
三.地址服务器寻址
四.未来扩展点之集群节点自动扩容缩容
一.单机寻址
com.alibaba.nacos.core.cluster.lookup.StandaloneMemberLookup
单机模式的寻址模式很简单,其实就是找到自己的IP:PORT组合信息,然后格式化为⼀个节点信息,接着调用afterLookup()将信息存储到ServerMemberManager中。
java
public class StandaloneMemberLookup extends AbstractMemberLookup {
@Override
public void start() {
if (start.compareAndSet(false, true)) {
String url = InetUtils.getSelfIp() + ":" + ApplicationUtils.getPort();
afterLookup(MemberUtils.readServerConf(Collections.singletonList(url)));
}
}
}
二.文件寻址
com.alibaba.nacos.core.cluster.lookup.FileConfigMemberLookup
文件寻址模式是Nacos集群模式下的默认寻址实现。文件寻址模式也很简单,就是每个Nacos节点需要维护⼀个叫cluster.conf的文件。
该文件默认只需填写每个成员节点的IP信息即可,端口会自动选择Nacos的默认端口8848。如果有特殊需求更改了Nacos的端口信息,则需要在该文件将该节点的完整网路地址信息补充完整(IP:PORT)。
arduino
//cluster.conf文件内容如下:192.168.16.101:8847
192.168.16.102
192.168.16.103
当Nacos节点启动时,会读取cluster.conf文件的内容,然后将文件内的IP解析为节点列表,接着调用afterLookup()方法将节点列表存入ServerMemberManager。
ini
private void readClusterConfFromDisk() {
Collection<Member> tmpMembers = new ArrayList<>();
try {
List<String> tmp = ApplicationUtils.readClusterConf();
tmpMembers = MemberUtils.readServerConf(tmp);
} catch (Throwable e) {
Loggers.CLUSTER.error("nacos-XXXX[serverlist] failed to get serverlist from disk!, error : {}", e.getMessage());
}
afterLookup(tmpMembers);
}
如果集群扩容缩容,那么就要修改每个Nacos节点下的cluster.conf文件,然后Nacos内部的文件变动监听中心会自动发现文件修改,接着重新读取文件内容、加载IP列表信息、更新新增的节点。
java
private FileWatcher watcher = new FileWatcher() {
@Override
public void onChange(FileChangeEvent event) {
readClusterConfFromDisk();
}
@Override
public boolean interest(String context) {
return StringUtils.contains(context, "cluster.conf");
}
};
public void start() throws NacosException {
if (start.compareAndSet(false, true)) {
readClusterConfFromDisk();
// Use the inotify mechanism to monitor file changes and automatically trigger the reading of cluster.conf
try {
WatchFileCenter.registerWatcher(ApplicationUtils.getConfFilePath(), watcher);
} catch (Throwable e) {
Loggers.CLUSTER.error("An exception occurred in the launch file monitor : {}", e.getMessage());
}
}
}
但是这种默认寻址模式有⼀个缺点------运维成本较大。当新增⼀个Nacos节点时,需要手动修改每个节点下的cluster.conf文件。而且由于每个Nacos节点都存在⼀份cluster.conf文件,如果其中⼀个节点的 cluster.conf 文件修改失败,就容易造成了集群间成员节点列表数据的不⼀致性。因此,Nacos引入了新的寻址模式------地址服务器寻址模式。
三.地址服务器寻址
com.alibaba.nacos.core.cluster.lookup.AddressServerMemberLookup
地址服务器寻址模式是Nacos官方推荐的⼀种集群成员节点信息管理。该模式利用了⼀个Web服务器来管理cluster.conf文件的内容信息,这样运维人员只需要管理这⼀份集群成员节点内容即可。
每个Nacos节点,只需要定时向Web请求当前最新的集群成员节点列表信息。通过地址服务器这种模式,就可以大大简化Nacos集群节点管理的成本。同时,地址服务器是⼀个非常简单的Web程序,其稳定性能得到很好保障。
四.未来扩展点之集群节点自动扩容缩容
目前Nacos的集群节点管理,还属于人工操作。因此,未来期望能够基于寻址模式,实现集群节点自动管理的功能。
能够实现新节点上线时,只需要知道原有集群中的⼀个节点信息。这样就可以在⼀定时间内,顺利加入原有的Nacos集群中。同时也能够自行发现不存活的节点,自动将其从集群可用节点列表中剔除。这其中的逻辑实现,其实就类似Consul的Gossip协议。
8.服务注册发现模块的注册中心的设计原理
(1)服务注册发现简介
(2)数据模型
(3)数据一致性
(4)负载均衡
(5)健康检查
(6)性能与容量
(7)易用性
(8)集群扩展性
(9)用户扩展性
(1)服务注册发现简介
服务注册发现是⼀个古老的话题,当应用开始脱离单机运行和访问时,服务注册发现就诞生了。
目前的网络架构是每个主机都有⼀个独立的IP地址,那么服务注册发现基本上都是通过某种方式获取到服务所部署的IP地址。
DNS协议是最早将⼀个网络名称翻译为网络IP的协议。在最初的架构选型中,DNS + LVS + Nginx基本可满足所有服务的发现,此时服务的IP列表通常配置在Nginx或LVS。
后来出现了RPC服务,服务的上下线更加频繁,人们开始寻求⼀种能支持动态上下线 + 推送IP列表变化的注册中心产品。
互联网软件行业普遍热捧开源产品,个人开发者或中小型公司往往会将开源产品作为选型首选。Zookeeper是⼀款经典的服务注册中心产品(虽然它最初定位并不在于此),在很长⼀段时间里,它是提起RPC服务注册中心时想到的唯⼀选择,这很大程度上与Dubbo在的普及程度有关。
Consul和Eureka都出现于2014年。Consul在设计上把很多分布式服务治理上要用到的功能都包含在内,可以支持服务注册、健康检查、配置管理、Service Mesh等。Eureka借着微服务的流行,与SpringCloud生态深度结合,也曾流行起来。
Nacos则携带着阿里巴巴大规模服务生产经验,试图在服务注册和配置管理这个市场上,提供给用户⼀个新的选择。
(2)数据模型
一.服务注册中心数据模型的演进
二.Zookeeper、Eureka、Consul和Nacos的数据模型对比
三.服务实例数据的隔离模型
四.Nacos的临时服务实例和持久化服务实例
一.服务注册中心数据模型的演进
服务注册中心的核心数据是服务的名字和它对应的网络地址。
当服务注册了多个实例时,需要对不健康的实例进行过滤或针对实例的⼀些特征进行流量的分配,那么就要在服务实例上存储⼀些如健康状态、权重等属性。
随着服务规模的扩大,渐渐的又需要在整个服务级别设定⼀些权限规则,以及对所有实例都生效的⼀些开关,于是在服务级别又会设立⼀些属性。
再往后,又发现单个服务的实例又会有划分为多个子集的需求。例如⼀个服务是多机房部署的,那么可能要对每个机房的实例做不同配置,这样又需要在服务和服务实例之间再设定⼀个数据级别。
二.Zookeeper、Eureka、Consul和Nacos的数据模型对比
Zookeeper没有针对服务发现设计数据模型,它的数据以⼀种抽象的树形KV结构来组织,理论上可存储任何语义数据。
Eureka或Consul都设计了服务实例级别的数据模型,可满足大部分场景,不过无法满足大规模和多环境的服务数据存储。
Nacos经过多年经验提炼出的数据模型是⼀种服务-集群-实例的三层模型,这样基本可以满足服务在所有场景下的数据存储和管理。
Nacos的数据模型虽然相对复杂,但是它并不强制使用它里面的所有数据。在大多数场景下,可以选择忽略这些数据属性,此时可以降维成和Eureka或Consul⼀样的数据模型。
三.服务实例数据的隔离模型
另外⼀个需要考虑的是数据的隔离模型。作为⼀个共享服务型的组件,需要能够在多个用户或业务方使用的情况下,保证数据的隔离和安全,这在稍微大⼀点的业务场景中非常常见。
此外服务注册中心往往支持云上部署,此时就要求服务注册中心的数据模型能够适配云上的通用模型。
Zookeeper、Consul和Eureka都没有很明确针对服务隔离的模型。Nacos则在⼀开始就考虑到如何让用户能以多种维度进行数据隔离,同时能够平滑的迁移到阿里云上对应的商业化产品。
Nacos提供了四层的数据逻辑隔离模型。
第一层:用户账号
用户账号对应的是⼀个企业或个体,该数据⼀般不会透传到服务注册中心。
第二层:命名空间
⼀个用户账号可以新建多个命名空间。这个命名空间对应的注册中心物理集群可以根据规则进行路由,这样可以让注册中心内部的升级和迁移对用户是无感知的。同时可以根据用户的级别,为用户提供不同服务级别的物理集群。
第三层:服务分组
服务分组和服务名组成的二维服务标识,通过二维服务标识可以满足接口级别的服务隔离。
第四层:服务实例名称
每个命名空间会对应⼀个服务实例名称。
四.Nacos的临时服务实例和持久化服务实例
Nacos 1.0.0介绍的另外⼀个新特性是:临时服务实例和持久化服务实例。在定义上区分临时服务实例和持久化服务实例的关键是健康检查的方式。
临时服务实例使用客户端上报模式,持久化服务实例使用服务端反向探测模式。
临时服务实例需要能够自动摘除不健康实例,而且无需持久化存储服务实例,那么这种服务实例就适用于类Gossip的协议。
持久化服务实例使用服务端探测的健康检查方式。因为客户端不会上报心跳,那么就不能自动摘除下线的实例,所以这种实例就适用于类Raft的协议。
在大中型的公司里,这两种类型的服务往往都有。
类型一: ⼀些基础的组件例如数据库、缓存等,这些往往不能上报心跳。这种类型的服务在注册时,就需要作为持久化实例注册。
类型二: 一些上层的业务服务例如微服务、Dubbo服务,服务提供者支持添加汇报心跳的逻辑,此时就可作为临时实例注册。
Nacos 2.0中继续沿用了持久化及非持久化的设定,但有了⼀些调整。Nacos 1.0中持久化及非持久化属性是作为实例的元数据进行存储和识别,这导致了同⼀个服务下可同时存在持久化实例和非持久化实例。但在实际使用中,这种模式会给运维人员带来极大的困惑和运维复杂度。从系统架构看,⼀个服务同时存在持久化及非持久化实例是矛盾的,这就导致该能力事实上并未被广泛使用。
为了简化Nacos的服务数据模型,降低运维复杂度,提升Nacos的易用性,在Nacos2.0中已将是否持久化的数据,抽象至服务级别,而且不再允许⼀个服务同时存在持久化实例和非持久化实例,实例的持久化属性继承自服务的持久化属性。
(3)数据一致性
一.不同的服务注册场景使用不同的一致性协议
二.Nacos支持AP和CP两种⼀致性协议并存
一.不同的服务注册场景使用不同的一致性协议
数据⼀致性是分布式系统永恒的话题,Paxos协议的复杂更让数据⼀致性成为大牛们吹水的常见话题。
不过从协议层面上看,⼀致性的选型已经很长时间没有新的成员加入了。目前来看基本可以归为两种:⼀种是基于Leader的非对等部署的单点写⼀致性,⼀种是对等部署的多点写⼀致性。
当我们选用服务注册中心的时候,并没有⼀种协议能够覆盖所有场景。
场景一:服务节点不会定时发送心跳到注册中心
由于无法通过心跳来进行数据的补偿注册,所以第⼀次注册就必须保证数据不会丢失,从而让强⼀致协议看起来是唯⼀选择。
场景二:客户端会定时发送心跳来汇报健康状态
第⼀次注册的成功率并不是非常关键,当然也很关键,只是相对来说可以容忍数据少量的写失败,因为后续还可以通过心跳再把数据补偿回来。此时Paxos协议的单点瓶颈就不太划算,这也是Eureka为什么不采用Paxos协议而采用自定义的Renew机制的原因。可见不同的服务注册需求,会用不同的协议。
根据Dubbo对Zookeeper的处理,其实采用Eureka的Renew机制更合适。因为Dubbo服务往Zookeeper注册的就是临时节点,Dubbo服务需要定时发送心跳到Zookeeper来续约节点。当Dubbo服务下线时,需要将Zookeeper上的节点摘除。
二.Nacos支持AP和CP两种⼀致性协议并存
Zookeeper虽然用ZAB协议保证了数据的强⼀致,但它缺乏机房容灾能力,无法适应⼀些大型场景。
Nacos因为要支持多种服务类型的注册,并能够具有机房容灾、集群扩展等必不可少的能力,所以在1.0.0起正式支持AP和CP两种⼀致性协议并存。
Nacos1.0.0重构了数据的读写和同步逻辑:首先将与业务相关的CRUD与底层的⼀致性同步逻辑进行了分层隔离。然后将业务的读写(主要是写,因为读会直接使用业务层的缓存)抽象为Nacos定义的数据类型。接着调用⼀致性服务进行数据同步。在决定使用CP还是AP⼀致性时,通过代理 + 可控制的规则进行转发。
Nacos目前的⼀致性协议实现:⼀个是基于简化的Raft协议的CP⼀致性,⼀个是基于自研的Distro协议的AP⼀致性。
Raft协议,基于Leader进行写入,其CP也并不是严格的。只是能保证⼀半所见⼀致,以及数据丢失的概率较小而已。
Distro协议,则是参考了ConfigServer和开源Eureka。在不借助第三方存储的情况下,实现基本大同小异。Distro重点做了⼀些逻辑优化和性能调优。
(4)负载均衡
一.客户端进行负载均衡
二.服务端进行负载均衡
三.服务端负载均衡和客户端负载均衡的优缺点
四.Ribbon设计的客户端负载均衡
五.基于标签的负载均衡策略
六.其他
一.客户端进行负载均衡
严格来说,负载均衡并不算是传统注册中心的功能。
⼀般服务发现的完整流程应该是:客户端先从注册中心获取到服务的实例列表,然后根据自身需求来选择其中的部分实例,或者按⼀定的流量分配机制(负载均衡机制)来选择访问不同的服务提供者。
因此服务注册中心本身⼀般不限定服务消费者的访问策略。Eureka、Zookeeper、Consul,本身都没有实现可配置及可扩展的负载均衡机制。Eureka的负载均衡是由Ribbon来完成的,而Consul的负载均衡则是由Fabio来完成。
二.服务端进行负载均衡
在阿里内部,却是使用的相反的思路。服务消费者往往并不关心所访问的服务提供者的负载均衡,它们只关心如何高效和正确地访问服务提供者的服务。服务提供者则非常关注自身被访问的流量的调配。这其中的一个原因是,阿里内部服务访问流量巨大,稍有不慎就会导致流量异常压垮服务提供者的服务。因此服务提供者需要能够完全掌控服务的流量调配,并可以动态调整。
三.服务端负载均衡和客户端负载均衡的优缺点
服务端的负载均衡,给服务提供者更强的流量控制权,但是无法满足不同消费者希望使用不同负载均衡策略的需求,而不同负载均衡策略的场景却是存在的。
客户端的负载均衡则提供了这种灵活性,并对用户扩展提供更友好的支持。但客户端负载均衡策略如果配置不当,可能会导致服务提供者出现热点,或者压根就拿不到任何服务提供者。
抛开负载均衡到底是在服务提供者实现还是在服务消费者实现,目前的负载均衡有基于权重、服务提供者负载、响应时间、标签等策略。
四.Ribbon设计的客户端负载均衡
Ribbon设计的客户端负载均衡,主要是选择合适现有的IRule、ServerListFilter等接口实现,或者自己继承这些接口,实现自己的过滤逻辑。
Ribbon设计的客户端负载均衡,采用的是两步负载均衡:第⼀步先过滤掉不会采用的服务提供者实例,第二步在过滤后的服务提供者实例里实施负载均衡策略。
Ribbon内置的几种负载均衡策略功能还是比较强大的,同时又因为允许用户去扩展,这可以说是⼀种比较好的设计。
五.基于标签的负载均衡策略
基于标签的负载均衡策略可以做到非常灵活。Kubernetes和Fabio都已经将标签运用到了对资源的过滤中。使用标签几乎可以实现任意比例和权重的服务流量调配。但是标签本身需要单独的存储以及读写功能,不管是放在注册中心本身或者对接第三方的CMDB。
Nacos 0.7.0版本除了提供基于健康检查和权重的负载均衡方式外,还新提供了基于第三方CMDB的标签负载均衡器。
使用基于标签的负载均衡器,可实现同标签优先访问的流量调度策略。在实际应用场景中,可用来实现服务的就近访问。当服务部署在多个地域时,这非常有用。使用这个标签负载均衡器,可以支持非常多的场景。虽然目前Nacos支持的标签表达式不丰富,不过会逐步扩展它支持的语法。
六.其他
除此之外,Nacos定义了Selector作为负载均衡的统⼀抽象。
理想的负载均衡实现应该是什么样的呢?不同人会有不同答案。Nacos试图将服务端负载均衡与客户端负载均衡通过某种机制结合起来,提供用户扩展性,并给予用户充分的自主选择权和轻便的使用方式。
负载均衡是⼀个很大的话题,当我们关注注册中心提供的负载均衡策略时,需要注意该注册中心是否有我们需要的负载均衡方式,使用方式是否复杂。如果没有,那么是否允许我们方便地扩展来实现我们需求的负载均衡策略。
(5)健康检查
一.客户端进行健康检查---TTL机制发送心跳
二.服务端进行健康检查---TCP端口和HTTP接口探测
三.客户端健康检查和服务端健康检查的关注点
一.客户端进行健康检查---TTL机制发送心跳
Zookeeper和Eureka都实现了⼀种TTL机制。即如果客户端在⼀定时间内没有向注册中心发送心跳,则会摘除该客户端。
Eureka做的更好的⼀点在于:它允许在注册服务时,自定义检查自身状态的健康检查方法。这在服务实例能够保持心跳上报的场景下,有比较好的体验。
Nacos也支持这种TTL机制,不过这与ConfigServer的机制又有⼀些区别。Nacos目前支持临时服务实例使用心跳上报方式维持活性。临时服务实例发送心跳的周期默认是5秒。如果Nacos服务端在15秒没收到心跳,则会将该临时实例设置为不健康。如果Nacos服务端在30秒没收到心跳,则会将该临时实例摘除。
不过有⼀些服务是无法上报心跳的,但可提供⼀个检测接口由外部去探测。这样的服务广泛存在,且这些服务对服务发现和负载均衡的需求同样强烈。
二.服务端进行健康检查---TCP端口和HTTP接口探测
服务端健康检查最常见的方式是TCP端口探测和HTTP接口返回码探测,这两种探测方式因为其协议的通用性可以支持绝大多数的健康检查场景。
在其他⼀些特殊场景,可能还需要执行特殊的接口才能判断服务是否可用。例如部署了数据库的主备,数据库的主备可能会在某些情况下进行切换,需要通过服务名对外提供访问,保证当前访问的库是主库。此时的健康检查接口,可能是⼀个检查数据库是否是主库的MySQL命令。
三.客户端健康检查和服务端健康检查的关注点
客户端健康检查和服务端健康检查有不同的关注点。
客户端健康检查主要关注:客户端上报心跳的方式、服务端摘除不健康客户端的机制。
服务端健康检查主要关注:探测客户端的方式、灵敏度以及设置客户端健康状态的机制。
从实现复杂性来说,服务端探测肯定是要更加复杂的。因为服务端需要根据注册服务所配置的健康检查方式,去执行相应的接口、判断相应的返回结果、并做好重试机制和线程池管理。这与客户端探测,只需要等待心跳,然后刷新TTL是不⼀样的。
同时服务端健康检查无法摘除不健康的实例。这意味着注册过的服务实例,如果不调用接口向服务端主动进行注销,那么这些服务实例都需要服务端去维持执行健康检查的探测任务。而客户端健康检查则可以随时摘除不健康实例,减轻服务端的压力。
Nacos既支持客户端的健康检查,也支持服务端的健康检查。同⼀个服务可以切换健康检查模式。这种健康检查方式的多样性很重要,这样可以支持各种类型的服务,并且让这些服务都可以使用Nacos的负载均衡能力。
Nacos下⼀步要做的是实现健康检查方式的用户扩展机制,不管是服务端探测还是客户端探测。这样可以支持用户传入⼀条业务语义的请求,然后由Nacos去执行,做到健康检查的定制。
(6)性能与容量
一.影响性能的因素
二.Zookeeper的性能及其限制分析
三.Zookeeper、Eureka和Nacos的容量分析
一.影响性能的因素
影响读写性能的因素有:⼀致性协议、机器配置、集群规模、数据规模、数据结构、读写逻辑等。
在服务发现的场景中,读写性能是非常关键的,但是并非性能越高就越好,因为追求性能往往需要其他方面做出牺牲。
二.Zookeeper的性能及其限制分析
Zookeeper的写性能可以达上万TPS,这得益于Zookeeper精巧的设计,不过这显然是因为有⼀系列的前提存在。
首先Zookeeper的写逻辑就是进行KV写入的,内部没有聚合。其次Zookeeper舍弃了服务发现的基本功能如健康检查、友好的查询接口。它在支持这些功能时,显然需要增加⼀些逻辑,甚至弃用现有的数据结构。最后Paxos协议本身就限制了Zookeeper集群的规模,3或5个Zookeeper节点是不能应对大规模的服务订阅和查询的。
三.Zookeeper、Eureka和Nacos的容量分析
在对容量的评估时,不仅要评估现有的服务规模,也要预测未来3到5年的扩展规模。阿里的中间件在内部支撑着集团百万级别服务实例,在容量上遇到的挑战可以说不会小于任何互联网公司,这个容量不仅仅意味着整体注册的实例数,也同时包含单个服务的实例数、整体的订阅者的数目以及查询QPS等。
Nacos在淘汰Zookeeper和Eureka的过程中,容量是⼀个非常重要的因素。Zookeeper的容量,从存储实例数来说,可以达到百万级别。
但是随着容量的扩大,性能问题也会随之而来。当大量的实例上下线时,Zookeeper的表现并不稳定。同时在推送机制上的缺陷,会导致客户端的资源占用上升,从而导致客户端性能急剧下降。
Eureka在服务实例规模在5000左右时,就已经出现服务不可用的问题。甚至在压测过程中,如果并发线程数过高,就会造成Eureka崩溃。不过如果服务实例规模在1000上下,目前所有注册中心都可满足。
Nacos可以支撑的服务实例规模约为100万,服务规模可达10万+。在实际环境中,这个数字还会因为机器、网络配置、JVM参数而有所差别。下图展示了使用Nacos 1.0.0版本进行压测后的结果总结,针对容量、并发、扩展性和延时等进行了测试和统计。
(7)易用性
易用性包括多方面的工作。比如API和客户端接入是否简单,文档是否齐全,控制台界面是否完善等。对于开源产品来说,还有⼀块是社区是否活跃。
从使用的经验和调研来看:Zookeeper的易用性是比较差的,其客户端的使用也比较复杂。而且没有针对服务发现的模型设计及相应的API封装,需要自己处理。对多语言的支持也不太好,同时没有比较好用的控制台进行运维管理。
Eureka和Nacos相比较Zookeeper而言,已经改善很多。都有针对服务注册与发现的客户端,及基于Spring Cloud体系的Starter,可以帮助用户以非常低的成本无感知的做到服务注册与发现。同时还暴露标准的HTTP接口,支持多语言和跨平台访问。
Eureka和Nacos都提供官方的控制台来查询服务注册情况,不过Eureka 2.0已停止开发,而Nacos目前依然在建设中。
(8)集群扩展性
一.集群扩展性与集群容量及读写性能的关系
当使用⼀个较小的集群规模就能支撑远高于现有数量的服务注册及访问时,集群的扩展能力暂时就不会那么重要。
从协议的层面上来说:Zookeeper使用的ZAB协议,由于是单点写,在集群扩展性上不具备优势。Eureka在协议上来说理论上可以扩展到很大规模,但因为都是点对点的数据同步,Eureka集群在扩容后,性能上有很大问题。
二.集群扩展性与多地域部署和容灾支持的关系
集群扩展一:双机房容灾
如果不对基于Leader写的协议进行改造,那么是无法支持双机房容灾的。这意味着Zookeeper不能在没有人工干预的情况下做到双机房容灾。
在单机房断网情况下,使机房内服务可用并不难,难的是如何在断网恢复后做数据聚合。Zookeeper的单点写模式就会有断网恢复后的数据对账问题。
Eureka的部署模式天然支持多机房容灾。因为Eureka采用的是纯临时实例的注册模式:不持久化、所有数据都可以通过客户端心跳上报进行补偿。
由于临时实例和持久化实例都有它的应用场景,为了能够兼容这两种场景,Nacos支持两种模式的部署。⼀种是和Eureka⼀样的AP协议的部署,这种模式只支持临时实例,可以完美替代当前的Zookeeper、Eureka,并支持机房容灾。另⼀种是支持持久化实例的CP模式,这种情况下不支持双机房容灾。
集群扩展二:异地多活
很多业务组件的异地多活正是依靠服务注册中心和配置中心来实现的,这其中包含流量的调度和集群访问规则的修改等。
机房容灾是异地多活的⼀部分,但是要让业务能够在访问服务注册中心时,动态调整访问的集群节点,这需要第三方的组件来做路由。
异地多活往往是⼀个包含所有产品线的总体方案,很难说单个产品是否支持异地多活。
集群扩展三:多数据中心
多数据中心其实也算是异地多活的⼀部分。Zookeeper和Eureka都没有给出官方的多数据中心方案,Nacos则提供了采用Nacos-Sync组件来做数据中心之间的数据同步,这意味着每个数据中心的Nacos集群都会有多个数据中心的全量数据。
Nacos-Sync是Nacos生态组件里的重要⼀环,不仅承担Nacos集群与Nacos集群之间的数据同步,也承担与Eureka、Zookeeper、Kubernetes及Consul间的数据同步。
(9)用户扩展性
在框架的设计中,扩展性是⼀个重要的设计原则。Spring、Dubbo、Ribbon等框架都在用户扩展性上做了比较好的设计。这些框架的扩展性往往由面向接口及动态类加载等技术,来运行用户扩展约定的接口,实现用户自定义的逻辑。
在Server的设计中,用户扩展是比较审慎的。因为引入用户扩展代码,可能会影响原有Server服务的可用性。同时如果出问题,排查的难度也比较大。
设计良好的SPI是可能的,但由此带来的稳定性和运维风险需要仔细考虑。在开源软件中,往往通过直接贡献代码的方式来实现用户扩展。好的扩展会被很多人不停的更新和维护,这也是⼀种好的开发模式。
Zookeeper和Eureka目前Server端都不支持用户扩展。⼀个支持用户扩展的服务发现产品是CoreDNS,CoreDNS整体架构就是通过插件来串联起来的,通过将插件代码以约定的方式放到CoreDNS工程下,重新构建就可以将插件添加到CoreDNS整体功能链路的⼀环中。
那么这样的扩展性是否是有必要的呢?假如要添加⼀种新的健康检查方式,连接数据库执行⼀条MySQL命令,通常是在代码里增加MySQL类型的健康检查方法、构建、测试然后发布。但如果允许用户上传⼀个jar包放到Server部署目录下的某个位置,Server就会自动扫描并识别到这张新的健康检查方式呢?这样不仅更酷,也让整个扩展的流程与Server的代码解耦,变得非常简单。
所以对于系统的⼀些功能,如果能通过精心的设计,开放给用户进行扩展,那么其扩展性是极强的,毕竟增加扩展的支持不会让原有功能有任何损失。
所有产品都应该尽量支持用户进行扩展,这需要Server端的SPI机制设计得足够健壮和容错。
Nacos已经通过SPI机制开放了对第三方CMDB的扩展支持,后续很快会开放健康检查及负载均衡等核心功能的用户扩展,目的就是为了能够以⼀种解耦的方式支持用户各种各样的需求。
9.服务注册发现模块的注册中心的服务数据模型
(1)服务、服务实例和集群
(2)服务的定义
(3)服务的元数据
(4)实例的定义
(5)实例的元数据
(6)持久化属性
(7)集群的定义
(8)服务的生命周期
(9)实例的生命周期
(10)集群的生命周期
(11)元数据的生命周期
(1)服务、服务实例和集群
服务Service指的是由应用程序提供的⼀个或⼀组软件功能的⼀种抽象概念。它和应用有所不同,应用的范围更广,和服务属于包含关系,即⼀个应用可能会提供多个服务。而Nacos则选择了服务作为注册中心的最基本概念。
服务实例Instance(以下简称实例)是某个服务的具体提供能力的节点。⼀个实例仅从属于⼀个服务,而⼀个服务可以包含⼀个或多个实例。在许多场景下,实例又被称为服务提供者(Provider),而使用该服务的实例被称为服务消费者(Consumer)。
集群Cluster是Nacos中⼀组服务实例的⼀个逻辑抽象的概念,它介于服务和实例之间,是⼀部分服务属性的下沉和实例属性的抽象。
(2)服务的定义
在Nacos中,服务的定义包括以下几个内容:
一.命名空间Namespace
命名空间是Nacos数据模型中最顶层、也是包含范围最广的概念,用于在类似环境或租户等需要强制隔离的场景中定义。Nacos的服务也需要使用命名空间来进行隔离。
二.分组Group
分组是Nacos数据模型中次于命名空间的⼀种隔离概念。区别于命名空间的强制隔离属性,分组属于⼀个弱隔离概念,主要用于逻辑区分⼀些服务使用场景或不同应用的同名服务。最常用的情况主要是同⼀个服务的测试分组和生产分组,或者将应用名作为分组以防止不同应用提供的服务重名。
三.服务名Name
服务名也就是服务实际的名字,⼀般用于描述该服务提供了某种功能或能力。
Nacos之所以将服务的定义拆分为命名空间、分组和服务名,除了方便隔离使用场景外,还有方便用户发现唯⼀服务的优点。
在注册中心的实际使用场景上,同个公司的不同开发者可能会开发出类似作用的服务,如果仅仅使用服务名来做服务的定义和表示,容易在⼀些通用服务上出现冲突,比如登陆服务等。
通常推荐使用如下方式来确保服务的天然唯⼀性:由运行环境作为命名空间、由应用名作为分组、由服务功能作为服务名。
当然使用者可以忽略命名空间和分组,仅使用服务名作为服务唯⼀标示。这就需要使用者在定义服务名时额外增加自己的规则,来确保在使用中能够唯⼀定位到该服务而不会发现到错误的服务上。
(3)服务的元数据
服务定义只是为服务设置⼀些基本信息,用于描述服务及快速找到服务。服务的元数据则是进⼀步定义了Nacos中服务的细节属性和描述信息,主要包含:
一.健康保护阈值ProtectThreshold
为了防止因过多实例故障,导致所有流量全部流入剩余实例,继而造成流量压力将剩余实例压垮导致的雪崩效应,我们应该将健康保护阈值定义为⼀个0到1之间的浮点数。
当健康实例数占总服务实例数的比例小于该值时,无论实例是否健康,都会将这个实例返回给客户端。这样做虽然损失了⼀部分流量,但保证了集群中剩余健康实例能正常工作。
二.实例选择器Selector
用于在获取服务下的实例列表时,过滤和筛选实例。该选择器也被称为路由器,或者负载均衡器的统一抽象。目前Nacos支持通过将实例的部分信息存储在外部元数据管理CMDB中,并在发现服务时使用CMDB中存储的元数据标签来进行筛选的能力。
三.拓展数据extendData
用于用户在注册实例时自定义扩展的元数据内容,形式为K-V。可以在服务中拓展服务的元数据信息,方便用户实现自己的自定义逻辑。
(4)实例的定义
由于服务实例是具体提供服务的节点,因此Nacos在设计实例的定义时,主要需要存储服务实例的⼀些网络相关的基础信息,主要包含以下内容:
一.网络IP地址
服务实例的IP地址,在Nacos2.0版本之后支持设置为域名。
二.网络端口
该实例的端口信息。
三.健康状态Healthy
用于表示服务实例是否为健康状态,会通过健康检查的手段进行维护。
四.集群Cluster
用于标示服务实例归属于哪个逻辑集群。
五.拓展数据extendData
用于用户自定义扩展的元数据内容,形式为K-V。可以在实例中拓展该实例的元数据信息,方便用户实现自己的自定义逻辑和标示该实例。
(5)实例的元数据
和服务元数据不同,实例的元数据主要作用于实例运维相关的数据信息,主要包含:
一.权重Weight
实例级别的配置,权重为浮点数,范围为0-10000。权重越大,分配给该实例的流量越大。
二.上线状态Enabled
标记该实例是否接受流量,优先级大于权重和健康状态。运维人员在不变动实例本身的情况下,可快速手动将某实例从服务中移除。
三.拓展数据extendData
不同于实例定义中的拓展数据。这个拓展数据是给予运维人员在不变动实例本身的情况下,快速地修改和新增实例的扩展数据,从而达到运维实例的作用。
在Nacos 2.0版本中,实例数据被拆分为实例定义和实例元数据。主要是因为这两类数据其实是同⼀个实例的两种不同场景:开发运行场景及运维场景。
对于上下线和权重这种实例元数据信息,⼀般认为在实例已经在运行时,需要运维人员手动修改和维护的数据。对于IP、端口和集群等这种实例定义信息,⼀般情况下在实例启动并注册后,则不会在进行变更。
将实例定义和实例元数据两部分数据合并后,就能得到实例完整信息,这也是Nacos 1.0版本中的实例数据结构。
(6)持久化属性
Nacos提供两种类型的服务:持久化服务和非持久化服务,分别给类DNS的基础服务组件场景和上层实际业务服务场景使用。
为了标示该服务是哪种类型,需要在创建服务时选择服务的持久化属性。考虑目前大多数使用动态服务发现的场景为非持久化服务的类型,比如Spring Cloud、Dubbo、Service Mesh等,默认为非持久化服务。
(7)集群的定义
在Nacos中,集群中主要保存了有关健康检查的⼀些信息和数据:
一.健康检查类型HealthCheckType
使用哪种类型的健康检查方式,目前支持:TCP、HTTP、MySQL,设置为NONE可以关闭健康检查。
二.健康检查端口HealthCheckPort
设置用于健康检查的端口。
三.是否使用实例端口进行健康检查UseInstancePort
如果使用实例端口进行健康检查,将会使用实例定义中的网络端口进行健康检查,而不再使用上述设置的健康检查端口进行。
四.拓展数据extendData
用于用户自定义扩展的元数据内容,形式为K-V。可自定义扩展该集群的元数据信息,方便用户实现自定义逻辑和表示集群。
(8)服务的生命周期
在注册中心中,实例数据都和服务实例的状态绑定,因此服务实例的状态直接决定了注册中心中实例数据的生命周期。而服务作为实例的聚合抽象,生命周期也会由服务实例的状态来决定。
服务的生命周期,开始于用户向注册中心发起服务注册的请求。
在Nacos中,发起服务注册有两种方式。⼀种是直接创建服务,⼀种是注册实例时自动创建服务。前者可以让发起者在创建时期就制定⼀部分服务的元数据信息,而后者只会使用默认的元数据创建服务。
在服务的生命周期期间,用户可以新增、删除服务实例,同时也能够对服务元数据进行修改。当用户主动发起删除服务的请求或⼀定时间内服务下没有实例后,服务才结束其生命周期,等待下⼀次创建。
(9)实例的生命周期
实例的生命周期,开始于注册实例的请求。但根据不同的持久化属性,实例后续的生命周期会有不同。
一.持久化的实例
会通过健康检查维护健康状态,但不会自动终止该实例的生命周期。在生命周期结束前,持久化实例可被修改数据,甚至可修改其健康状态。终止持久化实例生命周期的唯⼀方式就是注销实例的请求。
二.非持久化的实例
会根据版本的不同,采用不同的方式维持健康状态。如果是Nacos 1.0版本,会通过定时的心跳请求来进行续约。当超过⼀定时间没有心跳进行续约时,该非持久化实例则终止生命周期。如果是Nacos 2.0的版本,会通过gRPC的长连接来维持状态。当连接发生中断时,该非持久化实例则终止生命周期。
非持久化实例可通过主动注销实例的请求,主动终其生命周期。但由于长连接和心跳续约的存在,可能导致实例的生命周期刚被终止移除,马上又因为心跳和长连接的补偿请求,再次开启实例的生命周期,给人⼀种注销失败的假象。
(10)集群的生命周期
由于集群作为服务和实例的⼀个中间层,因此集群的生命周期与实例和服务的生命周期均有关。
集群的生命周期开始与该集群第⼀个实例的生命周期同时开始,因为⼀个实例必定归属于⼀个集群。因此当第⼀个实例的生命周期开始时,就是集群生命周期的开始。当⼀个集群下不存在实例时,集群的生命周期也不会立刻结束,而是会等到这个服务的生命周期结束时,才会⼀起结束生命周期。
(11)元数据的生命周期
由于元数据与其对应的数据模型紧密关联,所以元数据的生命周期基本和对应的数据模型保持⼀致。
但元数据会经常被运维人员主动操作,会被Nacos进行⼀段时间内的记忆,因此元数据的生命周期的终止相比对应的数据要滞后。若这滞后期间内,对应的数据又重新开始生命周期,则该元数据的生命周期将被立刻重置,不再终止。
10.服务注册发现模块的健康检查机制
(1)注册中心的健康检查机制
(2)Nacos健康检查机制
(3)Nacos临时实例的健康检查机制
(4)Nacos持久化实例的健康检查机制
(5)Nacos集群模式下的健康检查机制
(1)注册中心的健康检查机制
要知道⼀个服务是否健康:方式一是客户端主动上报,告诉服务端自己的健康状态。如果在⼀段时间没有上报,那么我们就认为服务已经不健康。方式二是服务端主动探测,检查客户端是否还能被探测。
如果所有服务都需要注册中心去主动探测,由于服务的数量会远远大于注册中心的数量,那么注册中心的任务量将很巨大,所以最好都采用服务主动上报的方式进行健康检查。如果服务本身没法主动进行健康上报,那么这时就可使用注册中心主动检查健康状态的方式。
想象这么⼀个场景,所在的地区突然发生地质灾害,A被掩盖在废墟下面。搜救队必须要知道A在废墟里,才能对A进行施救,那么有什么方法可以让救援队知道你在废墟下面?
第⼀种,A在废墟里面大喊,让搜救队知道你的位置和健康状态。第二种,搜救队使用了他们的专业检查设备,探测到A正埋在废墟下面。这两种检查方式可以类比到Nacos对于服务的探测。如果A在废墟中大声呼叫救援队并且提供A的位置和健康信息,那么相比于搜救队用探测设备挨着废墟探测会使探测队的工作量减轻很多,那么搜救队就可以专注于尽快将A救出。如果A在废墟之下因为身体状况无法呼救,那么搜救队就会放弃搜救吗?当然不是,搜救队肯定也会对废墟进行全面探测将A救出。
当前主流注册中心的健康检查机制主要都采用了TTL(Time To Live)机制。即客户端在⼀定时间内没向注册中心发送心跳,那么注册中心就会认为此服务不健康,进而触发后续的剔除逻辑。对于主动探测的方式,那么根据不同的场景,需要采用的方式可能会有不同。
(2)Nacos健康检查机制
既然以上两种健康检查机制都有应用的场景,且适用场景不⼀致,那么Nacos对于健康检查的机制又是如何抉择的?
在介绍Nacos的健康检查机制之前,先总结⼀下Nacos服务的特点。Nacos提供了临时实例和永久实例两种服务类型,供用户注册实例时选择。
临时实例只是临时存在于注册中心之中,会与注册中心保持心跳,会在服务下线或不可用时被注册中心剔除。注册中心会在⼀段时间没收到来自客户端的心跳后,将临时实例设置为不健康,然后在⼀段时间后进行剔除。
永久实例在被删除前会永久的存在于注册中心,且有可能并不知道注册中心存在,不会主动向注册中心上报心跳,那么这时就需要注册中心主动进行探活。
Nacos中健康检查的整体交互如下图示:
(3)Nacos临时实例的健康检查机制
用户可以通过两种方式进行临时实例的注册。
方式一:通过Nacos的OpenAPI进行服务注册
方式二:通过Nacos提供的SDK进行服务注册
OpenAPI的注册方式是用户根据自身需求调用HTTP接口对服务进行注册,然后通过HTTP接口发送心跳到注册中心。在注册服务的同时会注册⼀个全局的客户端心跳检测任务。如果⼀段时间没有收到来自客户端的心跳,该任务会将其标记为不健康。如果在间隔的时间内还未收到心跳,那么该任务会将其剔除。
SDK的注册方式是通过RPC与注册中心保持连接。客户端会定时通过RPC连接向注册中心发送心跳,来保持连接的存活。如果客户端和注册中心的连接断开,那么注册中心会主动剔除该客户端所注册的服务,达到下线的效果。同时Nacos注册中心会在启动时,注册⼀个过期客户端清除的定时任务,用于删除那些健康状态超过⼀段时间的客户端。
从上面的特点可以发现,对于不同类型的使用方式:Nacos健康检查的本质都是相同的,都是由客户端向注册中心发送心跳,注册中心会在连接断开或是心跳过期后将不健康的实例移除。
(4)Nacos持久化实例的健康检查机制
对于持久化实例的的健康检查,Nacos采用的是注册中心探测机制。注册中心会在初始化持久化实例时,启动一个探活的定时任务。
Nacos内置提供了三种探测协议,即HTTP、TCP、MySQL。⼀般而言,HTTP和TCP已经可以涵盖绝大多数的健康检查场景。
MySQL主要用于特殊的业务场景。例如数据库的主备需要通过服务名对外提供访问,需要确定当前访问数据库是否为主库时,那么此时的健康检查接口,就是⼀个检查数据库是否为主库的MySQL命令。
由于持久化的服务实例在被客户端服务主动删除前会⼀直存在,所以探活的定时任务会不断探测服务的健康状态,并且将无法探测成功的实例标记为不健康。
但有时会有这样的场景,有些服务不希望去校验其健康状态。于是Nacos也提供了对应的白名单配置,可以将服务配置到该白名单。那么Nacos会放弃对其进行健康检查,实例的健康状态由用户传入来决定。
(5)Nacos集群模式下的健康检查机制
⼀个完整的注册中心,应该具备高可用的特性。即注册中心是可以进行集群部署,然后作为⼀个整体对外提供服务。
集群部署时,客户端只会和其中⼀个注册中心服务保持连接和请求,但是客户端的服务信息需要注册到所有的注册中心服务节点上,这样其他客户端就可以从任意⼀个注册中心服务获取到所有的服务列表。
一.注册中心集群节点如何对不是和自己保持心跳连接的服务进行健康检查
在集群模式下,⼀个服务只会被集群中的某个注册中心节点所负责。但是该服务的实例信息也会以副本的形式同步到其他的注册中心节点中,这样服务的订阅者在查询服务列表时,就可以始终获取到全部的服务列表。
所以临时服务实例只会对负责自己的某个注册中心节点发送心跳信息,而一个注册中心节点只会对其负责的持久化服务实例进行健康探测,在获取到健康状态后再将健康信息同步到集群中的其他注册中心节点。
在Nacos中,从注册方式维度可以将服务的注册分为两大类。⼀是SDK方式,通过RPC连接进行注册。二是OpenAPI方式,通过IP + 端口进行注册。
二.服务实例如何寻找负责对自己进行健康检查的注册中心节点
一个服务实例只要和注册中心集群中的任意⼀个节点建立联系,那么就由这个注册中心节点负责对这个服务实例进行健康检查。
每个注册中心节点都会在启动时启动⼀个全局的同步任务,用于将其负责的所有服务实例信息同步到集群中的其他注册中心节点上。
注册中心节点对于不是由其负责的服务实例信息,会维护一个续约时间。当它收到其他注册中心节点同步过来的服务信息时,会更新其续约时间。
如果注册中心节点发现不是由自己负责的服务信息,其续约时间没有更新,则会认为此服务已不健康,从而实现对不是自己负责的服务的健康检查。
11.配置管理模块的配置一致性模型
(1)配置一致性模型
(2)Server间的数据一致性
(3)Client与Server的数据⼀致性
(1)配置一致性模型
Nacos配置管理⼀致性协议分为两个大部分:第⼀部分是Server间的⼀致性协议,第二部分是Client与Server的⼀致性协议。
配置作为分布式系统中非强⼀致数据,在出现脑裂时可用性高于⼀致性,因此阿里配置中心是采用AP⼀致性协议。
(2)Server间的数据一致性
一.有DB模式(读写分离架构)
⼀致性的核心是Server与DB保持数据⼀致性,从而保证Server数据⼀致。Server之间都是对等的,任何⼀个Server在处理配置数据的写入请求时,会先将数据持久化到DB,持久化成功后该Server再异步通知其他Server到DB中拉取最新的配置数据。
二.无DB模式
Server间采用Raft协议保证数据⼀致性,行业大部分产品采用此模式。Nacos提供此模式,是方便用户本机运行,降低对存储依赖。
(3)Client与Server的数据⼀致性
Client与Server的数据⼀致性是通过MD5值是否⼀致来保证的。如果发现不⼀致,Client就从Server拉取最新的数据。
一.Nacos 1.X
Nacos 1.X采用HTTP 1.1短连接模拟长连接。每30s发送⼀个心跳给Server,判断配置的MD5值是否和Server保持⼀致。如果⼀致就保持连接,如果有不⼀致配置,就把不⼀致的配置返回,然后Client就可以获取到最新的配置值。
二.Nacos 2.X
Nacos 2.x相比Nacos 1.x的30s⼀次的长轮询,升级成长连接模式。配置变更会启动建立长连接,配置变更后服务端推送变更配置列表。然后Clinet拉取配置更新,因此通信效率大幅提升。
12.Zookeeper、Eureka和Nacos的对比总结
(1)数据模型方面
(2)一致性协议方面
(3)容灾能力方面
(4)健康检查方面
(5)性能容量方面
(6)易用性方面
(7)集群扩展性方面
(8)用户扩展性方面
(1)数据模型方面
Zookeeper没有针对服务发现设计数据模型。
(2)一致性协议方面
如果使用定时心跳补偿数据,那么Paxos协议的单点瓶颈就会不太划算。这也是Eureka为什么不采用Paxos协议而采用自定义的Renew机制的原因。
而Dubbo服务往Zookeeper注册的就是临时节点,Dubbo服务要定时发送心跳到Zookeeper去进行节点的续约。
(3)容灾能力方面
Zookeeper虽然使用ZAB协议保证了数据的强⼀致,但是它缺乏机房容灾能力,无法适应⼀些大型场景。因为Zookeeper的单点写模式会存在断网恢复后的数据对账问题,所以Zookeeper比较难做到双机房容灾。
Eureka因为采用的是纯临时实例的注册模式 + 心跳补偿,所以天然支持多机房容灾。
Zookeeper和Eureka都没有给出官方的多数据中心方案,而Nacos则提供了采用Nacos-Sync组件来做数据中心之间的数据同步,所以Nacos具有机房容灾的能力。
(4)健康检查方面
Zookeeper和Eureka都实现了⼀种TTL机制,即如果客户端在⼀定时间内没有向注册中心发送心跳,则会摘除该客户端。Nacos也支持这种TTL机制,支持临时服务实例使用心跳上报方式维持活性。如果Nacos服务端在15秒没收到心跳,则会将该临时实例设置为不健康。如果Nacos服务端在30秒没收到心跳,则会将该临时实例摘除。
(5)性能容量方面
Zookeeper的写性能可达上万TPS,这得益于它精巧的设计。Zookeeper的写逻辑就是进行KV写入的,内部没有聚合。Zookeeper舍弃了服务发现的基本功能如健康检查。但Paxos协议本身就限制了Zookeeper集群的规模,3或5个Zookeeper节点是不能应对大规模的服务订阅和查询。
Zookeeper的容量,从存储实例数来说,可以达到百万级别。当大量的实例上下线时,Zookeeper在推送机制上的缺陷,会导致客户端的资源占用上升,从而影响客户端的性能。
Eureka在服务实例规模在5000左右时,会出现服务不可用,并发线程数过高会造成Eureka崩溃。
Nacos可以支撑的服务实例规模约为100万,服务规模可达10万+。
(6)易用性方面
Zookeeper的易用性比较差,客户端使用比较复杂,而且Zookeeper没有针对服务发现的模型设计以及相应的API封装。
Eureka和Nacos基于Spring Cloud体系的Starter,可帮助用户以非常低的成本、无感知地做到服务注册与发现,以及都提供官方控制台来查询服务注册情况。
(7)集群扩展性方面
Zookeeper使用的ZAB协议,由于是单点写,集群扩展性不具备优势。
Eureka理论上可以扩展到很大规模,但因为都是点对点的数据同步,Eureka集群在扩容后,性能上有很大问题。
(8)用户扩展性方面
Zookeeper和Eureka目前Server端都不支持用户扩展,Nacos已经通过SPI机制开放了对第三方CMDB的扩展支持。