文章目录
- 概述
- 一、客户端新注册实例信息在集群间同步
- 二、服务端集群节点信息在集群间同步
- 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
接口,获取数据。 - 将数据写入内存中。