1.分布式一致性协议
1.1 二阶段提交协议(2pc)
2 prepare commit,先执行事务,但不提交,待所有执行者都确认可以提交事务再一起提交,若有一个失败则回滚:
2PC 执行流程
1. 成功执行事务事务提交流程
阶段一:
事务询问
协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者
的响应。
执行事务 (写本地的Undo/Redo日志)
各参与者向协调者反馈事务询问的响应
阶段二:
发送提交请求:
协调者向所有参与者发出 commit 请求。
事务提交:
参与者收到 commit 请求后,会正式执行事务提交操作,并在完成提交之后释放整个事务执
行期间占用的事务资源。
反馈事务提交结果:
参与者在完成事务提交之后,向协调者发送 Ack 信息。
完成事务:
协调者接收到所有参与者反馈的 Ack 信息后,完成事务。
2. 中断事务流程
假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参
与者的反馈响应,那么就会中断事务
阶段一:
事务询问
协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者
的响应。
执行事务 (写本地的Undo/Redo日志)
各参与者向协调者反馈事务询问的响应
阶段二:
发送回滚请求:
协调者向所有参与者发出 Rollback 请求。
事务回滚:
参与者接收到 Rollback 请求后,会利用其在阶段一中记录的 Undo 信息来执行事务回滚操
作,并在完成回滚之后释放在整个事务执行期间占用的资源。
反馈事务回滚结果:
参与者在完成事务回滚之后,向协调者发送 Ack 信息。
中断事务:
协调者接收到所有参与者反馈的 Ack 信息后,完成事务中断。
优点:原理简单
缺点:
1.同步阻塞
所有的执行者都要等待其他执行者的结果。
2.单点问题
协调者出现错误则无法执行
3.数据不一致
若发送提交命令后有部分执行者失败则导致只有部分执行者成功
4.太过保守
一个执行者返回失败则事务终止
1.2 三阶段提交协议(3pc)
三阶段提交协议为了解决二阶段协议中的同步阻塞等问题,三阶段提交协议在协调者和参与者中都引入了超时机制,并且把两阶段提交协议的第一个阶段拆分成了两步:询问,然后再锁资源,最后真正提交。三阶段中的 Three Phase 分别为 CanCommit、PreCommit、DoCommit 阶段。
CanCommit 阶段
3PC 的 CanCommit 阶段其实和 2PC 的准备阶段很像。协调者向参与者发送 Can-Commit 请求,参与者如果可以提交就返回 Yes 响应,否则返回 No 响应。
PreCommit 阶段
协调者根据参与者的反应情况来决定是否可以继续事务的 PreCommit 操作。根据响应情况,有以下两种可能。
A. 假如协调者从所有的参与者获得的反馈都是 Yes 响应,那么就会进行事务的预执行:
发送预提交请求,协调者向参与者发送 PreCommit 请求,并进入 Prepared 阶段;
事务预提交,参与者接收到 PreCommit 请求后,会执行事务操作;
响应反馈,如果参与者成功执行了事务操作,则返回 ACK 响应,同时开始等待最终指令。
B. 假如有任何一个参与者向协调者发送了 No 响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就中断事务:
发送中断请求,协调者向所有参与者发送 abort 请求;
中断事务,参与者收到来自协调者的 abort 请求之后,执行事务的中断。
DoCommit 阶段
该阶段进行真正的事务提交,也可以分为以下两种情况。
A. 执行提交
发送提交请求。协调者接收到参与者发送的 ACK 响应后,那么它将从预提交状态进入到提交状态,并向所有参与者发送 doCommit 请求。
事务提交。参与者接收到 doCommit 请求之后,执行正式的事务提交,并在完成事务提交之后释放所有事务资源。
响应反馈。事务提交完之后,向协调者发送 ACK 响应。
完成事务。协调者接收到所有参与者的 ACK 响应之后,完成事务。
B. 中断事务
协调者没有接收到参与者发送的 ACK 响应,可能是因为接受者发送的不是 ACK 响应,也有可能响应超时了,那么就会执行中断事务。
C.超时提交
参与者如果没有收到协调者的通知,超时之后会执行 Commit 操作。
三阶段提交做了哪些改进
1.引入超时机制
在 2PC 中,只有协调者拥有超时机制,如果在一定时间内没有收到参与者的消息则默认失败,3PC 同时在协调者和参与者中都引入超时机制。执行者在一段时间没有收到协调者的提交指令,会将事务提交。
2.添加预提交阶段
在precommit前增加了一个cancommit阶段,这个阶段是轻量的,用于先预估是否能处理事务。
三阶段提交协议存在的问题
三阶段提交协议同样存在问题,具体表现为,在阶段三中,如果参与者接收到了 PreCommit 消息后,出现了不能与协调者正常通信的问题,在这种情况下,参与者依然会进行事务的提交,这就出现了数据的不一致性。
1.3 NWR协议
NWR,N代表N个主从服务,W代表写,R代表读。NWR协议的思想就是读和写的成功总数要大于N,才能确保读到最新的数据。
应用有主从共有N个存储,每次写成功的存储数量加上读成功的数量要大于N,比如N为3,那么写成功2个存储,读成功2个存储,2+2>3,那么可以确保读的数据中肯定有最新的:
或者写成功3个,读成功1个,那么也能确保读到最新的数据;
如果W为1,R为2,那么可能读失败的那个就是写失败的那个,就读不到最新的数据。
1.4 Gossip 协议
gossip,谣言,顾名思义,信息像谣言一样广泛无序传播,每个节点都向周围节点传播信息.
传播原理:
1. 反熵传播
是以固定的概率传播所有的数据。所有参与节点只有两种状态:Suspective(病原)、
Infective(感染)。过程是种子节点会把所有的数据都跟其他节点共享,以便消除节点之间数据的任
何不一致,它可以保证最终、完全的一致。缺点是消息数量非常庞大,且无限制;通常只用于新加
入节点的数据初始化。
2. 谣言传播
是以固定的概率仅传播新到达的数据。所有参与节点有三种状态:Suspective(病原)、
Infective(感染)、Removed(愈除)。过程是消息只包含最新 update,谣言消息在某个时间点之后会
被标记为 removed,并且不再被传播。缺点是系统有一定的概率会不一致,通常用于节点间数据
增量同步。
传播方式:
1.push
A节点向B节点传播(key,value,version),B节点接收后更新比自己新的数据;
2.pull
A节点向B节点传播(key,version),B将比A更新的数据推送给A;
3.push/pull
比pull多一步,A接收到B的信息后再将比B新的数据推送给B。
优点:
1.扩展性,任意新增节点,新增节点状态最终会与其他节点一致。
2.容错,任意节点的宕机不会影响集群的稳定性
3.去中心化,所有节点都是对等的,可以把消息遍布整个网络
4.最终一致性,消息可以在有限的时间遍布整个网络
缺点:
1.有时延
2.消息冗余,同样的消息可能会发送多遍。
1.5 paxos协议
1.5.1 什么是Paxos
Paxos协议其实说的就是Paxos算法, Paxos算法是基于消息传递且具有高度容错特性的一致性算
法,是目前公认的解决分布式一致性问题最有效的算法之一。
Paxos由 莱斯利·兰伯特(Leslie Lamport)于1998年在《The Part-Time Parliament》论文中首次公
开,最初的描述使用希腊的一个小岛Paxos,描述了Paxos小岛中通过决议的流程,并以此命名这个算法,但是这个描述理解起来比较有挑战性。后来在2001年,莱斯利·兰伯特重新发表了朴实的算法描述版本《Paxos Made Simple》自Paxos问世以来就持续垄断了分布式一致性算法,Paxos这个名词几乎等同于分布式一致性。Google的很多大型分布式系统都采用了Paxos算法来解决分布式一致性问题,如Chubby、Megastore以及Spanner等。开源的ZooKeeper,以及MySQL 5.7推出的用来取代传统的主从复制的MySQL GroupReplication等纷纷采用Paxos算法解决分布式一致性问题。然而,Paxos的最大特点就是难,不仅难以理解,更难以实现。Google Chubby的作者Mike Burrows说过这个世界上只有一种一致性算法,那就是Paxos,其它的算法都是残次品。
1.5.2 Paxos 解决了什么问题
在常见的分布式系统中,总会发生诸如机器宕机或网络异常(包括消息的延迟、丢失、重复、乱
序,还有网络分区)等情况。Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性。
注:这里某个数据的值并不只是狭义上的某个数,它可以是一条日志,也可以是一条命令
(command)。。。根据应用场景不同,某个数据的值有不同的含义。
在之前讲解2PC 和 3PC的时候在一定程度上是可以解决数据一致性问题的. 但是并没有完全解决就
是协调者宕机的情况.
如何解决2PC和3PC的存在的问题呢?
- 步骤1-引入多个协调者
- 步骤-引入主协调者,以他的命令为基准
其实在引入多个协调者之后又引入主协调者.那么这个就是最简单的一种Paxos 算法.Paxos的版本有: Basic Paxos , Multi Paxos, Fast-Paxos, 具体落地有Raft 和zk的ZAB协议。
1.5.3 Basic Paxos相关概念
- 角色介绍
Client:客户端
客户端向分布式系统发出请求,并等待响应。例如,对分布式文件服务器中文件的写请求。
Proposer:提案发起者
提案者提倡客户端请求,试图说服Acceptor对此达成一致,并在发生冲突时充当协调者以推
动协议向前发展
Acceptor: 决策者,可以批准提案
Acceptor可以接受(accept)提案;并进行投票, 投票结果是否通过以多数派为准, 以如果某
个提案被选定,那么该提案里的value就被选定了
Learner: 最终决策的学习者
学习者充当该协议的复制因素(不参与投票) - 决策模型
- basic paxos流程
basic paxos流程一共分为4个步骤:
Prepare
Proposer提出一个提案,编号为N, 此N大于这个Proposer之前提出所有提出的编号, 请求
Accpetor的多数人接受这个提案
Promise
如果编号N大于此Accpetor之前接收的任提案编号则接收, 否则拒绝
Accept
如果达到多数派, Proposer会发出accept请求, 此请求包含提案编号和对应的内容
Accepted
如果此Accpetor在此期间没有接受到任何大于N的提案,则接收此提案内容, 否则忽略
1.5.4 Basic Paxos流程图 - 无故障的basic Paxos
- Acceptor失败时的basic Paxos
在下图中,多数派中的一个Acceptor发生故障,因此多数派大小变为2。在这种情况下,Basic
Paxos协议仍然成功。 - Proposer失败时的basic Paxos
Proposer在提出提案之后但在达成协议之前失败。具体来说,传递到Acceptor的时候失败了,这个
时候需要选出新的Proposer(提案人),那么 Basic Paxos协议仍然成功 - 当多个提议者发生冲突时的basic Paxos
最复杂的情况是多个Proposer都进行提案,导致Paxos的活锁问题.
针对活锁问题解决起来非常简单: 只需要在每个Proposer再去提案的时候随机加上一个等待时间即
可.
1.5.5 Multi-Paxos流程图
针对basic Paxos是存在一定得问题,首先就是流程复杂,实现及其困难, 其次效率低(达成一致性需要2轮
RPC调用),针对basic Paxos流程进行拆分为选举和复制的过程. - 第一次流程-确定Leader
- 第二次流程-直接由Leader确认
1.5.6 Multi-Paxos角色重叠流程图
Multi-Paxos在实施的时候会将Proposer,Acceptor和Learner的角色合并统称为“服务器”。因此,
最后只有“客户端”和“服务器”。
1.6 raft协议
Raft算法概述不同于Paxos算法直接从分布式一致性问题出发推导出来,Raft算法则是从多副本状态机的角度提出,用于管理多副本状态机的日志复制。Raft实现了和Paxos相同的功能,它将一致性分解为多个子问题:Leader选举(Leader election)、日志同步(Log replication)、安全性(Safety)、日志压缩(Log compaction)、成员变更(Membership change)等。同时,Raft算法使用了更强的假设来减少了需要考虑的状态,使之变的易于理解和实现。Raft将系统中的角色分为领导者(Leader)、跟从者(Follower)和候选人(Candidate):Leader:接受客户端请求,并向Follower同步请求日志,当日志同步到大多数节点上后告诉Follower提交日志。Follower:接受并持久化Leader同步的日志,在Leader告之日志可以提交之后,提交日志。Candidate:Leader选举过程中的临时角色。
Raft算法角色Raft要求系统在任意时刻最多只有一个Leader,正常工作期间只有Leader和Followers。Raft算法角色状态转换如下:
Raft算法角色状态转换Follower只响应其他服务器的请求。如果Follower超时没有收到Leader的消息,它会成为一个Candidate并且开始一次Leader选举。收到大多数服务器投票的Candidate会成为新的Leader。Leader在宕机之前会一直保持Leader的状态。
Raft算法将时间分为一个个的任期(term),每一个term的开始都是Leader选举。在成功选举Leader之后,Leader会在整个term内管理整个集群。如果Leader选举失败,该term就会因为没有Leader而结束。
二、Leader选举
Raft 使用心跳(heartbeat)触发Leader选举。当服务器启动时,初始化为Follower。Leader向所有Followers周期性发送heartbeat。如果Follower在选举超时时间内没有收到Leader的heartbeat,就会等待一段随机的时间后发起一次Leader选举。Follower将其当前term加一然后转换为Candidate。它首先给自己投票并且给集群中的其他服务器发送 RequestVote RPC (RPC细节参见八、Raft算法总结)。结果有以下三种情况:赢得了多数的选票,成功选举为Leader;收到了Leader的消息,表示有其它服务器已经抢先当选了Leader;没有服务器赢得多数的选票,Leader选举失败,等待选举时间超时后发起下一次选举。
Leader选举过程选举出Leader后,Leader通过定期向所有Followers发送心跳信息维持其统治。若Follower一段时间未收到Leader的心跳则认为Leader可能已经挂了,再次发起Leader选举过程。Raft保证选举出的Leader上一定具有最新的已提交的日志,这一点将在四、安全性中说明。
三、日志同步
Leader选出后,就开始接收客户端的请求。Leader把请求作为日志条目(Log entries)加入到它的日志中,然后并行的向其他服务器发起 AppendEntries RPC (RPC细节参见八、Raft算法总结)复制日志条目。当这条日志被复制到大多数服务器上,Leader将这条日志应用到它的状态机并向客户端返回执行结果。
Raft日志同步过程某些Followers可能没有成功的复制日志,Leader会无限的重试 AppendEntries RPC直到所有的Followers最终存储了所有的日志条目。日志由有序编号(log index)的日志条目组成。每个日志条目包含它被创建时的任期号(term),和用于状态机执行的命令。如果一个日志条目被复制到大多数服务器上,就被认为可以提交(commit)了。
Raft日志Raft日志同步保证如下两点:如果不同日志中的两个条目有着相同的索引和任期号,则它们所存储的命令是相同的。如果不同日志中的两个条目有着相同的索引和任期号,则它们之前的所有条目都是完全一样的。第一条特性源于Leader在一个term内在给定的一个log index最多创建一条日志条目,同时该条目在日志中的位置也从来不会改变。第二条特性源于 AppendEntries 的一个简单的一致性检查。当发送一个 AppendEntries RPC 时,Leader会把新日志条目紧接着之前的条目的log index和term都包含在里面。如果Follower没有在它的日志中找到log index和term都相同的日志,它就会拒绝新的日志条目。一般情况下,Leader和Followers的日志保持一致,因此 AppendEntries 一致性检查通常不会失败。然而,Leader崩溃可能会导致日志不一致:旧的Leader可能没有完全复制完日志中的所有条目。
Leader和Followers上日志不一致上图阐述了一些Followers可能和新的Leader日志不同的情况。一个Follower可能会丢失掉Leader上的一些条目,也有可能包含一些Leader没有的条目,也有可能两者都会发生。丢失的或者多出来的条目可能会持续多个任期。Leader通过强制Followers复制它的日志来处理日志的不一致,Followers上的不一致的日志会被Leader的日志覆盖。Leader为了使Followers的日志同自己的一致,Leader需要找到Followers同它的日志一致的地方,然后覆盖Followers在该位置之后的条目。Leader会从后往前试,每次AppendEntries失败后尝试前一个日志条目,直到成功找到每个Follower的日志一致位点,然后向后逐条覆盖Followers在该位置之后的条目。
四、安全性
Raft增加了如下两条限制以保证安全性:拥有最新的已提交的log entry的Follower才有资格成为Leader。这个保证是在RequestVote RPC中做的,Candidate在发送RequestVote RPC时,要带上自己的最后一条日志的term和log index,其他节点收到消息时,如果发现自己的日志比请求中携带的更新,则拒绝投票。日志比较的原则是,如果本地的最后一条log entry的term更大,则term大的更新,如果term一样大,则log index更大的更新。Leader只能推进commit index来提交当前term的已经复制到大多数服务器上的日志,旧term日志的提交要等到提交当前term的日志来间接提交(log index 小于 commit index的日志被间接提交)。之所以要这样,是因为可能会出现已提交的日志又被覆盖的情况:
已提交的日志被覆盖在阶段a,term为2,S1是Leader,且S1写入日志(term, index)为(2, 2),并且日志被同步写入了S2;在阶段b,S1离线,触发一次新的选主,此时S5被选为新的Leader,此时系统term为3,且写入了日志(term, index)为(3, 2);S5尚未将日志推送到Followers就离线了,进而触发了一次新的选主,而之前离线的S1经过重新上线后被选中变成Leader,此时系统term为4,此时S1会将自己的日志同步到Followers,按照上图就是将日志(2, 2)同步到了S3,而此时由于该日志已经被同步到了多数节点(S1, S2, S3),因此,此时日志(2,2)可以被提交了。;在阶段d,S1又下线了,触发一次选主,而S5有可能被选为新的Leader(这是因为S5可以满足作为主的一切条件:1. term = 5 > 4,2. 最新的日志为(3,2),比大多数节点(如S2/S3/S4的日志都新),然后S5会将自己的日志更新到Followers,于是S2、S3中已经被提交的日志(2,2)被截断了。增加上述限制后,即使日志(2,2)已经被大多数节点(S1、S2、S3)确认了,但是它不能被提交,因为它是来自之前term(2)的日志,直到S1在当前term(4)产生的日志(4, 4)被大多数Followers确认,S1方可提交日志(4,4)这条日志,当然,根据Raft定义,(4,4)之前的所有日志也会被提交。此时即使S1再下线,重新选主时S5不可能成为Leader,因为它没有包含大多数节点已经拥有的日志(4,4)。
五、日志压缩
在实际的系统中,不能让日志无限增长,否则系统重启时需要花很长的时间进行回放,从而影响可用性。Raft采用对整个系统进行snapshot来解决,snapshot之前的日志都可以丢弃。每个副本独立的对自己的系统状态进行snapshot,并且只能对已经提交的日志记录进行snapshot。Snapshot中包含以下内容:日志元数据。最后一条已提交的 log entry的 log index和term。这两个值在snapshot之后的第一条log entry的AppendEntries RPC的完整性检查的时候会被用上。系统当前状态。当Leader要发给某个日志落后太多的Follower的log entry被丢弃,Leader会将snapshot发给Follower。或者当新加进一台机器时,也会发送snapshot给它。发送snapshot使用InstalledSnapshot RPC(RPC细节参见八、Raft算法总结)。做snapshot既不要做的太频繁,否则消耗磁盘带宽, 也不要做的太不频繁,否则一旦节点重启需要回放大量日志,影响可用性。推荐当日志达到某个固定的大小做一次snapshot。做一次snapshot可能耗时过长,会影响正常日志同步。可以通过使用copy-on-write技术避免snapshot过程影响正常日志同步。
六、成员变更
成员变更是在集群运行过程中副本发生变化,如增加/减少副本数、节点替换等。成员变更也是一个分布式一致性问题,既所有服务器对新成员达成一致。但是成员变更又有其特殊性,因为在成员变更的一致性达成的过程中,参与投票的进程会发生变化。如果将成员变更当成一般的一致性问题,直接向Leader发送成员变更请求,Leader复制成员变更日志,达成多数派之后提交,各服务器提交成员变更日志后从旧成员配置(Cold)切换到新成员配置(Cnew)。因为各个服务器提交成员变更日志的时刻可能不同,造成各个服务器从旧成员配置(Cold)切换到新成员配置(Cnew)的时刻不同。成员变更不能影响服务的可用性,但是成员变更过程的某一时刻,可能出现在Cold和Cnew中同时存在两个不相交的多数派,进而可能选出两个Leader,形成不同的决议,破坏安全性。
成员变更的某一时刻Cold和Cnew中同时存在两个不相交的多数派由于成员变更的这一特殊性,成员变更不能当成一般的一致性问题去解决。为了解决这一问题,Raft提出了两阶段的成员变更方法。集群先从旧成员配置Cold切换到一个过渡成员配置,称为共同一致(joint consensus),共同一致是旧成员配置Cold和新成员配置Cnew的组合Cold U Cnew,一旦共同一致Cold U Cnew被提交,系统再切换到新成员配置Cnew。
Raft两阶段成员变更Raft两阶段成员变更过程如下:Leader收到成员变更请求从Cold切成Cold,new;Leader在本地生成一个新的log entry,其内容是Cold∪Cnew,代表当前时刻新旧成员配置共存,写入本地日志,同时将该log entry复制至Cold∪Cnew中的所有副本。在此之后新的日志同步需要保证得到Cold和Cnew两个多数派的确认;Follower收到Cold∪Cnew的log entry后更新本地日志,并且此时就以该配置作为自己的成员配置;如果Cold和Cnew中的两个多数派确认了Cold U Cnew这条日志,Leader就提交这条log entry并切换到Cnew;接下来Leader生成一条新的log entry,其内容是新成员配置Cnew,同样将该log entry写入本地日志,同时复制到Follower上;Follower收到新成员配置Cnew后,将其写入日志,并且从此刻起,就以该配置作为自己的成员配置,并且如果发现自己不在Cnew这个成员配置中会自动退出;Leader收到Cnew的多数派确认后,表示成员变更成功,后续的日志只要得到Cnew多数派确认即可。Leader给客户端回复成员变更执行成功。异常分析:如果Leader的Cold U Cnew尚未推送到Follower,Leader就挂了,此后选出的新Leader并不包含这条日志,此时新Leader依然使用Cold作为自己的成员配置。如果Leader的Cold U Cnew推送到大部分的Follower后就挂了,此后选出的新Leader可能是Cold也可能是Cnew中的某个Follower。如果Leader在推送Cnew配置的过程中挂了,那么同样,新选出来的Leader可能是Cold也可能是Cnew中的某一个,此后客户端继续执行一次改变配置的命令即可。如果大多数的Follower确认了Cnew这个消息后,那么接下来即使Leader挂了,新选出来的Leader肯定位于Cnew中。两阶段成员变更比较通用且容易理解,但是实现比较复杂,同时两阶段的变更协议也会在一定程度上影响变更过程中的服务可用性,因此我们期望增强成员变更的限制,以简化操作流程。两阶段成员变更,之所以分为两个阶段,是因为对Cold与Cnew的关系没有做任何假设,为了避免Cold和Cnew各自形成不相交的多数派选出两个Leader,才引入了两阶段方案。如果增强成员变更的限制,假设Cold与Cnew任意的多数派交集不为空,这两个成员配置就无法各自形成多数派,那么成员变更方案就可能简化为一阶段。那么如何限制Cold与Cnew,使之任意的多数派交集不为空呢?方法就是每次成员变更只允许增加或删除一个成员。可从数学上严格证明,只要每次只允许增加或删除一个成员,Cold与Cnew不可能形成两个不相交的多数派。一阶段成员变更:成员变更限制每次只能增加或删除一个成员(如果要变更多个成员,连续变更多次)。成员变更由Leader发起,Cnew得到多数派确认后,返回客户端成员变更成功。一次成员变更成功前不允许开始下一次成员变更,因此新任Leader在开始提供服务前要将自己本地保存的最新成员配置重新投票形成多数派确认。Leader只要开始同步新成员配置,即可开始使用新的成员配置进行日志同步。
七、Raft与Multi-Paxos的异同
Raft与Multi-Paxos都是基于领导者的一致性算法,乍一看有很多地方相同,下面总结一下Raft与Multi-Paxos的异同。Raft与Multi-Paxos中相似的概念:
Raft与Multi-Paxos中相似的概念Raft与Multi-Paxos的不同:
八、Raft算法总结R
aft算法各节点维护的状态:
Raft各节点维护的状态Leader选举:
Leader选举日志同步:
日志同步Raft状态机:
Raft状态机安装snapshot:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。