一个更易理解的共识算法(论文原文为In Search of an Understandable Consensus Algorithm)
Diego Ongaro
在研究 Paxos 算法时,就深受其复杂性困扰。他觉得 Paxos 算法是一门极其难懂的算法,其工程化实践更是困难重重,原始的 Paxos 算法不经过一番修改很难应用于工程之中,而修改后的 Paxos 算法又很难保证其正确性。他总结出 Paxos 算法有两个大问题:
状态描述:
- 所有节点开始的时候都处于Follower状态,此时,第一个认识到集群中没有Leader的节点会把自己变成candidate
- 节点处于Candidate状态时,会发生一次或多次选举,最后根据选举结果决定自己时切换回Follower状态还是切换到Leader状态
- 如果切换到Leader状态,就会在Leader状态为客户端提供服务
- 如果节点在Leader状态的任期结束,或者是节点宕机亦或者其他的问题,就会切换回Follower状态,并开始下一个循环
任期的机制可以非常明确地表示集群的状态,而通过任期的比较,也可以确立一台服务器历史的状态
比如我们可以通过查看一台服务器是否具有在 t 2 t_2 t2任期内的日志,判断该服务器在 t 2 t_2 t2任期内是否宕机
相比其他共识算法十多种的通信类型,Raft算法的精简设计,极大减少了理解和实现的成本
信息解读:
S 5 S_5 S5是一个Leader,它向其它所有server发送心跳消息,来维持自己的地位
如果一个Server在它的进度条读完之前仍没有收到 S 5 S_5 S5的心跳消息的话,该server就会认为系统中没有可用的leader,然后开始选举.
开始一个选举过程后,follower先增加自己的当前任期号,并转换到candidate状态。然后投票给自己,并且并行地向集群中的其他服务器节点发送投票请求(
RequestVote RPC
)。最终会有三种结果:
它获得超过半数选票赢得了选举-> 成为Leader并开始发送心跳(告知集群中存在Leader),结束投票选举阶段
其他节点赢得了选举->收到新leader的心跳后,如果新leader的任期号不小于自己当前的任期号(任期号大概率相等),那么就从candidate回到follower状态。
一段时间之后没有任何获胜者->每个candidate都在一个自己的随机选举超时时间后增加任期号开始新一轮投票。
为什么会没有获胜者?
- 比如有多个follower同时成为candidate,得票太过分散,没有任何一个candidate得票超过半数,进入下一轮选举.
- "注意:当前选举阶段并没有产生任何Leader"这个结论不需要集群所有节点对此产生共识,而是通过每个candidate都在等待一个随机选举超时时间之后,默认去进入下一个选举阶段。
论文中给出的随机选举超时时间为 150~300ms,这意味着如果candidate没有收到超过半数的选票,也没有收到新Leader的心跳,那么他就会在150到300毫秒之间随机选择一个时间再次发起选举.
//请求投票RPC Request,由candidate发起
type RequestVoteRequest struct{
term int //自己当前的任期号 所有节点都带有任期号,因为raft的节点要通过任期号来确定自身的状态,以及判断接不接收这个RPC.
candidateld int //自己的ID Follower需要知道自己投票给谁
lastLogIndex int //自己最后一个日志号
lastLogTerm int //自己最后一个日志的任期
}
//请求投票RPC Response,由Follower回复candidate
type RequestVoteResponse struct{
term int //自己当前任期号
voteGranted bool //自己会不会投票给这个candidate
}
Follower的投票逻辑:
所有节点开始的时候都处于Follower状态,此时,第一个认识到集群中没有Leader的节点会把自己变成candidate
他会给自己的任期号加一并发请求投票request给其他follower。
收到一个requestVoteRequest之后会先校验这个candidate是否符合条件。
term是否比自己大?
与Request的后两个字段相关,我们之后再进行讲解
确认无误后开始投票,没有成为candidate的follower节点,对于同一个任期,会按照先来先得的原则投出自己的选票。
为什么RequestVoteRPC中要有candidate最后一个日志的信息呢,安全性子问题中会给出进一步的说明。
Leader被选举出来后,开始为客户端请求提供服务。
客户端怎么知道新leader是哪个节点呢?
Leader接收到客户端的指令后,会把指令作为一个新的条目追加到日志中去。
一条日志中需要具有三个信息:
只有任期号和日志号一起看才能确定一个日志
在上图中,最上面的一行代表Leader,其余四行代表Follower,可以观察到,Follower节点的进度不一定是一致的
但是在这里,只要有三个节点(包括Leader在内)复制到了日志,Leader就可以提交了,在上图中可以提交的日志号到7
在此过程中,leader或follower随时都有崩溃或缓慢的可能性,Raft必须要在有宕机的情况下继续支持日志复制,并且保证每个副本日志顺序的一致(以保证复制状态机的实现)。具体有三种可能:
对于上述问题的第三点,我们以上图举例:
总结一下:
//追加日志RPC Request
type AppendEntriesRequest struct {
term int //自己当前的任期号
leaderld int //leader(也就是自己)的ID,告诉follower自己是谁
prevLogIndex int //前一个日志的日志号 用于进行一致性检查
prevLogTerm int //前一个日志的任期号 用于进行一致性检查,只有这两个都与follower中的相同,follower才会认为日志是一致的
//如果只有日志号相同,这可能就是上图中f的情况,依旧需要向前回溯
entries []byte //当前日志体,也就是命令内容
leaderCommit int //leader的已提交日志号
}
//追加日志RPC Response
type AppendEntriesResponse struct{
term int // 自己当前任期号
success bool //如果follower包括前一个日志,则返回true
}
提交详解:
Request:
- 对于follower而言,接收到了leader的日志,并不能立即提交,因为这时候还没有确认这个日志是否被复制到了大多数节点。
- 只有leader确认了日志被复制到大多数节点后,leader才会提交这个日志,也就是应用到自己的状态机中
- 然后leader会在AppendEntries RPC中把这个提交信息告知follower(也就是上面的leaderCommit).
- 然后follower就可以把自己复制但未提交的日志设为已提交状态(应用到自己的状态机中).
- 对于还在追赶进度的follower来说,若leaderCommit大于自己最后一个日志,这时它的所有日志都是可以提交的
Response:
- 这个success标志只有在request的term大于等于自己的term,且request通过了一致性检查之后才会返回true,否则都返回false
Leader宕机处理:选举限制
如果仅仅依靠投票选举子问题中的规则会出现这种情况:如果一个follower落后了leader若干条日志(但没有漏一整个任期),那么下次选举中,按照领导者选举里的规则,它依旧有可能当选leader。它在当选新leader后就永远也无法补上之前缺失的那部分日志,从而造成状态机之间的不一致。
所以需要对领导者选举增加一个限制,保证被选出来的leader一定包含了之前各任期的所有被提交的日志条目。
而raft实现这个限制的方法就是依靠RequestVote RPC
中的后两个参数.
回顾一下这两个参数
//请求投票RPC Request,由candidate发起
type RequestVoteRequest struct{
term int //自己当前的任期号 所有节点都带有任期号,因为raft的节点要通过任期号来确定自身的状态,以及判断接不接收这个RPC.
candidateld int //自己的ID Follower需要知道自己投票给谁
lastLogIndex int //自己最后一个日志号
lastLogTerm int //自己最后一个日志的任期
}
RequestVote RPC
执行了这样的限制:RPC中包含了candidate的日志信息,如果投票者自己的日志比candidate的还新,它会拒绝掉该投票请求.
Raft通过比较两份日志中最后一条日志条目的索引值和任期号来定义谁的日志比较新。
Leader宕机处理:新leader是否提交之前任期内的日志条目
- a中,S1是leader
- b中,S1崩溃了,S5通过S3S4的选票赢得选举
- 到了c中,S5又崩溃了,S1恢复了并赢得选举成为leader,此时日志2已经被复制到了大多数机器上,但还没有被提交(此时日志2是老日志,它不能被立即提交)
- 到了d中,S1再次崩溃,S5通过S2S3S4的选票再次选举成功,并将日志3复制到follower上
- 在这里我们可以观察到,哪怕S1把日志2复制到了大多数节点,最终还是会被日志3覆盖(即没有在集群中提交日志2)
- 在这里我们提出一种假设
- 假设S1重新当选leader(c时),在S1S2S3中都把日志2提交了 ,这时候可以认为日志2已经提交了,也可以返回客户端提交成功
- 但是这时候S1宕机了,集群重新选举,S5当选,这时raft会通过强制复制把日志2覆盖掉,出现d中的情况,然而在这时,集群中已提交的日志被覆盖了,这是很危险的
如果各位仍感觉上述两点不理解的话,可以自己动手,用官方提供的动画Raft Scope来回顾一下这两点.
Follower和Candidate宕机处理
RequestVote RPC
和AppendEntriesRPC都会失败。时间与可用性限制
我们前面讲的领导者选举、日志复制和安全性三个子问题,可以保证raft在集群节点稳定的状态下正常运行,甚至可以容忍一定程度的故障,但很多时候我们需要对集群的配置需要调整,这时需要对raft的配置文件进行改变,改变的过程可能会影响raft的正常运行,我们当然可以停止集群再执行变更,但这必然要停止对外服务一段时间,因此,raft设计了更为方便的方案.
在需要改变集群配置的时候(如增减节点、替换宕机的机器或者改变复制的程度),Raft可以进行配置变更自动化。
而自动化配置变更机制最大的难点是保证转换过程中不会出现同一任期的两个leader,因为转换期间整个集群可能划分为两个独立的大多数。
以下图举一个例子
- 下图为三节点( S 1 , S 2 , S 3 S_1,S_2,S_3 S1,S2,S3)集群扩容到五节点( S 1 , S 2 , S 3 , S 4 , S 5 S_1,S_2,S_3,S_4,S_5 S1,S2,S3,S4,S5)
- S 1 , S 2 S_1,S_2 S1,S2为老配置集群((别忘了还有一个leader呢), S 3 , S 4 , S 5 S_3,S_4,S_5 S3,S4,S5为新配置集群
- 老配置为三节点, S 1 , S 2 S_1,S_2 S1,S2可以选出一个leader(2/3)
- 新配置为五节点, S 3 , S 4 , S 5 S_3,S_4,S_5 S3,S4,S5可以选出一个leader(3/5)
- 这时就出现了两个Leader,也就出现了脑裂问题了
那么怎么解决这个问题呢?
所以raft配置采用了一种两阶段的方法。
集群先切换到一个过渡的配置,称之为联合一致(joint consensus) 。(这样我们只需要关注怎样避免在联合一致状态发生脑裂问题就可以了。)
而配置信息作为一个日志体包装为一个普通的AppendEntries RPC,发送给所有的follower。
//追加日志RPC Request
type AppendEntriesRequest struct {
term int //自己当前的任期号
leaderld int //leader(也就是自己)的ID,告诉follower自己是谁
prevLogIndex int //前一个日志的日志号 用于进行一致性检查
prevLogTerm int //前一个日志的任期号 用于进行一致性检查,只有这两个都与follower中的相同,follower才会认为日志是一致的
//如果只有日志号相同,这可能就是上图中f的情况,依旧需要向前回溯
entries []byte //当前日志体,也就是命令内容
leaderCommit int //leader的已提交日志号
}
第一阶段,leader发起 C o l d , n e w C_{old,new} Cold,new,使整个集群进入联合一致状态。这时,所有RPC都要在新旧两个配置中都达到大多数才算成功。
第二阶段,leader发起 C n e w C_{new} Cnew,作使整个集群进入新配置状态。这时,所有RPC只要在新配置下能达到大多数就算成功。
一旦某个服务器将该新配置日志条目增加到自己的日志中,他就会用该配置来做出未来所有的决策(服务器总是使用它日志中最新的配置,无论该配置日志是否已经被提交)。
这意味着Leader不用等待 C o l d , n e w C_{old,new} Cold,new和 C n e w C_{new} Cnew返回,就会直接使用其中的新规则来作出决策。
我们假设leader可以在集群成员变更任何时候宕机,大概有以下几种可能(也在下图中标注出来了):
我们来具体解读一下增加机器时集群成员变更的两个阶段:
- 首先我们有 S 1 , S 2 , S 3 S_1,S_2,S_3 S1,S2,S3三个节点,其中S3是现在任期的leader。
- 这时我们增加 S 4 , S 5 S_4,S_5 S4,S5两个节点,raft会先将他们设置为只读,等到他们追上日志进度后,才会开始集群成员变更.
- 然后现任leader S 3 S_3 S3发起 C o l d , n e w C_{old,new} Cold,new,并复制给了 S 4 , S 5 S_4,S_5 S4,S5。
- 注意,这时的 S 3 , S 4 , S 5 S_3,S_4,S_5 S3,S4,S5已经进入了联合一致状态,他们的决策要在新旧两个配置中都达到大多数才算成功。
- S 1 , S 2 S_1,S_2 S1,S2超时,开始进行选举,并且以两票可以产生一个老配置的leader。
- 但是,在联合一致状态下, S 3 , S 4 , S 5 S_3,S_4,S_5 S3,S4,S5中的任意节点必须要在老配置( S 1 , S 2 , S 3 S_1,S_2,S_3 S1,S2,S3)和新配置( S 1 , S 2 , S 3 , S 4 , S 5 S_1,S_2,S_3,S_4,S_5 S1,S2,S3,S4,S5)下都拿到超过半数选票才能当选。
- 因为 S 1 , S 2 S_1,S_2 S1,S2投票给了他们之中的一个节点,所以在老配置中,超过半数的条件是不满足的.
- 所以 S 3 , S 4 , S 5 S_3,S_4,S_5 S3,S4,S5无法选出leader,集群中只能选出 S 1 , S 2 S_1,S_2 S1,S2中的一个leader。
- 这样集群成员变更就失败了,但不会出现两个leader。
- 这里其实还有一种可能,就是重新选出的新leader具有 C o l d , n e w C_{old,new} Cold,new
- 比如图中 S 1 , S 3 , S 4 , S 5 S_1,S_3,S_4,S_5 S1,S3,S4,S5都复制了 C o l d , n e w C_{old,new} Cold,new,但还没有提交,这时选出的新leader一定具有 C o l d , n e w C_{old,new} Cold,new
- 但按照安全性限制,这个新leader无法提交 C o l d , n e w C_{old,new} Cold,new
- 可以让它继续发送 C n e w C_{new} Cnew继续进行集群成员变更。
- 假设 S 3 S_3 S3没有宕机,并且正常复制 C o l d , n e w C_{old,new} Cold,new,满足了联合一致条件。
- 比如图中 S 2 , S 3 , S 4 S_2,S_3,S_4 S2,S3,S4都复制了 C o l d , n e w C_{old,new} Cold,new.
- 在这种情况下,leader(S3)的 C o l d , n e w C_{old,new} Cold,new日志在新旧两种配置的集群中都超过半数了, C o l d , n e w C_{old,new} Cold,new就可以被提交了。
- S 2 S 3 / S 1 S 2 S 3 = 2 / 3 S_2 S_3 / S_1 S_2 S_3 =2/3 S2S3/S1S2S3=2/3
- S 2 S 3 S 4 / S 1 S 2 S 3 S 4 S 5 = 3 / 5 S_2S_3S_4/S_1S_2S_3S_4S_5 =3/5 S2S3S4/S1S2S3S4S5=3/5
- 这时有可能出现第二种leader宕机的情况:leader在 C o l d , n e w C_{old,new} Cold,new已提交但 C n e w C_{new} Cnew未发起时宕机
- 这时候选举限制安全性规则决定了选出的新leader一定具有 C o l d , n e w C_{old,new} Cold,new,也就是符合在两种配置集群中都超过半数,已经不存在脑裂的可能了。
- 这里要说明一下,就像上图一样,集群成员变更的过程中依旧可以对外服务。
- 联合一致状态下,也是可以正常执行命令的,但也需要在两个配置集群中都达到大多数才能提交
- C o l d , n e w C_{old,new} Cold,new,提交后,leader就会发起 C n e w C_{new} Cnew,这时leader只要满足新配置中的条件,就可以提
交日志。
- 比如说图中 S 3 S 4 S 5 S_3S_4S_5 S3S4S5复制了 C n e w C_{new} Cnew, C n e w C_{new} Cnew就可以提交了,不用再在 S 1 S 2 S 3 S_1S_2S_3 S1S2S3中达到大多数了。
- S 3 S 4 S 5 / S 1 S 2 S 3 S 4 S 5 = 3 / 5 S_3S_4S_5/S_1S_2S_3S_4S_5 =3/5 S3S4S5/S1S2S3S4S5=3/5
- 这时有可能出现第三种leader宕机的情况:leader在 C n e w C_{new} Cnew已发起时宕机
- 已经复制了 C n e w C_{new} Cnew的节点会只按新配置选举,
- 没有复制 C n e w C_{new} Cnew的节点会按新老配置选举。
- 有没有复制 C n e w C_{new} Cnew的节点都有可能当上leader.
- 但没有复制 C n e w C_{new} Cnew的节点选举成功也会发 C n e w C_{new} Cnew。
但是这里有一种情况需要特别讨论一下,即缩减节点
- 由 S 1 S 2 S 3 S 4 S 5 S_1S_2S_3S_4S_5 S1S2S3S4S5缩减为 S 1 S 2 S 3 S_1S_2S_3 S1S2S3, C o l d , n e w C_{old,new} Cold,new仍需要复制到两个集群中的大多数才能提交,但 C n e w C_{new} Cnew只需要复制到 S 1 S 2 S 3 S_1S_2S_3 S1S2S3中的两个就可以提交了。
- 这时如果leader S 3 S_3 S3宕机了, C n e w C_{new} Cnew会不会被覆盖呢?
- 不会的,因为处于联合一致状态的节点,也就是只复制了 C o l d , n e w C_{old,new} Cold,new没有复制 C n e w C_{new} Cnew的节点,必须要在两个集群都得到大多数选票才能选举成功。
- 而 S 2 S 3 S_2S_3 S2S3不会投票给 S 1 S 4 S 5 S_1S_4S_5 S1S4S5中的一个,所以 S 3 S_3 S3宕了,只有 S 2 S_2 S2才能当选,已提交的 C n e w C_{new} Cnew不会被覆盖。
这里再补充一下(第一种情况), C o l d , n e w C_{old,new} Cold,new的复制满足了在新老配置中都超过半数的条件,但leader宕机,这时leader无法提交 C o l d , n e w C_{old,new} Cold,new,但继续发 C n e w C_{new} Cnew的情况。
- leader在 C o l d , n e w C_{old,new} Cold,new未提交时宕机
- 如图中,leader S 3 S_3 S3复制了 C o l d , n e w C_{old,new} Cold,new到了新老配置的大多数节点,满足联合一致,但 S 3 S_3 S3未提交 C o l d , n e w C_{old,new} Cold,new就宕机了,这时, S 1 S_1 S1当选leader
- 选出的新leader( S 1 S_1 S1)具有 C o l d , n e w C_{old,new} Cold,new但这个新leader无法提交 C o l d , n e w C_{old,new} Cold,new(安全性限制)
- 但是可以让它继续发送 C n e w C_{new} Cnew,这时,它把 C n e w C_{new} Cnew复制到了 S 1 S 4 S 5 S_1S_4S_5 S1S4S5节点,构成了新配置集群的大多数,但这时它能提交吗?
- 并不能,因为他没有 S 3 S_3 S3的反馈, C o l d , n e w C_{old,new} Cold,new的提交规则并没有满足,这样提交的 C n e w C_{new} Cnew ,会把 C o l d , n e w C_{old,new} Cold,new也一并提交,这是不安全的
- 论文中没有给出这个问题的处理方法
- 但在某些设计中,这里可以强制让 C n e w C_{new} Cnew按照联合一致规则提交,如果leader满足不了条件,自动退位。
到这里,我们就明确了集群成员变更两阶段的全程,接下来,我们就论文上的这张图进行简单的回顾
在 C o l d , n e w C_{old,new} Cold,new发起但未提交时,raft集群还未进入联合一致状态。这时leader宕机,可以仅靠老配置选出来的新leader。
一旦 C o l d , n e w C_{old,new} Cold,new提交,raft集群就进入了联合一致状态,这时leader宕机,选出的新leader也要符合联合一致的选票规则了。
C o l d , n e w C_{old,new} Cold,new提交后,leader就可以发起 C n e w C_{new} Cnew,从发起 C n e w C_{new} Cnew开始,集群就可以仅靠新配置进行选举和日志复制了。
红色箭头说明,如果是缩减集群的情况下,leader可能自身就是缩减的对象,那么它会在 C n e w C_{new} Cnew复制完成后自动退位,这点我们接下来会进行补充说明。
集群成员变更还有三个补充规则需要说明一下:
RequestVote RPC
。
RequestVote RPC
。虽然它们不可能当选leader,但会导致raft集群反复进入投票选举阶段,影响集群的正常运行。RequestVote RPC
上补充了一个规则:一个节点如果在最小超时时间之内收到了RequestVote RPC
,那么它会拒绝此RPC。RequestVote RPC
就不会影响到raft集群的正常运行了。联合一致(jointconsensus)集群成员变更方法是比较复杂的,Raft作者对它做出了优化,即单节点集群成员变更方法,这里暂时不做讲解,有兴趣的同学可以在章节8.3中翻阅或者去翻阅Raft作者的文章
共识算法的三个主要特性:
no-op补丁
要追求强一致性读的话,就需要让这个读的过程或结果,也在大多数节点上达到共识。
稳妥的方法:把读也当做一个log,由leader发到所有的所有节点上寻求共识,这个读的log提交后,得到的结果是一定符合线性一致性的。
优化后的方法,要符合以下规则:
因为只有在自己任期内提交了一个日志,leader才能确认之前任期的哪些日志已被提交,才不会出现已提交的数据读取不到的情况。
安全性规则能保证被选出的leader一定具有所有已被提交的日志,但它可能有的日志还没有提交,它并不能确定哪些日志是已提交的,哪些日志没提交,而在它任期内提交一个日志,就能确定这一点。
优化过后的线性一致性读,也至少需要一轮RPC(leader确认的心跳)。并不比写操作快多少(写操作最少也就一轮RPC)。
所以,还可以更进一步,因为读的这轮RPC仅仅是为了确认集群中没有新leader产生。那么如果leader上一次心跳发送的时间还不到选举超时时间的下界,集群就不能选出一个新leader,那么这段时间就可以不经过这轮心跳确认,直接返回读的结果。(但不建议使用这种方法)
如果不要求强一致性读,怎么样利用follower承载更大的读压力呢?
分析Raft的性能
最根本的,每完成一个日志(命令)的复制与提交,需要的网络(RPC)来回次数。raft在理想情况下,只需要一次AppendEntries RPC来回即可提交日志(理论上的极限)。
影响Raft性能的因素以及优化方法
Raft与Paxos比较
“raft不允许日志空洞,所以性能没Paxos好。”
这里的Paxos,实际上指的是一个能完美处理所有日志空洞带来的边界情况,并能保证处理这些边界情况的代价,要小于允许日志空洞带来的收益的共识算法。
总结:raft确实有不允许日志空洞这个性能上限,但大部分系统实现,连raft的上限,都是远远没有达到的。所以无需考虑raft本身的瓶颈。
raft允许日志空洞的改造 -> ParallelRaft。
Multi-Raft将数据拆成一个个部分(Region),每个Region单独使用一个Raft组进行同步。这样,不同
Region之间的数据就不需要保持顺序了。• 但Region内部的数据还是要顺序复制与提交,Multi-Raft没有根本解决此问题。
我们把Raft中的限制总结为以下两点:
ParallelRaft就要打破这两点规则,让log可以乱序确认(Out-of-Order Acknowledge)和乱序提交(Out-of-Order Commit)。
当然,直接应用这两个“乱序”会造成算法的错误,所以ParallelRaft采用了一些措施来保证在这两个“乱序”的情况下依旧保持算法的正确性。
为了便于理解,我们先说明乱序提交问题的解决方案。
乱序提交(Out-of-Order Commit)
因为有的日志中的命令可能会修改相同的数据,如果跳过空洞先应用了后续的日志,就可能造成状态机间的不一致,导致错误。
为了解决这个问题,ParallelRaft引入了一种名叫look behind buffer的数据结构。
ParallelRaft的每个log都附带有一个look behind buffer。look behind buffer存放了前N个log修改的**LBA(逻辑块地址)**信息。
Merge阶段总体流程:
进入Merge阶段,Leader Candidate会通过所有FollowerCandidate的log来补齐自己所有的日志空洞。
下图中,S2S3宕机,选出S1为Leader Candidate,S4S5为Follower Candidate。
我们可以把所有的log分为三类:
已提交的日志
未提交的日志
不确定提没提交的日志
checkpoint
ParallelRaft的checkpoint就是状态机的一个快照。在实际实现中,ParallelRaft会选择有最新checkpoint的节点做Leader Candidate,而不是拥有最新日志的节点,有两个原因:
catch up
ParallelRaft把落后的Follower追上Leader的过程称为catch up,有两种类型:
fast-catch-up:Follower和Leader差距较小时(差距小于上一个checkpoint),仅同步日志。
streaming-catch-up:Follower和Leader差距较大时(差距大于上一个checkpoint),同步checkpoint和日志。
case 1需要streaming-catch-up。
case 2只用fast-catch-up。
case 3中多出来的日志会被Leader覆盖掉,和Raft一样。
本文章仅限个人学习使用,侵删