文章目录
- 前言
- 1、临时节点集群间同步
- 1.1、DistroProtocol#sync
- 1.2、DistroProtocol#syncToTarget
- 1.3、NacosDelayTaskExecuteEngine#addTask
- 1.4、NacosDelayTaskExecuteEngine构造方法
- 1.5、ProcessRunnable#run
- 1.6、DistroHttpDelayTaskProcessor#process
- 1.7、NacosExecuteTaskExecuteEngine#addTask
- 1.8、TaskExecuteWorker#process
- 1.9 InnerWorker#run
- 1.10 DistroHttpCombinedKeyExecuteTask#run
- 1.11 DistroDelayTaskProcessor#process
- 1.12、DistroSyncChangeTask#process
- 1.13、DistroSyncChangeTask#doExecute
- 1.14、DistroHttpAgent#syncData
- 1.15、NamingProxy#syncData
- 1.16、DistroController#onSyncDatum
- 1.17、DistroController#onSyncDatum
- 1.18、DistroConsistencyServiceImpl#onSyncDatum
- 2、永久节点集群间同步
前言
之前分析过eureka
,eureka
是一个AP
的注册中心,他的节点间数据同步是通过p2p
来进行的,而nacos
既是CP
系统也是AP
系统,临时节点的数据是存储在内存中,节点间的同步是通过P2P
方式,永久节点则是通过raft
来实现数据的一致性的,是属于CP
的。
1、临时节点集群间同步
在服务注册的时候,如果是临时节点,我们会调用
DistroConsistencyServiceImpl#put
方法,之前讲解过写入map
和写入内存队列,最后还会将变更数据同步到集群中的其他节点,这次来剖析一下同步流程,这里默认延迟时间是2s
,然后/2
是1s
1.1、DistroProtocol#sync
这里会遍历所有成员,然后抛去自己,然后调用
syncToTarget
方法
1.2、DistroProtocol#syncToTarget
这里他会把节点的地址也封装到
DistorKey
中,然后包装成一个Task
,交给延迟任务引擎来处理
1.3、NacosDelayTaskExecuteEngine#addTask
这里就是判断如果任务存在就合并,不存在就添加进去,我们看下什么时候执行任务,肯定会从任务队列中获取任务然后执行。
1.4、NacosDelayTaskExecuteEngine构造方法
这里会创建一个延迟定时任务,每
100ms
执行一次,对应执行的任务在ProcessRunnable#run
中
1.5、ProcessRunnable#run
这里会从队列中获取任务,然后根据
taskKey
去获取对应的processor
,调用调用process
方法,这里匹配到的processor
是DistroHttpDelayTaskProcessor
1.6、DistroHttpDelayTaskProcessor#process
这里又封装成一个DistroHttpCombinedKeyExecuteTask,然后把这个task交给任务执行引擎
1.7、NacosExecuteTaskExecuteEngine#addTask
这里根据任务获取
processor
,这里没有处理这个任务的processor
,所以会通过hash
的方式获取一个TaskExecuteWorker
,然后调用其processor
方法
1.8、TaskExecuteWorker#process
这里会把任务
put
到一个队列中,我们看下什么地方会从队列中获取任务然后进行处理。
1.9 InnerWorker#run
在
TaskExecuteWorker
的构造方法中会创建InnerWorker
线程,并调用其start
方法,在这个 方法中就不断从队列中取出任务然后执行任务,这里会调用task
的run
方法,之前我们已经知道任务被封装成DistroHttpCombinedKeyExecuteTask
,我们看下它的run
方法
1.10 DistroHttpCombinedKeyExecuteTask#run
这里又封装了一个
DistroHttpCombinedKeyDelayTask
任务,然后这里设置延迟时间也是1s
,最后把任务交给延迟任务执行引擎,这里会走merge
任务的逻辑,在NacosDelayTaskExecuteEngine
有个延迟任务,100ms
执行一次,因为任务的延迟时间是1s
,到第10
次的时候会将task
取出来,根据这个任务获取processor
,获取不到默认是DistroDelayTaskProcessor
,所以会调用DistroDelayTaskProcessor#process
方法
1.11 DistroDelayTaskProcessor#process
这里又会封装成
DistroSyncChangeTask
,然后交给Worker
线程执行,我们之前分析过,在worker
处理的时候会调用taks#run
方法,所以我们看下DistroSyncChangeTask#run
1.12、DistroSyncChangeTask#process
这个就是父类中的模版,做一些容错的处理,真正执行的时候会调用
doExecute
方法
1.13、DistroSyncChangeTask#doExecute
获取到真实要同步的数据,然后获取同步组件,调用
syncData
来发送同步数据
1.14、DistroHttpAgent#syncData
这里会调用
NamingProxy#syncData
来同步数据
1.15、NamingProxy#syncData
这里就是向集群其他节点发送同步数据。请求的接口是
distro/datum
,对应服务端的接口DistroController#onSyncDatum
1.16、DistroController#onSyncDatum
- 遍历
map
,判断key
是不是临时数据的key
,如果是的话,根据key
解析出来namespace
和serviceName
- 去注册表中看有没有这个服务,如果没有创建一个空的
- 调用
DistroProtocol#onReceive
处理数据
1.17、DistroController#onSyncDatum
- 首先通过
resourceType
获取一个DistroDataProcessor
- 调用
DistroDataProcessor#processData
方法
1.18、DistroConsistencyServiceImpl#onSyncDatum
- 首先对数据进行反序列化
- 调用
onPut
方法,这个方法之前分析过会将数据存入DataStore
,并且将事件变更放入内存队列中。然后会有线程从队列中取任务处理,然后把数据更新到注册表中。
2、永久节点集群间同步
在之前的版本
nacos
同步采用的是自研的raft
协议,2.0.0
版本之后采用JRaft
,这里我们还是基于之前的Raft
讲解。
2.1、RaftCore#init(选举流程)
首先从本地文件加载数据到内存中,
- 然后注册了一个选举任务500ms执行一次。
- 注册了一个心跳任务500ms执行一次。
2.2、MasterElection#run(选举任务)
- 这里获取自己的节点信息,拿自己的
leaderDueMs
减去500
,这个一开始是0~15000
的随机数,只有减到负数才进行选举,如果减到负数了,则重置leaderDueMs
和heartbeatDueMs
,然后就是向其他节点发送选票
2.3、MasterElection#sendVote
- 获取自己的peer,然后重置所有的选票信息,把自己的term++,然后投给自己一票,把自己的状态改成候选人状态。
- 向其他的节点发送拉票信息。节点收到其他节点的选票信息是在RaftCore#receivedVote方法中处理的。
2.4、RaftCore#receivedVote
这里就是对比term的大小,如果term小于对方则投票给对方,如果term大于对方,则把选票设置为自己,返回给拉票房。
2.5、RaftCore#decideLeader
拉票方收到对方的投票结果会调用
decideLeader
方法,这里就是判断所有节点收到的选票的数量,如果有节点到达半数以上,则选举那个节点为leader
,如果本机为leader
,则会发布leader
选举事件
2.6、HeartBeat#run(心跳任务)
和选举任务差不多,也是对一个随机数做减法,减到0就可以继续往下走,发送心跳了。
2.6、HeartBeat#sendBeat(发送心跳)
这里只有
leader
节点才能发送心跳,这里就是压缩数据,向所有的follwer
节点发送心跳。
2.7、RaftCore#receivedBeat(处理心跳)
Fellower节点接到心跳逻辑
- 首先比较term的大小,如果当前term比leader大则抛出异常,因为leader的版本是落后的。
- 重置本地的leaderDueMs和heartbeatDueMs,如果leader一直正常维持心跳,那么fellower是不会进行选举的。
- 会调用makeLeader方法,这个主要是看本地维护的leader和远程的leader是不是同一个,如果不是则进行更新。
2.8、RaftConsistencyServiceImpl#onPut
当服务注册到
nacos
中,他会调用consistencyService#put
方法,把注册信息同步到集群其他节点,对于永久节点consistencyService
的实现有两个,1.4版本之前是RaftConsistencyServiceImpl
,之后是PersistentServiceProcessor
。我们这次介绍RaftConsistencyServiceImpl
2.8、RaftCore#signalPublish
- 判断不是leader的话则会把请求转发给leader
- 如果是leader,则先进行加锁,然后调用onPublish方法进行本地存储。
- 通过给所有的fellower节点,这里用了这里用了CountDownLatch,阻塞等过半的节点成功就返回。最大等待5s,如果5s还没同步到过半节点,就抛出异常,释放锁资源。这里就会导致节点间的数据不一致了。所以它并不是强一致的,而是实现成最终一致的。
2.8、最终一致性的Raft协议
它实现最终一致性,就是
leader
在发送心跳的时候会将这个数据的key
与timestamp
(也相当于版本号)
带给follower
节点,然后follower
节点收到leader
发过来的心跳,会将本地的key ,timestamp
与leader
带过来的key,timestamp
进行比较,如果本地少了这个key
,或者是key
对应的timestamp
低于leader
的话,就会发送请求去leader
那拉取不一致的数据