分布式共识算法Raft

0. 分布式共识算法Raft

一个更易理解的共识算法(论文原文为In Search of an Understandable Consensus Algorithm)

0.1 什么是分布式共识问题?

  • 分布式环境下系统集群存在很多节点,每个节点都可提出议案,我们需要:
    • 对多个节点提出的议案作裁决并得到一个一致的结论;
    • 让每个节点都感知到最终结论,从而使集群整体状态保持一致
    • 允许一部分节点宕机后集群仍可正常工作,先前通过的议案仍可访问,集群状态仍维持一致

0.2 Raft算法的前世今生

  • 在 Raft 算法提出之前,学术界早已有 Paxos 算法 (莱斯利·兰伯特在1990年提出)来解决分布式共识问题。
  • 到了 2006 年,Google 在两篇经典论文 Bigtable:A Distributed Storage System for Structured DataThe Chubby lock service for loosely-coupled distributed systems 中提及用 Paxos 算法实现了一个分布式锁服务 Chubby,于是 Paxos 算法开始进入工业界领域被广大技术人员熟知。
  • 在分布式共识算法领域,Paxos 算法可以说是宗师级角色,统治该领域十余年,大多数共识算法都是在其基础上进行改进和优化,Raft 算法也不例外。正因如此,Chubby 的作者 Mike Burrows 曾说过:只有一种分布式共识算法,那就是 Paxos,其他共识算法都只是 Paxos 算法的不完整版
  • 即便是大名鼎鼎的Paxos算法,也存在一些问题,Raft 算法的作者 Diego Ongaro 在研究 Paxos 算法时,就深受其复杂性困扰。他觉得 Paxos 算法是一门极其难懂的算法,其工程化实践更是困难重重,原始的 Paxos 算法不经过一番修改很难应用于工程之中,而修改后的 Paxos 算法又很难保证其正确性。他总结出 Paxos 算法有两个大问题:
    1. 非常难于理解,Diego Ongaro花了一年时间才掌握Paxos算法
    2. 没有给工程实现提供一个好的基础,而证明这点的最好论据就是:Paxos 算法自首次提出以来已过去十多年了,开源社区几乎没有一个被广泛认可的工程实现,很多 Paxos 算法的实现都是对其完整版的近似。
  • 正因如此,Diego Ongaro打算开发一门新的共识算法,而这门新的共识算法就是著名的Raft算法

1. Raft概述

  • Raft 是用来管理复制日志(replicated log)的一致性协议。它跟 Paxos 作用相同,效率也相当,但是它的组织结构跟 Paxos 不同.
  • 目的是实现集群的高可用性,让集群中的每个节点都可用,即具备完整的正确的日志.
  • 相比于Paxos,Raft最大的特性就是易于理解(Understandable)。为了达到这个目标,
    Raft主要做了两方面的事情:
    • 问题分解:把共识算法分为三个子问题,分别是领导者选举(leader election)日志
      复制(log replication)安全性(safety).
    • 状态简化:对算法做出一些限制,减少状态数量和可能产生的变动。
    • 而Raft算法的强大也是被证明了的,论文中对43个大学生做了个实验,让他们同时学习Paxos和Raft,结果显示,其中有33个
      人学习Raft的成绩好于学习Paxos的成绩。

2. 复制状态机

  • 在具体介绍Raft之前,我们要先了解一下复制状态机(Replicatedstatemachine)的概念。
  • 相同的初始状态+相同的输入=相同的结束状态
  • 多个节点上,从相同的初始状态开始,执行相同的一串命令,产生相同的最终状态
  • 在Raft中,leader将客户端请求(command)封装到一个个**日志实体(log entry)**中,再把这些log entries复制到所有follower节点,然后所有节点一起把log应用到自己的状态机(state Machine)上,根据复制状态机的理论,大家的结束状态肯定是一致的。
  • 这样,client无论查询那个节点的状态机,查询到的结果都是一致的
  • 可以说,我们使用共识算法,就是为了实现复制状态机。一个分布式场景下的各节点间,就是通过共识算法来保证命令序列的一致,从而始终保持它们的**状态一致,**从而实现高可用的。(投票选主是一种特殊的命令)
  • 这里稍微拓展一点,复制状态机的功能可以更加强大。比如数据库两个副本一个采用行存储的数据结构存储数据,另一个采用列存储,只要它们初始数据相同,并持续发给他们相同的命令,那么同一时刻从两个副本中读取到的结果也是一样的,这就是一种HTAP(Hybrid Transaction and Analytical Process,混合事务和分析处理)的实现方法(比如TiDB就是利用这种方式来巧妙的实现自己的HTAP特性的)。

