一、角色模型
(一) Raft-node 存在三种角色:
1、Follower:接受来自leader和Candidate 的请求,节点启动以后的初始化状态一定是Follower
2、Leader:处理来自客户端的请求,并且承担log复制到Follower的工作
3、Candidate:发起投票,竞选Leader(Follower 超时变成 Candidate)
(二) Raft Strong Leader
1、Raft是限制仅有Leader节点可以处理写入操作
2、Leader负责与所有的Follower节点进行通信,负责将log复制到Follower节点,并收集大多数节点的应答
3、Leader需要向所用的Follower节点发起心跳,保持和确认Leader地位
二、选举
(一) Terms
时间被划分为一个个任期 (term),term id 按时间轴单调递增;
每一个任期的开始都是 Leader 选举,选举成功之后,Leader 在任期内管理整个集群,也就是 “选举 + 常规操作”
每个任期最多一个 Leader,可能没有 Leader (spilt-vote 导致)。
(二) 选举示意图
- 在系统初始化的时候,所有节点都出于Follower的角色。
2. 每个节点随机分配了倒计时时间。倒计时结束以后,节点从Follower --> Candidate,并发起投票
3. B、C节点只收到了节点A的投票请求,于是都投票给节点A,节点A成为Leader角色,并与定时向所有的Follower节点发起Heartbeat,保持和确认自己Leader角色。
上面描述了只有一个Candidate角色时,是如何发起投票的,如果同时有多个Candidate节点发起投票怎么办呢??
节点A和节点D同时成为Candidate角色发起投票,并获取到一半的票数,都不满足得到大多数票数的条件,因此会重新开始倒计时(时间是随机的),最先倒计时结束的发起新一轮投票
节点B和节点C投票给节点A以后,就会拒绝投票给节点D,节点A晋升为Leader节点,并且发起Hearbeat,节点D收到HeartBeat以后自动转化为Follower节点。
(三) 选举限制
在任何基于领导人的一致性算法中,领导人都必须存储所有已经提交的日志条目。在某些一致性算法中,例如 Viewstamped Replication,
某个节点即使是一开始并没有包含所有已经提交的日志条目,它也能被选为领导者。
Raft 使用投票的方式来阻止一个候选人赢得选举除非这个候选人包含了所有已经提交的日志条目。候选人为了赢得选举必须联系集群中的大部分节点,
这意味着每一个已经提交的日志条目在这些服务器节点中肯定存在于至少一个节点上。如果候选人的日志至少和大多数的服务器节点一样新(这个新的定义会在下面讨论),
那么他一定持有了所有已经提交的日志条目。请求投票 RPC 实现了这样的限制: RPC 中包含了候选人的日志信息,然后投票人会拒绝掉那些日志没有自己新的投票请求。
Raft 通过比较两份日志中最后一条日志条目的索引值和任期号定义谁的日志比较新。如果两份日志最后的条目的任期号不同,那么任期号大的日志更加新。
如果两份日志最后的条目任期号相同,那么日志比较长的那个就更加新。
三、日志复制
(一) 日志复制原理介绍
一旦一个领导人被选举出来,他就开始为客户端提供服务。客户端的每一个请求都包含一条被复制状态机执行的指令。领导人把这条指令作为一条新的日志条目附加到日志中去,然后并行的发起附加条目 RPCs 给其他的服务器,让他们复制这条日志条目。
当这条日志条目被安全的复制(下面会介绍),领导人会应用这条日志条目到它的状态机中然后把执行的结果返回给客户端。如果跟随者崩溃或者运行缓慢,再或者网络丢包,领导人会不断的重复尝试附加日志条目 RPCs (尽管已经回复了客户端)直到所有的跟随者都最终存储了所有的日志条目。
日志由有序序号标记的条目组成。每个条目都包含创建时的任期号(图中框中的数字),和一个状态机需要执行的指令。一个条目当可以安全的被应用到状态机中去的时候,就认为是可以提交了。
在上图中,每一条日志条目中存储了一条状态机指令,和收到这条指令时的任期,并且每条日志条目按index顺序存储。raft算法的一致性模块,觉得了那些日志条目被commited,并且将对应的命令应用到状态机中
raft的日志系统存在一下特性:
- 如果在不同的日志中的两个条目拥有相同的索引和任期号,那么他们存储了相同的指令。
- 如果在不同的日志中的两个条目拥有相同的索引和任期号,那么他们之前的所有日志条目也全部相同
(二) 日志的一致性保证
在Raft算法中,领导人处理不一致是通过强制跟随者直接复制自己的日志来解决。这意味着在跟随者中的冲突的日志条目会被领导人的日志覆盖
使得跟随者的日志进入和自己一致的状态,领导人必须找到最后两者达成一致的地方,然后删除从那个点之后的所有日志条目,发送自己的日志给跟随者。
所有的这些操作都在进行附加日志 RPCs 的一致性检查时完成。领导人针对每一个跟随者维护了一个 nextIndex,这表示下一个需要发送给跟随者的日志条目的索引地址。
当一个领导人刚获得权力的时候,他初始化所有的 nextIndex 值为自己的最后一条日志的index加1(图 7 中的 11)。如果一个跟随者的日志和领导人不一致,
那么在下一次的附加日志 RPC 时的一致性检查就会失败。在被跟随者拒绝之后,领导人就会减小 nextIndex 值并进行重试。最终 nextIndex 会在某个位置使得领导人和跟随者的日志达成一致。
四、网络分区
(一) Symmetric network partition tolerance(对称网络分区容忍性)
如上图 S1 为当前 leader,网络分区造成 S2 不断增加本地 term,为了避免网络恢复后 S2 发起选举导致正在良心 工作的 leader step-down,从而导致整个集群重新发起选举,
可以增加了 pre-vote 过程来避免这个问题的发生。在 request-vote 之前会先进行 pre-vote(currentTerm + 1, lastLogIndex, lastLogTerm),多数派成功后才会转换状态为 candidate 发起真正的 request-vote,
所以分区后的节点,pre-vote 不会成功,也就不会导致集群一段时间内无法正常提供服务。
(二) Asymmetric network partition tolerance(非对称网络分区容忍性)
如上图 S1 为当前 leader,S2 不断超时触发选主,S3 提升 term 打断当前 lease,从而拒绝 leader 的更新。
可以增加了一个 tick 的检查,每个 follower 维护一个时间戳记录下收到 leader 上数据更新的时间(也包括心跳),只有超过 election timeout 之后才允许接受 request-vote 请求。
参考文档
https://juejin.im/post/5c88756a6fb9a049f9136c1a
https://github.com/maemual/raft-zh_cn/blob/master/raft-zh_cn.md
https://www.jianshu.com/p/8e4bbe7e276c