目录
-
Raft 基础
-
选举
-
日志复制
-
脑裂情况
大名鼎鼎的 Paxos 算法可能不少人都听说过,几乎垄断了一致性算法领域,在 Raft 协议诞生之前,Paxos 几乎成了一致性协议的代名词。
但是对于大多数人来说,Paxos 算法太难以理解了,而且难以实现。因此斯坦福大学的两位教授 Diego Ongaro 和 John Ousterhout 决定设计一种更容易理解的一致性算法,最终提出了 Raft 算法!
Raft 是一种更为简单方便易于理解的分布式算法,主要解决了分布式中的一致性问题。
相比传统的 Paxos 算法,Raft 将大量的计算问题分解成为了一些简单的相对独立的子问题,并有着和 Multi-Paxos 同样的性能,下面我们通过动图,以后还原 Raft 内部原理。
Raft 基础
| 名词解释
Raft 协议一共包含如下 3 类角色:
-
Leader(领袖):领袖由群众投票选举得出,每次选举,只能选出一名领袖;
-
Candidate(候选人):当没有领袖时,某些群众可以成为候选人,然后去竞争领袖的位置;
-
Follower(群众):这个很好理解,就不解释了。
然后在进行选举过程中,还有几个重要的概念:
-
Leader Election(领导人选举):简称选举,就是从候选人中选出领袖;
-
Term(任期):它其实是个单独递增的连续数字,每一次任期就会重新发起一次领导人选举;
-
Election Timeout(选举超时):就是一个超时时间,当群众超时未收到领袖的心跳时,会重新进行选举。
| 角色转换

这幅图是领袖、候选人和群众的角色切换图,我先简单总结一下:
-
群众 -> 候选人:当开始选举,或者“选举超时”时
-
候选人 -> 候选人:当“选举超时”,或者开始新的“任期”
-
候选人 -> 领袖:获取大多数投票时
-
候选人 -> 群众:其它节点成为领袖,或者开始新的“任期”
-
领袖 -> 群众:发现自己的任期 ID 比其它节点分任期 ID 小时,会自动放弃领袖位置
-
备注:后面会针对每一种情况,详细进行讲解。
选举
| 情况 1:领导人选举
为了便于后续的讲解,我画了一副简图,“选举定时器”其实就是每个节点的“超时时间”。

成为候选人:每个节点都有自己的“超时时间”,因为是随机的,区间值为 150~300ms,所以出现相同随机时间的概率比较小,因为节点 B 最先超时,这时它就成为候选人。

选举领导人:候选人 B 开始发起投票,群众 A 和 C 返回投票,当候选人 B 获取大部分选票后,选举成功,候选人 B 成为领袖。

心跳探测:为了时刻宣誓自己的领导人地位,领袖 B 需要时刻向群众发起心跳,当群众 A 和 C 收到领袖 B 的心跳后,群众 A 和 C 的“超时时间”会重置为 0,然后重新计数,依次反复。
这里需要说明一下,领袖广播心跳的周期必须要短于“选举定时器”的超时时间,否则群众会频繁成为候选者,也就会出现频繁发生选举,切换 Leader 的情况。

| 情况 2:领袖挂掉情况
当领袖 B 挂掉,群众 A 和 C 会的“选举定时器”会一直运行,当群众 A 先超时时,会成为候选人,然后后续流程和“领导人选举”流程一样,即通知投票 -> 接收投票 -> 成为领袖 -> 心跳探测。

| 情况 3:出现多个候选者情况
当出现多个候选者 A 和 D 时,两个候选者会同时发起投票,如果票数不同,最先得到大部分投票的节点会成为领袖;如果获取的票数相同,会重新发起新一轮的投票。

当 C 成为新的候选者,此时的任期 Term 为 5,发起新一轮的投票,其它节点发起投票后,会更新自己的任期值,最后选择新的领袖为 C 节点。

日志复制
| 复制状态机
复制状态机的基本思想是一个分布式的状态机,系统由多个复制单元组成,每个复制单元均是一个状态机,它的状态保存在操作日志中。
如下图所示,服务器上的一致性模块负责接收外部命令,然后追加到自己的操作日志中,它与其他服务器上的一致性模块进行通信,以保证每一个服务器上的操作日志最终都以相同的顺序包含相同的指令。
一旦指令被正确复制,那么每一个服务器的状态机都将按照操作日志的顺序来处理它们,然后将输出结果返回给客户端。