3. 状态简化

  • 在任何时刻,每一个服务器节点都处于leader,follower或candidate这三个状态之一。
  • 相比于Paxos,这一点就极大简化了算法的实现,因为Raft只需考虑状态的切换,而不用像Paxos那样考虑状态之间的共存和互相影响

状态描述:

  1. 所有节点开始的时候都处于Follower状态,此时,第一个认识到集群中没有Leader的节点会把自己变成candidate
  2. 节点处于Candidate状态时,会发生一次或多次选举,最后根据选举结果决定自己时切换回Follower状态还是切换到Leader状态
  3. 如果切换到Leader状态,就会在Leader状态为客户端提供服务
  4. 如果节点在Leader状态的任期结束,或者是节点宕机亦或者其他的问题,就会切换回Follower状态,并开始下一个循环
  • Raft把时间分割成任意长度的任期(term),任期用连续的整数标记。
  • 每一段任期从一次选举开始。在某些情况下,一次选举无法选出leader(比如两个节点收到了相同的票数,如下图 t 3 t_3 t3),在这种情况下,这一任期会以没有leader结束;一个新的任期(包含一次新的选举)会很快重新开始。Raft保证在任意一个任期内,最多只有一个leader。

任期的机制可以非常明确地表示集群的状态,而通过任期的比较,也可以确立一台服务器历史的状态

比如我们可以通过查看一台服务器是否具有在 t 2 t_2 t2任期内的日志,判断该服务器在 t 2 t_2 t2任期内是否宕机

  • Raft算法中服务器节点之间使用RPC进行通信,并且Raft中只有两种主要的RPC:
  • RequestVoteRPC(请求投票):由candidate在选举期间发起。
  • AppendEntriesRPC(追加条目):由leader发起,用来复制日志和提供一种心跳机制。
  • 服务器之间通信的时候会交换当前任期号;如果一个服务器上的当前任期号比其他的小,该服务器会将自己的任期号更新为较大的那个值。
  • 如果一个candidate或者leader发现自己的任期号过期了,它会立即回到follower状态。
  • 如果一个节点接收到一个包含过期的任期号的请求,它会直接拒绝这个请求。

相比其他共识算法十多种的通信类型,Raft算法的精简设计,极大减少了理解和实现的成本

4. 领导者选举

