共识算法Raft
# 共识算法 Raft
参考视频:解读共识算法Raft (opens new window)
该算法来源于论文《In Search of an Understandable Consensus Algorithm》 (opens new window)
通过Raft 可视化 (opens new window)更加有利于对 Raft 的理解
# 1. Raft 简介
Raft,一个更易理解的共识算法
相比于 Paxos,Raft 的最大特性就是利于理解(Understandable),为了达到这个目标,Raft 主要做了两个方面的事情:
- 问题分解:把共识算法分为三个子问题
- 领导选举(leader election)
- 日志复制(log replication)
- 安全性(safety)
- 状态简化:对算法做出一些限制,减少状态数量和可能产生的波动
# 2. 复制状态机
复制状态机(Replicated state machine)的核心思想:
- 相同的初始状态 + 相同的输入 = 相同的结束状态
- 可理解为:从相同的初始状态开始,执行相同的一串命令,产生相同的最终状态。
在 Raft 中,leader 将客户端请求封装成一个个 log entry 中,将这些 log entries 复制到所有 follower 节点,然后大家按相同顺序应用 log entries 中的请求,根据复制状态机的理论,各节点的结束状态肯定一致。
客户端 client 给 leader 发送命令,leader 根据命令生成 log,并且发送给其他的 follower 节点,所有节点一起把 log 应用到自己的的状态机中。这时,无论 client 查询哪个节点,只要这个节点正常应用了日志,查询到的结果都是一致的。
可以说,使用共识算法就是为了实现复制状态机。一个分布式场景下的各节间就是通过共识算法来保证命令序列的一致,从而始终保持它们的状态一致,从而实现高可用。
# 3. 状态简化
⭐ 角色
在任意时刻,每个服务器节点都处于 Leader,Follower 或 Candidate 这三个状态之一。
任意一个节点启动时都是一个 Follower,当该节点察觉到集群中没有 Leader 的话,它会把自己的状态从 Follower 切换成 Candidate,在 Candidate 状态下经历一次或多次选举,最终根据选举的结果决定自己切换到 Leader 或 Follower 状态。
- Leader:负责发起心跳,响应客户端,创建日志,同步日志。
- Candidate:Leader 选举过程中的临时角色,由 Follower 转化而来,发起投票参与竞选。
- Follower:接受 Leader 的心跳和日志同步数据,投票给 Candidate。
⭐ 任期
Raft 把时间分割成任意长度的任期(term),用连续的整数标记。
每一段任期从一次选举开始,在一任期内,最多只有一个 leader。在某些情况下,一次选举无法选出 leader,这一任期会以没有 leader 结束。
任期的机制可以非常明确的标识集群的状态,通过任期的比较,可以确认一台服务器历史的状态。比如,可以查看一台服务器是否具有 t2 任期内的日志来判断它在 t2 时间段内是否宕机。
服务器之间的通信会交换当前任期号;如果一个服务器上的当前任期号比其他的小,该服务器会将自己的任期号更新为较大的值
如果一个 candidate 或 leader 发现称自己的任期号过期了,它会立即回到 follower 状态
如果一个节点接收到一个过期的任期号的请求,它会拒绝这个请求
⭐ 通信
Raft 算法中服务器节点之间使用 RPC 进行通信,由两种主要的 RPC:
- RequestVote RPC:请求投票,由 candidate 在选举期间发起
- AppendEntries RPC:追加条目,由 leader 发起,用来复制日志和提供心跳
# 4. 领导者选举
Raft 内部有一种心跳机制,如果存在 Leader,那么它就会周期性地向所有 Follower 发送心跳,来维持自己的地位。如果 Follower 一段时间没收到心跳,那么它就会认为系统中没有可用的 Leader 了,然后开始选举。
一个选举过程中,Follower 先增加自己的当前任期号,并转换到 Candidate 状态。然后投票给自己,并且并行的向集群中的其他服务节点发送投票请求(RequestVote RPC)
最终会有三种结果:
- 自己成为 Leader:它获得超过半数选票赢得选举,成为 Leader 开始发送心跳。
- 其他节点成为 Leader:其他节点赢得选举,它收到了新的 Leader 的心跳后,如果新 Leader 的任期号大于等于自己当前的任期号,那么从 Candidate 回到 Follower 状态。
- 未选出 Leader:一段时间后没有任何获胜者,每一个 Candidate 都在一个自己的「随机选举超时时间」后增加任期号开始新一轮投票,每一轮的投票都不需要和其他节点达成共识,只跟自己的「随机选举超时时间」有关。
- 没有选出的原因:多个 Follower 同时成为 Candidate,得票太过分散,没有任何节点超过半数。
- 论文中给出的「随机选举超时时间」为 150 ~ 300ms
⭐ 投票者的投赞成票条件
每个 Follower 只有一张选票,按照先来先得的原则投出。条件如下:
- Candidate 的 term 是否比自己大;
- Candidate 包含各任期所有被提交的日志条目,第六部分详细解释(埋个坑)。
⭐ RequestVote RPC
- 请求,由 Candidate 发起,论文中称为 Argument
- term:int,自己当前的任期号
- candidateId:int,自己的 ID
- lastLogIndex:int,自己最后一个日志号
- lastLogTerm:int,自己最后一个日志的任期
- 响应,由 Follower 回复 Candidate,论文中称为 Results
- term:int,自己当前任期号
- voteGranted:bool,自己会不会投给这个 Candidate
⭐ 参数解释
- term:Raft 节点通过 term 来确定自身的状态,以及判断接不接收这个 RPC。如果一个节点接收到一个过期的任期号的请求,它会拒绝这个请求。
- candidateId:Follower 需要知道自己投票给谁
- lastLogIndex:后面介绍
- LastLogTerm:后面介绍
- voteGranted:Follower 收到请求后会校验这个 Candidate 是否符合条件,符号条件返回 true。
Raft 集群启动时,所有的节点都是 Follower,当某一节点意识到集群中没有 Leader 节点时会开始竞选 Leader,它会把自己的任期号加一并发请求投票给其他 Follower,通常来说第一个意识到集群中没有 Leader 的节点具有新发优势,很大概率上会成为 Leader。在一个 Leader 任期结束或失效后,也同样会进入这样一个任期的循环。
# 5. 日志复制
难点开始了!
# 5.1 客户端请求
Leader 被选举出来后,开始为客户端请求提供服务。但客户端如何知道哪个节点是 Leader 呢?
客户端随机向一个节点发起请求,会有三种情况
- 该节点为 Leader,执行指令即可
- 该节点为 Follower,该节点可以通过心跳得知 Leader 的 ID,告知客户端该找谁
- 找到的节点宕机了,即无响应,客户端再去找其他节点重复上述过程
Raft 集群中只要有超过一半的节点正常工作,那么 Raft 集群就能正常提供服务
# 5.2 日志
Leader 接收到客户端的请求后会把相应的指令作为一个新的条目追加到日志中,日志具有三个信息,我们看图中 log index 行和 leader 行:
- 状态机的指令,通常为对某些值进行某些操作。如图,日志 1 就是将 x 赋值为 3;
- 任期号,用于检测多个日志副本之间的不一致以及判定节点状态。如图,方块上的数字就是任期号;
- 日志索引,区分日志前后关系。如图,最上面的数值就是日志索引。
由于某些节点宕机,有可能出现日志索引相同,但日志不同的日志。所以,只有日志索引和任期号两个条件才能唯一确定一个日志。
# 5.3 简单情况日志复制
Leader 并行发送追加条目请求(AppendEntries RPC) 给 Follower,让他们复制日志。当日志被超过半数的 Follower 复制后,Leader 就可以在本地执行该指令并把结果返回客户端。
AppendEntries RPC 的属性后面将介绍
我们把本地执行指令,也就是 Leader 应用日志到状态机这一步称为提交。
只要日志复制超过半数的节点后,Leader 就一定会提交?
不是,因为 Follower 复制完成,到 Follower 通知 Leader,再到 Leader 提交,这一过程是需要时间的,这个时间内如果 Leader 宕机了,那么日志将无法提交。
上图中,第一行为 Leader,后面四行是 Follower,它们的日志复制情况各不相同,只要有 3 个及以上节点(包括 Leader)复制到了日志,Leader 就可以提交。所以途中可以提交到的日志到 7.
# 5.4 复杂情况日志复制
在日志复制过程中,Leader 和 Follower 随时都有可能崩溃或缓慢,Raft 必须要在有宕机的情况下继续支持日志复制,并且保证每个副本日志顺序的一致性。下面分三种情况讨论:
⭐ 情况1:Follower 无响应
Follower 因为某些原因没有给 Leader 响应,那么 Leader 会不断地重发追加条目请求(AppendEntries RPC),即使 Leader 已经回复了客户端,但它仍不会放弃这个节点,会继续重发追加条目请求,直至 Follower 的日志追上 Leader。
⭐ 情况2:Follower 崩溃后恢复
Follower 崩溃后恢复,需要保证 Follower 能按顺序恢缺失的日志。
- 与情况 1 不同的是,可能在 Follower 崩溃的期间内 Leader 发生多次变更,当前 Leader 并不知 Follower 宕机前日志复制到哪里。
- 此时,通过一致性检查,Leader 在每一个发往 Follower 的追加条目请求中,会放入前一个日志条目的日志索引和任期号,如果 Follower 在它的日志中找不到前一个日志,那么他就会拒绝此日志,leader 收到 Follower 的拒绝后,会发送前一个日志条目,从而逐渐向前定位到 Follower 第一个缺失的日志,并按照顺序补齐所有的缺失的日志。
⭐ 情况3:Leader 崩溃
Leader 崩溃,新旧 Leader 未提交的日志不同,那么崩溃的 Leader 可能已经复制了日志到一部分 Follower 但还没有提交。而被选出的新 Leader 又可能不具备这些日志,这样有部一分 Follower 中的日志和新 Leader 的日志不相同。
Leader 通过强制 Follower 复制它的日志来解决不一致的问题,这意味着 Follower 中跟 Leader 冲突的日志条目会被新 Leader 的日志条目覆盖。因为没有提交,所以不违背一致性。
=
以上图为例,F 具有 2,3 任期的日志,而其他节点不具有,这大概意味着它在 2,3 任期内担任 Leader,但是这期间内的日志没有正常复制到大多数节点,所以无法提交。4,5,6 任期内,F 大概率宕机了,如果此时 F 突然恢复了,那么它在2,3 期间内的日志与当前 Leader 发生冲突,此时,Raft 会强制 F 复制 Leader 的日志来解决不一致的问题。Leader 会通过一致性检查找到 Follower 中最后一个和自己一致的日志,并把这之后冲突的日志全部覆盖。可以覆盖的原因是这些日志未提交,并不影响一致性。
因此,Leader 当权之后不需要任何特殊操作来使日志恢复一致性,只需要进行正常操作,日志就能在回复 AppendEntries 一致性检查失败的时候自动趋于一致。
一些说明:
Leader 从不会覆盖或者删除自己的日志条目。(Append-Only)
只要过半的服务器能正常运行,Raft 就能够接受、复制并应用新的日志条目;
在正常情况下,新的日志条目可以在一个 RPC 来回中被复制给集群中的过半机器;
单个运行慢的 Follower 不会影响整体的性能,他只要慢慢追上日志就行了,其他节点不需要去等他。
# 5.5 AppendEntries RPC
追加条目请求,由 Candidate 发起,论文中称为 Argument
- term:int,自己当前的任期号
- leaderId:int,Leader 的 ID
- prevLogIndex:int,前一个日志的日志号
- prevLogTerm:int,前一个日志的任期号
- entries[]:byte,当前日志体
- leaderCommit:int,Leader 的已提交日志号
追加条目响应,由 Follower 回复 Candidate,论文中称为 Results
- term:int,自己当前任期号
- success:bool,如果 Follower 包括前一个日志,则返回 True
通过上面的介绍,基本了解了日志复制的过程和一些特殊情况,下面来看看 AppendEntries RPC 的参数作用:
- term:Raft 节点通过 term 来确定自身的状态,以及判断接不接收这个 RPC。如果一个节点接收到一个过期的任期号的请求,它会拒绝这个请求。
- leaderId:Leader 用来告诉 Follower 自己是谁
- prevLogIndex、prevLogTerm:前一个日志条目的日志索引和任期号(两个条件确定一条日志),用来进行一致性检查,如果 Follower 在它的日志中找不到前一个日志,那么他就会拒绝此日志,Leader 收到 Follower 的拒绝后,会发送前一个日志条目,从而逐渐向前定位到 Follower 第一个缺失的日志,并按照顺序补齐所有的缺失的日志。
- entries:日志题,命令内容
- leaderCommit:Follower 接收到 Leader 的日志并不能立刻提交,因为还没有确认这个日志是否被复制到大多数节点,只有 Leader 确认了日志被复制到大多数节点后,Leader 才会提交日志,Leader 会通过设置 leaderCommit 告知 Follower 提交单哪里了,Follower 就可以把自己复制但未提交的日志设为已提交状态。只要 commitIndex < leaderCommit,这些小于 leaderCommit 的日志都可以提交。
- success:只要在请求大于等于自己 term,且请求通过一致性检查之后才会返回 true,否则返回 false
# 6. 安全性
难度进一步升级了!
领导者选举和日志复制两个子问题实际上已经覆盖了共识算法的全程,但这两点还不能完全保证每一个状态机会按照相同的顺序执行相同的命令。所以 Raft 通过几个补充规则完善整个算法,是算法在各类宕机问题下都不出错。这些规则包括:
- 选举限制
- Leader 宕机处理
- Follower 和 Candidate 宕机处理
- 时间与可用性限制
# 6.1 选举限制
Leader 宕机后的选举限制,前面第四部分我们讨论过投票者投出赞同票的条件:
- Candidate 的 term 比自己大
- Candidate 包含各任期所有被提交的日志条目,下面将详细说明这一点
如果一个 Follower 落后了 Leader 若干个条日志(但没有遗漏整个任期),那么下次选举中,按照领导者选举里的规则,它有可能当选 Leader。它在当选新 Leader 后就永远也无法补上之前缺失的那部分日志,从而造成状态机之间的不一致。
所以,需要对领导者选举增加一个限制,保证被选出来的新 Leader 一定包含之前各任期的所有被提交的日志条目,但新 Leader 中的这些日志可能只是复制还没被提交。
「被提交」这个概念后面详细介绍。
再来回顾一下 RequestVote RPC
- 请求,由 Candidate 发起,论文中称为 Argument
- term:int,自己当前的任期号
- candidateId:int,自己的 ID
- lastLogIndex:int,自己最后一个日志号
- lastLogTerm:int,自己最后一个日志的任期
- 响应,由 Follower 回复 Candidate,论文中称为 Results
- term:int,自己当前任期号
- voteGranted:bool,自己会不会投给这个 Candidate
其中 lastLogIndex 和 lastLogTerm 唯一确定最后一条日志,如果投票者的日志比 Candidate 的还“新”,那么他就会拒绝掉该投票请求。
如何比较两份日志最后一条日志条目谁最新:
- 两份日志任期号不同,任期号大的日志更“新”
- 两份日志日期号相同,日志索引大(日志较长)的日志更“新”
(a) 阶段, S1 是 Leader;(b) 阶段, S1 宕机,S5 通过 S3 和 S4 的选票赢得选举; (c) 阶段, S5 又崩溃了,S1 重启后又重新当选 Leader,此时日志 2 被复制到 S2 和 S3 上,但还没有提交; (d) 阶段, S1 再次宕机,S5 通过 S2,S3 和 S4 的选票再次选举成功,为什么 S2 和 S3 会投票给 S5 呢?因为它们的日志索引相同,但 S5 的任期号更大,投票符合规则。
(c) 阶段,此时日志 2(任期号为2,日志索引为2)已经复制到了大部分节点,达到了在 Leader 上提交的条件,却仍被覆盖了,这会不会不安全?下一个安全规则将进行说明。
# 6.2 Leader 宕机处理
Leader 宕机后,新 Leader 是否提交之前任期内的日志条目呢?
⭐ Raft 提交内容知识点补充:
- 当前任期内,若某个日志条目已经复制到过半的服务器节点上,Leader 就知道该日志条目可以被提交了
- 前面我们所描述的提交指的都是「单点提交」,针对于某一个节点的提交;「集群提交」,针对与整个集群的提交。
- Follower 通过 AppendEntries RPC 中的 leaderCommit 参数可以知道 Leader 提交到哪了,来判断自己的日志是否可以提交。有两种提交方式:
- 心跳,也是一种特殊的 AppendEntries RPC,没有日志体,但是有 leaderCommit 参数
- 新日志对应的 AppendEntries RPC
- Follower 提交后会返回给 Leader 成功标志,也就是 AppendEntries RPC 中的 success 参数。
- Leader 收到半数以上的 success 成功,就认为集群提交成功。
- 实际上 Leader 单点提交后就可以返回客户端,没必要等待集群提交。(这部分暂不深入讨论)
下面我们继续讨论新 Leader 对老 Leader 任期内日志的处理。
如果某个 Leader 在提交某个日志条目之前崩溃了,以后的 Leader 会试图完成该日志条目的复制,注意,是复制而不是提交。
通过选举限制我们知道,新 Leader 会具有老 Leader 已提交的日志,但这些老的日志在新 Leader 中还没有提交,但是新 Leader 不会提交这些日志,为什么不会提交呢?看下图:
下面所提到的日志 2 为任期号为 2 且索引为 2 的日志;日志 3 为任期号为 3 且索引为 2 的日志
- 阶段 (a),S1 为 Leader,将日志 2 复制给 S2,但未提交。
- 阶段 (b),S5 当选 Leader,还未开始复制日志 3 就宕机了。
- 阶段 (c),S1 重新当选 Leader,将日志 2 复制给 S3,但此时它无法提交日志,还未开始复制日志 4 就宕机了。
- 阶段 (d),S5 重新当选 Leader,复制日志 3 到所有节点。(S5 的任期为 3,可以拿到 S2,S3 和 S4 的选票)
阶段 (c),S1 重新当选 Leader,此时集群中有超过半数的节点包含日志 2,若此时 S1 提交日志 2,那么它将返回客户端命令执行成功。此时再来到阶段 (d),S5 重新当选 Leader,复制日志 3 到所有节点,已经被提交的日志 2 被覆盖,集群中不再包含日志 2。此时,客户端认为命令执行成功,但集群中却没有包含对应的执行过程。
所以,Leader 不会通过计算副本数目的方式来提交之前任期内的日志条目,只有 Leader 当前任期内的日志才通过计算副本数目来提交。(我的简单理解,Leader 不会提交之前任期内的日志,只能提交当前任期内的日志)
假设,没有经过阶段 (d),直接从阶段 (c) 来到了阶段 (e),S1 仍然为 Leader,它已经完成了日志 4 的复制,此时也可以完成日志 4 的提交,但是由于 Leader 不能提交之前任期内的日志,那么日志 2 该如何被提交呢?其实,当它日志 4 被提交的时候,日志 2 也会被自动提交。也就是说,新 Leader 在它的任期内新产生一个日志,在这个日志提交的时候,老 Leader 任期内的日志也就被提交了。
所以,一旦当前任期的某个日志条目以这种方式被提交,那么由于日志匹配特性,之前的所有日志条目也都会被间接地提交。
如果不理解的话,可以看视频 (opens new window)的第 11min,动态展示了这一过程。
# 6.3 Follow 和 Candidate 宕机
Follow 和 Candidate 宕机后,后续发给他们的 RequestVote 和 AppendEntries RPC 都会失败,Leader 通过无限次的重试来处理这种失败,如果崩溃的机器重启了,那么这些 RPC 就会成功的完成。
如果一个服务器在完成一个 RPC,但还没有响应的时候崩溃了,那么它重启后会再次收到同样的请求。重复收到相同的请求也没关系,因为 RPC 是幂等的。
# 6.4 时间与可用性限制
Raft 算法不依赖客观时间,即使网络或其他因素造成后发的 RPC 先到,也不会影响 Raft 的正确性。只要整个系统满足下面的时间要求,Raft 就可以选举并维持一个稳定的 Leader。
广播时间(broadcastTime)<< 选举超时时间(Election Timeout)<< 平均故障时间(MTBF)
- 广播时间,由系统决定,Raft 需要接受并将详细存储,时间大约为 0.5ms 到 20ms,取决于存储技术
- 平均故障时间,由系统决定,大部分服务器的平均故障时间将都在几个月以下
- 选举超时时间,如果一次网络来回的时间大于节点的选举超时时间,那么 Leader 在收到选举反馈之前就会因为超时开启下一轮选举,就永远选不出 Leader,所以选举超时时间要大于广播时间,因此可以选择 10ms 到 500ms 之间。
# 7. 集群成员变更
多节点变更法,joint consensus,该方法存在太多边界情况,违背 Raft 简单设计的初衷。现在大部分堆 Raft 算法的实现都是基于单节点变更的方法。单节点变更可以极大简化实现难度。
# 7.1 脑裂问题
在需要改变集群配置的时候,如增减节点、替换宕机的机器或改变复制的程度,Raft 可以进行配置变更自动化。而配置变更自动化机制最大的难点是保证转换过程中不会在同时出现两个 Leader(脑裂问题),因为转换期间整个集群可能划分为两个独立的大多数。
扩容是一个逐步变更的过程,所有节点不可能在同一时间完成,上图,从 3 节点扩容到 5 节点,当处于方框所选阶段时,S1 和 S2 处于老配置,它们认为集群中只有 S1,S2 和 S3 三个节点,只要获取两票就可以当选 Leader;S3、S4 和 S5 处于新配置,它们认为集群有 S1 ~ S5 五个节点,需要获得三票才可以当选 Leader。
这时,S1 和 S2 两票可以选出一个 Leader,S3 ~ S5 三票可以选出一个 Leader,集群中可能就出现两个 Leader 了,这就是脑裂问题。
# 7.2 两阶段转换
Raft 采用两阶段方法解决脑裂问题,集群先从老配置切换到一个过度配置,再从过度配置切换到新配置。这个过度配置称为联合一致(joint consnsus),我们只需要关注怎样避免在联合一致状态下发生脑裂问题。
配置信息作为一个日志体包装为一个普通的 AppendEntries RPC,发送给所有的 Follower
- 第一阶段:Leader 发起 Cold,new,使整个集群进入联合一致状态。这时,所有 RPC 都要在新旧两个配置中达到大多数才算成功。
- 第二阶段:Leader 发起 Cnew,使整个集群进入新配置状态。这时,所有 RPC 只要在新配置下能达到大多数就算成功。
一旦某个服务器将该新配置日志条目增加到自己的日志中,它就会用该配置做出未来所有的决策,这就意味着无论新配的日志条目是否提交,服务器总是会使用它日志中最新的配置,而 Leader 只要发起 Cold,new 或 Cnew,就会按照联合一致或者新配置来执行。
我们分别讨论 Leader 在 Cold, Cold,new 和 Cnew 下的宕机情况,分析是否会发生脑裂问题。
- Leader 在 Cold,new 未提交时宕机
- Leader 在 Cold,new 已提交但 Cnew 未发起时宕机
- Leader 在 Cnew 发起时宕机
起初,集群中有三个节点,S3 作为 Leader,此后 S4, S5 加入集群,此时它们为只读状态,等到它们追上日志进度后才会开始集群成员变更。然后,Leader S3 发起 Cold,new ,并复制给 S4 和 S5,此时 S3 ~ S5 已经进入联合一致状态,它们的决策要在新旧两个配置中都达到大多数才算成功。现在考虑上面提到的三种宕机情况:
⭐ Leader 在 Cold,new 未提交时宕机
- S1,S2 超时开始进行选举,只要其中一个节点拿到 2 票,比如 S1,S2 都投给了 S1,此时 S1 就是老配置的 Leader;
- S3 ~ S5 中的节点想要当选 Leader,需要在老配置 S1 ~ S3 和 新配置 S1 ~ S5 下都拿到超过半数选票。比如,S3 可以拿到 S3 ~ S5 的选票,超过新配置投票的一半以上,但 S3 拿不到 S1,S2 的选票,因为 S1,S2 都投给了 S1,所以 S3 无法获取老配置一半以上的投票,所以新配置中无法产生一个 Leader.
- 此时,集群成员变更失败,但不会出现脑裂问题,仍保证了 Raft 的正确性。
下面看一下特殊情况:
如果在新配置中重新选出了 S1 作为 Leader,新 Leader 具有 Cold,new,但由于安全性的限制,新 Leader 无法提交 Cold,new,不过它可以发送 Cnew 继续进行集群成员变更,他把 Cold,new 复制到了 S3 ~ S4.
但这时 Cnew 能够提交吗?由于 S1 没有得到 S3 的反馈,这里提交 Cnew 是不安全的。论文中没有给出这种情况的处理方法。在某些实现中可以让 Cnew 按照联合一致的规则提交。如果当前 Leader 在一段时间内还满足不了这个提交条件,那么他就会自动退位。
⭐ Leader 在 Cold,new 已提交但 Cnew 未发起时宕机
正常情况下,Leader S3 未宕机,S3 的 Cold,new 日志在新旧两种配置的集群中都超过了半数,Cold,new 可以提交了。
Leader 在 Cold,new 已提交但 Cnew 未发起时宕机了,由于选举限制,旧配置的节点只能投票给新配置的节点,所以选出的新 Leader 一定具有 Cold,new ,此时不存在脑裂的可能。
需要注意的时,联合一致状态下,集群也是可以正常执行命令的,但也需要在新旧配置集群中达到大多数才能提交。
⭐ Leader 在 Cnew 发起时宕机
Cold,new 提交后,Leader 就会发起 Cnew,这时 Leader 只要满足新配置的条件,就可以提交日志了。
要发起 Cnew,意味着 Cold,new 已经被复制到了大多数节点,就不需要再去管老配置了。
Leader 在 Cnew 发起时宕机,已经复制 Cnew 的节点会按新配置选举,没有复制 Cnew 的节点会按老配置选举。没有复制 Cnew 的节点选举成功也会法 Cnew。此时也不会出现脑裂问题。
下图为缩减节点的情况下,由 S1 ~ S5 缩减为 S1 ~ S3,Leader S3 将 Cnew 复制到 S1 ~ S3 中的两个就可以提交了,也就是说 S2 复制完成,S3 就能提交了。如果 Leader S3 宕机了,那么此时Cnew 会不会被覆盖呢?
不会的,因为处于联合一致状态的节点,也就是只复制了 Cold,new 没有复制 Cnew 的节点,必须要在两个集群都得到大多数选票才能选举成功。而 S2,S3 不会投给 S1,S4 和 S5 中的任意个,所以 S3 宕机了,只有 S2 才能当选,已提交的 Cnew 不会被覆盖。
# 7.3 补充规则
- 新增节点时,需要等新增的节点完成日志同步再开始集群成员变更。这点是防止集群在新增节点还未同步日志时就进入联合一致状态或新配置状态,影响正常命令日志的提交。只需要让新节点在同步完成日志前不具有投票权,也不参与日志计数,也就是处于一个只读的状态。
- 缩减节点是,Leader 本身可能就是要缩减的节点,这时它会在完成 Cnew 的提交后自动退位。
- 为了避免下线的节点超时选举而影响集群的运行,服务器会在它确信集群中有 leader 存在时拒绝 RequestVote RPC。只要 Follower 连续收到 Leader 的心跳,那么推出集群节点的 RquestVote RPC 就不会影响到 Raft 集群的正常运行。