| 数据同步流程
数据同步流程,借鉴了“复制状态机”的思想,都是先“提交”,再“应用”。
当 Client 发起数据更新请求,请求会先到领袖节点 C,节点 C 会更新日志数据,然后通知群众节点也更新日志,当群众节点更新日志成功后,会返回成功通知给领袖 C,至此完成了“提交”操作。
当领袖 C 收到通知后,会更新本地数据,并通知群众也更新本地数据,同时会返回成功通知给 Client,至此完成了“应用”操作,如果后续 Client 又有新的数据更新操作,会重复上述流程。

| 日志复制原理
每一个日志条目一般包括三个属性:整数索引 Log Index、任期号 Term 和指令 Commond。
每个条目所包含的“整数索引”即该条目在日志文件中的槽位,“任期号”对应到图中就是每个方块中的数字,用于检测在不同服务器上日志的不一致问题,指令即用于被状态机执行的外部命令,图中就是带箭头的数字。
领导人决定什么时候将日志条目应用到状态机是安全的,即可被提交的呢?一旦领导人创建的条目已经被复制到半数以上的节点上了,那么这个条目就称为可被提交的。
例如,图中的 9 号条目在其中 4 节点(一共 7 个节点)上具有复制,所以 9 号条目是可被提交的;但条目 10 只在其中 3 个节点上有复制,因此 10 号条目不是可被提交的。

一般情况下,Leader 和 Follower 的日志都是保存一致的,如果 Leader 节点在故障之前没有向其它节点完全复制日志文件之前的所有条目,会导致日志不一致问题。
在 Raft 算法中,Leader 会强制 Follower 和自己的日志保存一致,因此 Follower 上与 Leader 的冲突日志会被领导者的日志强制覆写。
为了实现上述逻辑,就需要知道 Follower 上与 Leader 日志不一致的位置,那么 Leader 是如何精准找到每个 Follower 日志不一致的那个槽位呢?
Leader 为每一个 Follower 维护了一个 nextlndex,它表示领导人将要发送给该追随者的下一条日志条目的索引,当一个 Leader 赢得选举时,它会假设每个 Follower 上的日志都与自己的保持一致。
于是先将 nextlndex 初始化为它最新的日志条目索引数 +1,在上图中,由于 Leader 最新的日志条目 index 是 10 ,所以 nextlndex 的初始值是 11。
当 Leader 向 Follower 发送 AppendEntries RPC 时,它携带了(item_id,nextIndex - 1)二元组信息,item_id 即为 nextIndex - 1 这个槽位的日志条目的 term。
Follower 接收到 AppendEntries RPC 消息后,会进行一致性检查,即搜索自己的日志文件中是否存在这样的日志条目。
如果不存在,就像 Leader 返回 AppendEntries RPC 失败,然后领导人会将 nextIndex 递减,然后进行重试,直到成功为止。
之后的逻辑就比较简单,Follower 将 nextIndex 之前的日志全部保留,之后的全部删除,然后将 Leader 的 nextIndex 之后的日志全部同步过来。
上面只是讲述了方法,下面举个例子,加深一下理解,还是以上面的图为例。Leader 的 nextlndex 为 11,向 b 发送 AppendEntries RPC(6,10),发现 b 没有,继续发送(6,9)(6,8) (5,7) (5,6) (4,5),最后发送(4,4)才找到。
所以对于 b,nextlndex=4 之后的日志全部删除,然后将 Leader 的 nextlndex=4 的日志全部追加过来。
脑裂情况
当网络问题导致脑裂,出现双 Leader 情况时,每个网络可以理解为一个独立的网络,因为原先的 Leader 独自在一个区,所以向他提交的数据不可能被复制到大多数节点上,所以数据永远都不会提交,这个可以在第 4 幅图中提现出来(SET 3 没有提交)。

当网络恢复之后,旧的 Leader 发现集群中的新 Leader 的 Term 比自己大,则自动降级为 Follower,并从新 Leader 处同步数据达成集群数据一致,同步数据的方式可以详见“日志原理”。

本文深入介绍了Raft一致性算法,包括选举过程中的领袖、候选人和群众角色转换,日志复制的原理及数据同步流程,以及在网络分区导致的双领袖情况下的解决策略。Raft算法以其简洁易懂的特点,成为Paxos的替代方案,确保分布式系统中的一致性。
322

被折叠的 条评论
为什么被折叠?