信息解读:

  1. S 5 S_5 S5是一个Leader,它向其它所有server发送心跳消息,来维持自己的地位

  2. 如果一个Server在它的进度条读完之前仍没有收到 S 5 S_5 S5的心跳消息的话,该server就会认为系统中没有可用的leader,然后开始选举.

  3. 开始一个选举过程后,follower先增加自己的当前任期号,并转换到candidate状态。然后投票给自己,并且并行地向集群中的其他服务器节点发送投票请求(RequestVote RPC)。

  4. 最终会有三种结果:

    • 它获得超过半数选票赢得了选举-> 成为Leader并开始发送心跳(告知集群中存在Leader),结束投票选举阶段

    • 其他节点赢得了选举->收到新leader的心跳后,如果新leader的任期号不小于自己当前的任期号(任期号大概率相等),那么就从candidate回到follower状态。

    • 一段时间之后没有任何获胜者->每个candidate都在一个自己的随机选举超时时间后增加任期号开始新一轮投票。

  5. 为什么会没有获胜者?

    • 比如有多个follower同时成为candidate,得票太过分散,没有任何一个candidate得票超过半数,进入下一轮选举.
    • "注意:当前选举阶段并没有产生任何Leader"这个结论不需要集群所有节点对此产生共识,而是通过每个candidate都在等待一个随机选举超时时间之后,默认去进入下一个选举阶段。
  6. 论文中给出的随机选举超时时间为 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的投票逻辑:

  1. 所有节点开始的时候都处于Follower状态,此时,第一个认识到集群中没有Leader的节点会把自己变成candidate

  2. 他会给自己的任期号加一并发请求投票request给其他follower。

  3. 收到一个requestVoteRequest之后会先校验这个candidate是否符合条件。

    • term是否比自己大?

    • 与Request的后两个字段相关,我们之后再进行讲解

  4. 确认无误后开始投票,没有成为candidate的follower节点,对于同一个任期,会按照先来先得的原则投出自己的选票。

  5. 为什么RequestVoteRPC中要有candidate最后一个日志的信息呢,安全性子问题中会给出进一步的说明。

5. Raft日志复制(重点)

  • Leader被选举出来后,开始为客户端请求提供服务。

  • 客户端怎么知道新leader是哪个节点呢?

    • 非常容易解决,Client仍然向老节点发送请求,此时,会有三种情况
      • 这个节点恰好是Leader
      • 这个节点是Follower,Follower可以通过心跳得知Leader的ID,据此告知Client该找哪个节点
      • 这个节点宕机了,此时Client会向其它任一节点发送请求,重复这个过程
    • 也有一些比如设置第三方节点的做法,但这里不做说明,有兴趣的同学可以去自行了解
  • Leader接收到客户端的指令后,会把指令作为一个新的条目追加到日志中去。

  • 一条日志中需要具有三个信息:

    • 状态机指令
    • leader的任期号(对于检测多个日志副本之间的不一致情况和判定节点状态,都有重要作用)
    • 日志号(日志索引,区分日志的前后关系)

    只有任期号和日志号一起看才能确定一个日志

    • Leader并行发送AppendEntries RPC给follower,让它们复制该条目。当该条目被超过半数的follower复制后,leader就可以在本地执行该指令并把结果返回客户端
    • 我们把本地执行指令,也就是leader应用日志与状态机这一步, 称作提交
  1. 在上图中,最上面的一行代表Leader,其余四行代表Follower,可以观察到,Follower节点的进度不一定是一致的

  2. 但是在这里,只要有三个节点(包括Leader在内)复制到了日志,Leader就可以提交了,在上图中可以提交的日志号到7

  3. 在此过程中,leader或follower随时都有崩溃或缓慢的可能性,Raft必须要在有宕机的情况下继续支持日志复制,并且保证每个副本日志顺序的一致(以保证复制状态机的实现)。具体有三种可能:

    1. 如果有follower因为某些原因没有给leader响应,那么leader会不断地重发追加条目请求(AppendEntries RPC),哪怕leader已经回复了客户端。
    2. 如果有follower崩溃后恢复,这时Raft追加条目的一致性检查生效,保证follower能按顺序恢复崩溃后的缺失的日志。
      • Raft的一致性检查:leader在每一个发往follower的追加条目RPC中,会放入前一个日志条目的索引位置和任期号,如果follower在它的日志中找不到前一个日志,那么它就会拒绝此日志,leader收到follower的拒绝后,会发送前一个日志条目,从而逐渐向前定位到follower第一个缺失的日志。
    3. 如果leader崩溃,那么崩溃的leader可能已经复制了日志到部分follower但还没有提交,而被选出的新leader又可能不具备这些日志,这样就有部分follower中的日志和新leader的日志不相同。

