通过示例分解graft底层代码。
graft运行环境如下,
-
nats server:单点。运行在localhost:4222。其中topic有3个,分别是:
- vote_request:发起投票
- vote_response:响应投票
- heartbeat:发送心跳
-
graft-example:集群数量为3,RPC通过nats server相互通信,通信协议为protobuf。
架构
节点起来后,会与nats server建立初始化连接。具体的消息队列topic说明如下:
-
cluster.heartbeat:
- 集群所有节点都订阅相同的topic,例如:graft.cluster.vote_request。
- 订阅该topic:接收集群中其他的节点的心跳。
- 向该topic发送心跳消息
-
vote_request:
- 集群所有节点都订阅相同的topic,例如:graft.cluster.vote_request。
- 订阅该topic:接收其他节点发出的投票。
- 向该topic发送投票消息
-
node.response
- 各个节点订阅各自的response topic:接收投票响应
本节描述raft协议的大体流程介绍。
本节描述集群启动后的状态变化过程:
以Node1为例,描述获得Leader角色的状态变化
- Node1启动后,先以Follower角色启动。
- 此时,节点中没有Leader节点,也即没有收到Heartbeat消息,则会触发Term Timeout,转换为Candidate角色。
- 向集群其他节点发送选举投票消息。
- 接收到其他节点确认投票消息后,达到(N/2 + 1)选票数后,则标志为自己为Leader节点。
- Node1节点作为Leader节点,向集群中其他节点发送心跳消息来维持集群中的Leader角色。
本节描述集群运转过程中,此时若有新节点加入。
以Node2为例,描述新节点加入后的状态变化
- Node2启动后,先以Follower角色启动。
- 此时,集群中由于有Node1作为Leader节点在发送Heartbeat消息,则Node2会接收到Heartbeat的消息。
- Node2设置Node1为Leader节点,并设置状态为当前集群的状态。(term、log index等等)
本节描述graft源码中,详细的代码分析。
初始化 - 以Follower状态启动
假设集群刚被初始化,当前没有任何节点处于Leader节点状态,也即没有收到任何节点的心跳信息。
- 节点启动后,状态为Follower。
- 由于当前集群没有Leader,导致当前term的选举超时(election timeout),节点转换为Candidate,同时更新term为term+1。各个节点的超时时间是随机超时时间,假设此时Node1首先超时。
我们将列举Node1获得leader状态的过程:
-
向集群中发起投票。
- 投票给自己
- 通过nats server的cluster.vote_request队列中发出投票。
-
根据投票结果,进行不同的状态转换,
- 赢得多数投票:将当前节点更新为Leader节点。
- 未赢得多数投票。下列详细分析:
发起投票后,在等待投票结果的过程中,会出现下列的情况,
a. 当前选举周期超时,即没有节点获取到多数投票。此时重置状态,重新进行投票选举。
b. 从node1.response中获取其他节点的响应:
- 判断response是否有效:是否投票给自己;是否是当前的term投票。
- 若response有效,则选票计数+1。
- 若获得多数选票,则切换为Leader节点。
- 否则,继续等待其他节点的投票结果。
c. 从cluster.vote_request中获取其他节点的投票请求:
- 如果发起投票的请求term落后于node1的term,则发送拒绝response;
- 如果对于当前term,node1已经投票给其他节点,则发送拒绝response;
- 如果node1接收到的其他节点的request中的term大于node1的term,则更新term到更新的term,并重置当前term的信息,包括:投票信息和leader信息。
- 如果node1已经是leader,则直接返回拒绝票。
- 否则,上述都没有触发。则投票给请求者。
d. 从cluster.heartbeat中获取集群中的心跳信息:
- 如果当前节点还没有设定Leader,则设置心跳信息中的Leader为当前节点的Leader。
- 如果当前节点已经有设定Leader,则需要根据term的比对进行处理:
- 当前term > 心跳term,则忽略心跳请求。
- 心跳term > 当前term,则更新term到心跳term,且重置投票状态,并且更新当前节点的Leader为心跳节点的Leader。
- 心跳term == 当前term,且当前节点为Leader节点,则忽略。
- 心跳term == 当前term,且当前节点为Candidate状态,则投票给心跳节点的Leader。