文章目录
- 概述
- 一、客户端新注册实例信息在集群间同步
- 二、服务端集群节点信息在集群间同步
- 2.1、DistroMapper
- 2.2、ProtocolManager
- 2.3、ServerListManager
- 2.4、RaftPeerSet
- 三、客户端实例状态信息在集群间同步
- 四、服务端新节点上线同步集群数据
概述
在Nacos集群模式下,客户端可以选择注册在集群中的某一台机器上:
server:port: 9002
spring:application:name: stock-servicecloud:nacos:discovery:# 指定nacos server的地址server-addr: localhost:8847# 指定集群名称cluster-name: njmetadata:version: v3#ephemeral: falsenamespace: public
当客户端启动完成后,集群中8847所在的服务端,首先获取到了客户端实例,随后8867,8857也同步获取到了客户端实例 。

这里就牵涉到两个问题:
- 客户端只向集群中的某一台机器进行了注册,集群中的其他节点是如何感知到的?
- 如果客户端下线,或者健康状态发生改变,其他节点又是如何同步的?
也就是文中的四点:
- 客户端启动,注册实例到集群中的某个节点,该注册信息在整个集群中的同步。
- 客户端实例状态信息发生改变,在集群间同步。
- 服务端集群节点状态信息在集群间同步。
- 服务端新上线的节点,同步集群中的节点信息。
一、客户端新注册实例信息在集群间同步
当客户端发起注册实例的请求,到达服务端后,在DistroConsistencyServiceImpl#put方法中,除了单机模式下将实例信息放入阻塞队列之外,集群模式下还需要进行同步。默认的延迟时间是1s。

在sync方法中,首先会去创建一个DistroDelayTask任务实例,然后放入NacosDelayTaskExecuteEngine中。该类是Nacos自己实现的延迟任务执行引擎。这里的套路就和注册实例信息一样,首先将实例放入队列中,然后再由其他线程异步获取并执行逻辑。

最终调用到的是DistroSyncChangeTask#run方法,同样是发起http请求,通知集群中的其他节点,如果返回失败,还会进行重试:


访问的是服务端的/distro/datum:

请求发送到服务端的DistroController的onSyncDatum

最终执行onPut方法,将实例信息放入队列中。

二、服务端集群节点信息在集群间同步
如果集群中的某个节点宕机了,或者集群启动,那么集群之间需要同步状态信息,是通过registerServerStatusReporter定时任务实现的,默认2s一次。

真正执行逻辑的是ServerStatusReporter的run方法中的synchronizer.send:

同样是发送http请求,通知其他节点,调用的是OperatorController的/server/status接口。

为了观察集群中某个节点下线后,其他节点是如何同步状态的,手动模拟某个节点下线:

在onReceiveServerStatus方法中,首先会对传入的configInfo参数进行一系列的处理,获取IP端口,权重等信息,然后调用 memberManager.update(server);方法。

update方法,如果当前节点的状态已经是DOWN,就会直接从memberAddressInfos中删除该实例信息:

并且在实例信息有改变的情况下,还会通过notifyMemberChange();方法去发布一个事件:

实际上该事件是放在DefaultPublisher的queue属性中的,该属性是一个阻塞队列。

真正处理该事件的方法,是DefaultPublisher的run方法:

receiveEvent又调用了notifySubscriber,这里的subscribers订阅者有四个,这四个订阅者都是MemberChangeListener的子类

2.1、DistroMapper
DistroMapper的onEvent,主要的用途是用于Distro一致性协议中的数据同步路由。在该方法中,主要完成了三件事:
- 选出健康节点,并生成节点 IP 列表。
- 排序节点列表,确保顺序一致。
- 替换旧的健康节点列表。

当 Nacos 集群成员发生变化时,DistroMapper 会自动更新健康节点列表,并保持节点顺序一致,保障 AP 模式下的数据同步路由稳定运行。
2.2、ProtocolManager
ProtocolManager的onEvent用于当集群成员列表发生变化时,分别通知 AP 协议组件(Distro)和 CP 协议组件(Raft),以更新它们各自的一致性成员信息,启用不同的线程进行处理。ProtocolManager 是这两种协议的统一协调者。

2.3、ServerListManager
ServerListManager的onEvent用于接收到成员列表变更事件后,直接用最新的成员列表替换本地的servers列表。ServerListManager 是用于维护当前 Nacos 节点所感知的集群服务器列表的,会被其他组件依赖:
- 服务注册、发现时选择服务器;
- 启动时检查集群节点是否就绪;
- 分布式协议中做心跳/同步时依据节点列表操作

2.4、RaftPeerSet
RaftPeerSet的onEvent用于Naocs CP模式下的raft算法,在该方法中,主要完成了:
- 获取当前的最新成员列表, 和上一次的成员列表比较,找出新增或变更的节点。
- 如果成员发生了变化(比如新节点加入或节点 IP 变化),通知 Raft 协议层更新 投票成员列表
- 更新缓存,记录最新的集群节点状态

三、客户端实例状态信息在集群间同步
这里利用到的是scheduleServiceReporter定时任务。默认一分钟执行一次。

真正执行逻辑的是ServiceReporter的run方法:

distroMapper.responsible(serviceName)是关键代码,该方法主要用于判断当前节点是否负责处理某个服务(serviceName) 的数据同步和持久化
- 获取当前 Nacos 集群中所有 健康的节点地址。
- 如果 Distro 没启用,或者是单机模式,直接返回 true,所有服务都由自己负责。
- 健康节点列表为空,表示 Distro 还没初始化好,此时不能确定责任关系。
- 获取当前节点在健康节点列表中的索引。
- 用服务名做 Hash,然后和健康节点数量取模,得到这个服务应由哪个节点处理(索引)。
- 判断这个 target 索引,是否属于当前节点。

同样是发送http请求,通知其他节点,调用的是ServiceController的serviceStatus方法,主要用于验证当前服务节点的服务信息是否和其他节点保持一致。

在ServiceController的serviceStatus方法中,如果发现服务状态有变化,则会调用addUpdatedServiceToQueue方法,向阻塞队列中存入信息:



UpdatedServiceProcessor的run方法,再次开启任务:

所以最终执行逻辑的是ServiceUpdater的run:

实例的健康状态发生了改变:

发布事件,被实现了ApplicationListener<ServiceChangeEvent>的实现类监听,最终通过udp协议,推送给客户端。

在集群模式下,服务端检查心跳的定时任务,实际上也是由集群中的某一个节点去完成的:

这里就是利用了上面提到的hash算法,不满足条件的节点直接return,不会执行心跳检查任务。
四、服务端新节点上线同步集群数据
上线的新节点,需要从其他节点同步数据,是通过DistroLoadDataTask任务实现的:

在load方法中:
- 首先获取集群中除了自己的其他节点。如果没有获取到,就会阻塞等待。
- 如果还没有注册任何数据类型,同样阻塞等待。
- 从其他 Nacos 节点获取数据快照;

然后循环调用loadAllDataSnapshotFromRemote方法,该方法有两个关键操作:
- 根据地址调用
DistroController的/distro/datums接口,获取数据。 - 将数据写入内存中。