对于上述问题的第三点,我们以上图举例:

  • 可以发现,此时follower中的c和d比leader还多出两个日志,那么为什么leader没有多出的日志还可以当选leader呢?
  • 是因为c和d多出的日志还没有提交,也就不构成多数.
  • 在这七个节点的集群中,leader可以依靠a,b,e和自己的选票当选leader(当然f也可以投票,因为a,b,e,f的任期都小于等于leader)
  • 再看最后一个节点f(宕机的leader节点),它具有2,3任期的日志,别的节点都不具有,这意味着它在这两个任期内担任leader.
  • 但是它在2,3任期内的日志都没有正常复制到大多数节点,也就没有提交.
  • 这时,如果f恢复了,即便它在2,3任期的日志与leader不同,也不会产生冲突,因为Raft在这种情况下,leader通过强制follower复制它的日志来解决不一致的问题,这意味着follower中跟leader冲突的日志条目会被新leader的日志条目覆盖(因为没有提交,所以不违背外部一致性)。
  • 这样图中的c,d,e,f节点中与leader不同的日志,最终都会被覆盖掉.
  • 也有可能当前的leader宕机,这个时候a,c,d是有机会当上leader的,如果c,d当选leader,就可以把多出的日志复制给follower,来使自己多出的日志提交

总结一下:

  • 通过这种机制,leader在当权之后就不需要任何特殊的操作来使日志恢复到一致状态。
  • Leader只需要进行正常的操作,然后日志就能在回复AppendEntries一致性检查失败的时候自动趋于一致。
  • Leader从来不会覆盖或者删除自己的日志条目。(Append-Only)
  • 这样的日志复制机制,就可以保证一致性特性:
    • 只要过半的服务器能正常运行,Raft就能够接受、复制并应用新的日志条目;
    • 在正常情况下,新的日志条目可以在一个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的已提交日志号
}
//追加日志RPC Response
type AppendEntriesResponse struct{
   
    term				int				// 自己当前任期号
    success 			bool			//如果follower包括前一个日志,则返回true
}

提交详解:

Request:

  1. 对于follower而言,接收到了leader的日志,并不能立即提交,因为这时候还没有确认这个日志是否被复制到了大多数节点。
  2. 只有leader确认了日志被复制到大多数节点后,leader才会提交这个日志,也就是应用到自己的状态机中
  3. 然后leader会在AppendEntries RPC中把这个提交信息告知follower(也就是上面的leaderCommit).
  4. 然后follower就可以把自己复制但未提交的日志设为已提交状态(应用到自己的状态机中).
  5. 对于还在追赶进度的follower来说,若leaderCommit大于自己最后一个日志,这时它的所有日志都是可以提交的

Response:

  • 这个success标志只有在request的term大于等于自己的term,且request通过了一致性检查之后才会返回true,否则都返回false

6. 安全性(重难点)

  • 领导者选举和日志复制两个子问题实际上已经涵盖了共识算法的全程,但这两点还不能完全保证每一个状态机会按照相同的顺序执行相同的命令。
    • 这里非常强调顺序,因为日志中的命令应用到状态机的顺序是一定不能颠倒的(因为状态机要产生相同的最终状态)
    • 但很多共识算法为了提高效率,会允许日志乱序复制到非leader的节点,这样就会在日志中出现很多空洞(如上图所示),造成非常多的边界情况需要处理。
    • 而raft为了避免这些复杂处理,在日志复制阶段就保证了日志是有序且无空洞的.
    • 但raft能生效的前提是leader是正常的,如果leader出现宕机,他的后几个日志的状态就可能出现不正常.
    • 这时就引出了安全性问题(即新leader是否具有这些不正常的日志,以及怎么处理这些日志)
  • 所以Raft通过几个补充规则完善整个算法,使算法可以在各类宕机问题下都不出错。
  • 这些规则包括(不讨论安全性条件的证明过程):
    1. Leader宕机处理:选举限制
    2. Leader宕机处理:新leader是否提交之前任期内的日志条目
    3. Follower和Candidate宕机处理
    4. 时间与可用性限制
  1. 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通过比较两份日志中最后一条日志条目的索引值和任期号来定义谁的日志比较新。

      • 如果两份日志最后条目的任期号不同,那么任期号大的日志更“新”。
      • 如果两份日志最后条目的任期号相同,那么日志较长的那个更“新”。
  2. Leader宕机处理:新leader是否提交之前任期内的日志条目

    • 如果某个leader在提交某个日志条目之前崩溃了,以后的leader会试图完成该日志条目的复制
    • 复制,而非提交,不能通过心跳提交老日志。那么为什么不能提交呢,我们以下图为例.
    1. a中,S1是leader
    2. b中,S1崩溃了,S5通过S3S4的选票赢得选举
    3. 到了c中,S5又崩溃了,S1恢复了并赢得选举成为leader,此时日志2已经被复制到了大多数机器上,但还没有被提交(此时日志2是老日志,它不能被立即提交)
    4. 到了d中,S1再次崩溃,S5通过S2S3S4的选票再次选举成功,并将日志3复制到follower上
    5. 在这里我们可以观察到,哪怕S1把日志2复制到了大多数节点,最终还是会被日志3覆盖(即没有在集群中提交日志2)
    6. 在这里我们提出一种假设
    7. 假设S1重新当选leader(c时),在S1S2S3中都把日志2提交了 ,这时候可以认为日志2已经提交了,也可以返回客户端提交成功
    8. 但是这时候S1宕机了,集群重新选举,S5当选,这时raft会通过强制复制把日志2覆盖掉,出现d中的情况,然而在这时,集群中已提交的日志被覆盖了,这是很危险的
    • 所以说Raft永远不会通过计算副本数目的方式来提交之前任期内的日志条目。

    • 只有leader当前任期内的日志条目才通过计算副本数目的方式来提交;

      • 因为可以确认自己当前的任期号是最大的
    • 新leader的提交是危险的,但是复制是安全的,依旧会把老日志复制到所有节点,那么这些老的日志怎么才能被提交呢?

      • 这个新leader会在它的任期内新产生一个日志,在这个日志提交时,老leader任期内的日志也就可以提交了
      • 这是因为一旦当前任期的某个日志条目被提交,那么由于日志匹配特性,之前的所有日志条目也都会被间接地提交。

如果各位仍感觉上述两点不理解的话,可以自己动手,用官方提供的动画Raft Scope来回顾一下这两点.

  1. Follower和Candidate宕机处理

    • Follower和Candidate崩溃后的处理方式比leader崩溃要简单的多,并且两者的处理方式是相同的。
    • 如果follower或candidate崩溃了,那么后续发送给他们的RequestVote RPC和AppendEntriesRPC都会失败。
    • Raft通过无限的重试来处理这种失败。如果崩溃的机器重启了,那么这些RPC就会成功地完成。
    • 如果一个服务器在完成了一个RPC,但是还没有响应的时候崩溃了,那么它重启之后就会再次收到同样的请求。(Raft的RPC都是幂等的)
  2. 时间与可用性限制

    • raft算法整体不依赖客观时间,也就是说,哪怕因为网络或其他因素,造成后发的RPC先到,也不会影响raft的正确性。
      • 这点和Google的分布式数据库spanner的设计思路不同
    • 虽然Raft不依赖客观时间,但是整个系统的一些时间指标还是要满足的,只要整个系统满足下面的时间要求,Raft就可以选举出并维持一个稳定的leader:
      • 广播时间(broadcastTime)<<选举超时时间(electionTimeout)<<平均故障时间(MTBF)
      • 广播时间和平均故障时间是由系统决定的,但是选举超时时间是我们自己选择的。Raft的RPC需要接受并将信息落盘,所以广播时间大约是0.5ms到20ms,取决于存储的技术。因此,选举超时时间可能需要在10ms到500ms之间。大多数服务器的平均故障间隔时间都在几个月甚至更长,因此我们通常不需要过分担忧宕机.

7. 集群成员变更

  • 我们前面讲的领导者选举、日志复制和安全性三个子问题,可以保证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}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值