共识算法-Paxos&Raft

1.共识算法的提出

先讲个故事:

​ 一组拜占庭将军分别各率领一支军队共同围困一座城市。各支军队的行动策略限定为进攻或撤离两种。因为部分军队进攻部分军队撤离可能会造成灾难性后果,因此各位将军必须通过投票(少数服从多数)来达成一致策略,即所有军队一起进攻或所有军队一起撤离。因为各位将军分处城市不同方向,他们只能通过信使互相联系。在投票过程中每位将军都将自己投票给进攻还是撤退的信息通过信使分别通知其他所有将军,这样一来每位将军根据自己的投票和其他所有将军送来的信息就可以知道共同的投票结果而决定行动策略。(来自维基百科)。

这个事情可能会产生两个问题:

1.各将军之中可能有人叛变。

2.信使可能叛变,信使可能被截杀。

以上所介绍的就是著名的“拜占庭将军”问题,可能看了上边的文字大家不太理解,那么我们画个图:

image-20210724163319124

​ 如果B1、B2、B3、B4、B5五组拜占庭将军共同围困蓝色的敌军,但此时B2叛变了会发生什么呢,如果B2收到B1、B5的进攻指令,收到B3、B4的撤退指令,现在需要B2投出关键一票了,此时他和B1、B5说自己要进攻,但是和B3、B4说自己要撤退,这样在B1、B5看来有三票进攻,在B4、B5看来有三票撤退,这样就出现了行动不一致的情况。

​ 如果在各个将军之间安排了信使进行通信,但是信使叛变了,向各将军传递了错误的信息,也会造成上述行动不一致的情况。信使还有可能出现被截杀的情况,这样就导致了信无法到达每个将军的手中,假设B1的部分信使被杀了,那么B1的信息可能传递到了B5、B2但是没有传递到B3、B4,这样也会造成情况不一致,其实在真实的过程中还可能产生更多复杂的情况,就不做出一一的介绍了。

​ 上述的故事映射到计算机系统里,将军便成了计算机,而信差就是通信系统。虽然上述的问题涉及了电子化的决策支持与信息安全,却没办法单纯的用密码学与数字签名来解决。因为电路错误仍可能影响整个加密过程,这不是密码学与数字签名算法在解决的问题。因此计算机就有可能将错误的结果提交去,亦可能导致错误的决策。

如何解决问题

​ 以上的问题,显然是所有的将军要达成一个共识(撤退或者进攻),那么在计算机系统中或者分布式系统中,为了达成这个共识,科学家们设计了一系列的算法,这就是共识算法。

1.非拜占庭容错算法

​ 故障容错算法(Crash Fault Tolerance, CFT), 即非拜占庭容错算法,解决的是分布式系统中存在故障,但不存在恶意攻击的场景下的共识问题。也就是说,在该场景下可能存在消息丢失,消息重复,但不存在消息被篡改或伪造的场景。一般用于局域网场景下的分布式系统,如分布式数据库。属于此类的常见算法有Paxos算法(主要指下文中的三种算法)、Raft算法,、ZAB协议等。

2.拜占庭容错算法

​ 拜占庭容错算法,可以解决分布式系统中既存在故障,又存在恶意攻击场景下的共识问题。一般用于互联网场景下的分布式系统,如在数字货币的区块链技术中。属于此类的常见算法有PBFT算法、PoW算法。

2.Paxos算法介绍

​ 因为我们的分布式系统有大多数的情况都是处在同一个局域网内,基本不太可能产生黑客攻击以及挟持等问题,所以在大多数分布式系统尤其是分布式存储系统中主要还是以非拜占庭容错算法为主。这也就是我们的Paxos算法、Raft算法,、ZAB协议等。我们首先介绍一下Paxos算法,因为他是所有分布式一致性算法的基石、鼻祖。【Paxos是共识算法,但是放在分布式系统中又叫分布式一致算法,可以理解为这两种叫法分别是站在不同的角度】

再来一个故事

​ 一个叫做Paxos的小岛上面住了一批居民,岛上所有的事情由一些特殊的人决定,他们叫做议员。议员的总数是确定的,不能更改。岛上每次事务的变更都需要通过一个提议,每个提议都有一个编号(PID),这个编号是一直增长的,不能倒退。每个提议如果想要通过都需要超过半数的议员同意才可以。每个议员手中都有个小本本,来记录上一个提议的编号,每个议员只会同意大于当前编号的提议,如果议员收到小于等于当前编号的提议,他会拒绝,并告知对方:你的提议已经有人提过了。每个议员会不断变更自己小本本上的编号。并且所有议员小本本上的编号总是相同的。

​ 上边这个故事在1990由Lamport在《The Part-time Parliament》的论文中介绍的,但是原文1998年才得以发表。后来Lamport又重新写了一篇[《Paxos Made Simple》][https://lamport.azurewebsites.net/pubs/paxos-simple.pdf]。

Paxos的地位

​ 这个算法在分布式领域有着非常重要的地位,具体重要到什么程度呢?Google的Chubby的作者Mike Burrows说这个世界上只有一种一致性算法,那就是Paxos,其它的算法都是残次品。虽然这句话有些夸大,但是足以证明这个算法的地位,尤其是后来的Raft算法,、ZAB协议等都是基于Paxos算法演化而来并真正落实到工业环境中,包括我们58的WMB的存储设计理念也来自于经典Paxos算法(下文中会有介绍)。

用张图来解释一下:

image-20210724183754334

如上图所示,Paxos算法分出了Basic-Paxos、Multi-Paxos以及Fast-Paxos等,这三种都是学术界有关一致性算法的讨论,学术界的讨论一般停留在理论阶段,比较难于实现,后来就基于Multi-Paxos演化出了可以实际落地的Raft和ZAB算法,这两种算法理论提出以后肯定就会有实际的项目落地使用,后边这几个开源组件像Tidb、Etcd以及Zookeeper等都是基于Raft算法实现了自己的分布式存储。

3.Paxos原理解析

​ 说了那么多,让我们真正开始Paxos算法原理的解析,有点难于理解,大家耐着性子仔细看,如有错误请指正。

Basic-Paxos

在经典的Paxos算法中提出了三种角色:

  • proposers(提议者)
  • acceptors(接受者/决策者)
  • learners(记录员,因为不重要所以可以被代替)

​ 三个角色具体怎么配合的?proposers负责通过两阶段提交的方式提出提案,提案的信息包括提案的唯一编号(PID)以及提案的具体内容记为P(id,value)。第一阶段通过多数派读的方式(就是请求决策者中的n/2+1)请求acceptors,acceptors负责判断提案编号是否大于自己小本本上记的最大编号以此来决定回复是否允许proposers进行提案,如果多数派同意proposers进行提案,那么proposers就会向所有的acceptors发起第二阶段真正的提案请求,acceptors接受到提案请求后同样再次对编号进行判断来决定是否批准这个提案,如果提案最终被大多数的accetors同意即达到了多数派(一般为n/2+1),则该提案被批准。记录员(learners)就负责记录一下这个被批准的提案。

​ 如何解释的形象一些?就像你正在上课,同学们想回答问题,纷纷同时举手,于是老师就参照学号选取了最大学号的同学进行作答,最大学号的同学站起来作答,作答完毕老师让同学坐下。这就是一个形象两阶段提交,选取最大编号。同学举手到老师看到,老师允许你作答你才能站起来,这就是第一阶段的提交。当你站起来进行正式进行作答直到作答完毕,老师赞成你的答案让你坐下这就是第二阶段的提交。(实在想不到更好的例子了)

如何更形象一点,继续画个图加深理解:

image-20210725010745239

通过一个决议分为两个阶段:

  1. prepare阶段:
    1. proposer选择一个提案编号n并将prepare请求发送给acceptors中的一个多数派;
    2. acceptor收到prepare消息后,如果提案的编号大于它已经回复的所有prepare消息(回复消息表示接受accept),则acceptor将自己上次接受的提案回复给proposer,并承诺不再回复小于n的提案;
  2. 批准阶段:
    1. 当一个proposer收到了多数acceptors对prepare的回复后,就进入批准阶段。它要向回复prepare请求的acceptors发送accept请求,包括编号n和根据P2c决定的value(如果根据P2c没有已经接受的value,那么它可以自由决定value)。
    2. 在不违背自己向其他proposer的承诺的前提下,acceptor收到accept请求后即批准这个请求。

刚才我们结论先行,那么这个Paxos算法是如何被证明的呢?

算法证明中文过程(摘自维基百科):

首先将议员的角色分为 proposers,acceptors,和 learners(允许身兼数职)。proposers 提出提案,提案信息包括提案编号和提议的 value;acceptor 收到提案后可以接受(accept)提案,若提案获得多数派(majority)的 acceptors 的接受,则称该提案被批准(chosen);learners 只能“学习”被批准的提案。划分角色后,就可以更精确的定义问题:

  1. 决议(value)只有在被 proposers 提出后才能被批准(未经批准的决议称为“提案(proposal)”);
  2. 在一次 Paxos 算法的执行实例中,只批准(chosen)一个 value;
  3. learners 只能获得被批准(chosen)的 value。
在 Leslie Lamport 之后发表的paper中将 majority 替换为更通用的 quorum 概念,但在描述classic paxos的论文  Paxos made simple(页面存档备份,存于互联网档案馆) 中使用的还是majority的概念。

另外还需要保证 progress。这一点以后再讨论。

作者通过不断加强上述3个约束(主要是第二个)获得了 Paxos 算法。

批准 value 的过程中,首先 proposers 将 value 发送给 acceptors,之后 acceptors 对 value 进行接受(accept)。为了满足只批准一个 value 的约束,要求经“多数派(majority)”接受的 value 成为正式的决议(称为“批准”决议)。这是因为无论是按照人数还是按照权重划分,两组“多数派”至少有一个公共的 acceptor,如果每个 acceptor 只能接受一个 value,约束2就能保证。

于是产生了一个显而易见的新约束:

P1:一个 acceptor 必须接受(accept)第一次收到的提案。

注意 P1 是不完备的。如果恰好一半 acceptor 接受的提案具有 value A,另一半接受的提案具有 value B,那么就无法形成多数派,无法批准任何一个 value。

约束2并不要求只批准一个提案,暗示可能存在多个提案。只要提案的 value 是一样的,批准多个提案不违背约束2。于是可以产生约束 P2:

P2:一旦一个具有 value v 的提案被批准(chosen),那么之后批准(chosen)的提案必须具有 value v。

注:通过某种方法可以为每个提案分配一个编号,在提案之间建立一个全序关系,所谓“之后”都是指所有编号更大的提案。

如果 P1 和 P2 都能够保证,那么约束2就能够保证。

批准一个 value 意味着多个 acceptor 接受(accept)了该 value。因此,可以对 P2 进行加强:

P2a:一旦一个具有 value v 的提案被批准(chosen),那么之后任何 acceptor 再次接受(accept)的提案必须具有 value v。

由于通信是异步的,P2a 和 P1 会发生冲突。如果一个 value 被批准后,一个 proposer 和一个 acceptor 从休眠中苏醒,前者提出一个具有新的 value 的提案。根据 P1,后者应当接受,根据 P2a,则不应当接受,这种场景下 P2a 和 P1 有矛盾。于是需要换个思路,转而对 proposer 的行为进行约束:

P2b:一旦一个具有 value v 的提案被批准(chosen),那么以后任何 proposer 提出的提案必须具有 value v。

由于 acceptor 能接受的提案都必须由 proposer 提出,所以 P2b 蕴涵了 P2a,是一个更强的约束。

但是根据 P2b 难以提出实现手段。因此需要进一步加强 P2b。

假设一个编号为 m 的 value v 已经获得批准(chosen),来看看在什么情况下对任何编号为 n(n>m)的提案都含有 value v。因为 m 已经获得批准(chosen),显然存在一个 acceptors 的多数派 C,他们都接受(accept)了v。考虑到任何多数派都和 C 具有至少一个公共成员,可以找到一个蕴涵 P2b 的约束 P2c:

P2c:如果一个编号为 n 的提案具有 value v,该提案被提出(issued),那么存在一个多数派,要么他们中所有人都没有接受(accept)编号小于 n 的任何提案,要么他们已经接受(accept)的所有编号小于 n 的提案中编号最大的那个提案具有 value v。

可以用数学归纳法证明 P2c 蕴涵 P2b:

假设具有value v的提案m获得批准,当n=m+1时,采用反证法,假如提案n不具有value v,而是具有value w,根据P2c,则存在一个多数派S1,要么他们中没有人接受过编号小于n的任何提案,要么他们已经接受的所有编号小于n的提案中编号最大的那个提案是value w。由于S1和通过提案m时的多数派C之间至少有一个公共的acceptor,所以以上两个条件都不成立,导出矛盾从而推翻假设,证明了提案n必须具有value v;

若(m+1)…(N-1)所有提案都具有value v,采用反证法,假如新提案N不具有value v,而是具有value w’,根据P2c,则存在一个多数派S2,要么他们没有接受过m…(N-1)中的任何提案,要么他们已经接受的所有编号小于N的提案中编号最大的那个提案是value w’。由于S2和通过m的多数派C之间至少有一个公共的acceptor,所以至少有一个acceptor曾经接受了m,从而也可以推出S2中已接受的所有编号小于n的提案中编号最大的那个提案的编号范围在m…(N-1)之间,而根据初始假设,m…(N-1)之间的所有提案都具有value v,所以S2中已接受的所有编号小于n的提案中编号最大的那个提案肯定具有value v,导出矛盾从而推翻新提案N不具有value v的假设。根据数学归纳法,我们证明了若满足P2c,则P2b一定满足。

P2c是可以通过消息传递模型实现的。另外,引入了P2c后,也解决了前文提到的P1不完备的问题。

上边正向证明的过程有点难于理解,但是我想在知道结论的情况下利用反证法来简单证明一下结论的正确性还是可以的。如果我们不采用按照提案编号最大者进行提案或者不进行两阶段提交会有什么问题呢?

于是来两个图:

image-20210725024226922

上图图展示了在没有自增id的情况下,如果C客户端进行多数派读取会得到a=1和a=2两个值,但是他不知道要取哪一个如果此时有个戳记来标志最后一次提交那么它只需要通过戳记来读取最后一次的写入就可以。

image-20210725024409038

上图展示当只有一轮RPC的情况下,如果A客户端和B客户端在统一时刻获取了a进行操作,分别对a进行加一和加二操作,我们希望最终C客户端在读取的时候能够读取到4,但是实际结果却是3,因为B客户端把A客户端写入的数据覆盖了。所以我们希望B客户端在A客户端操作以后再进行操作,这也就要求B客户端需要知道有人在操作a数据。如果在写入之前进行一个多数派的写前读,此时就会抛弃掉比B客户端编号小的提案请求,从而保证写入顺序。

​ 所以通过上述两个反例我们也能窥探出Paxos在控制分布式存储系统的一致性上有一定的合理性。

Basic-Paxos算法的缺点

​ 1.要经历两轮RPC,这个网络传输的过程是耗时的。

​ 2.角色太多,如果落地实现要考虑多种角色的管理和分配。

​ 3.会产生活锁,当一个proposer发现存在编号更大的提案时将终止提案。这意味着提出一个编号更大的提案会终止之前的提案过程。如果两个proposer在这种情况下都转而提出一个编号更大的提案,就可能陷入活锁。【活锁也可以通过 随机睡眠-重试 的方法解决,即当P2提案编号大于P1的提案编号时,P1会先随机睡眠一段时间,然后使用更大的编号进行二次请求】

Multi-Paxos

​ 因为Basic-Paxos存在一些问题,所以演化了Multi-Paxos,Multi-Paxos主要改变在于指定了唯一的Proposer(Leader),并且减少了一次RPC调用。朴素的Paxos算法会产生活锁的原因在于有多个Proposer进行提案,如果改为一个Propser,所有的请求都打到这个Leader上,这样用Leader来控制顺序,活锁的问题也就不存在了。如果只有一个Proposer那么两次RPC的要求也就不存在了,因为我们可以通过一个Leader进行顺序的严格控制,上边举的反例也就不复存在了。

话不多说,来张图:

image-20210725153256050

通过上图可以看到,我们Proposer只有一个,只有在请求成为Leader的时候会发生一次RPC,后续的client请求,都只需要直接打到Leader上,然后发起一次RPC即可,通过这种方式避免了,两次的RPC以及活锁的问题。

Fast-Paxos

​ 在Basic-Paxos和Multi-Paxos中都需要经历Client->Proposer->Acceptor->Learner,我们是否可以继续简化这个过程呢,Fast-Paxos就为我们提供了思路,思路就是如果Proposer本身没有数据需要被确认的话,那么Client可以直接发送Accept请求给Acceptor,从而跳过Proposer这一步即演变成Client->Acceptor->Learner,这样就会减少一次请求时延。但是因为此时Client有可能有多个,可能在同一时刻提出编号为N的提案,这样就会发生冲突,所以当有冲突发生的时候我们还需要Leader的决定或者Acceptor的自主决定,这就是Fast-Paxos算法核心流程。

在不发生冲突的情况下(即只有一个客户端):

image-20210725154932923

如果有了多个客户端就会发生冲突,如果发生冲突,在Fast-Paxos的理论中有两种解决方案,一种就是通过Leader进行介入判断,然后重新发起提案,另一种解决方案就是由Acceptor自行解决,如果交由Acceptor解决那么就需要Client和Acceptor都记录上一次决议确定(chosen)的历史,用以在提议前知道哪个proposer的提议上一次被确定、当次决议能不能节省一次通信步骤。

1.如果发生冲突由Leader进行重新提案

image-20210725160747438

2.如果发生冲突,由Acceptor进行重新提案(这样可以少一次rtt)

image-20210725161904428

以上就是三种Paxos算法的介绍,文章中主要介绍了这三种,当然Paxos还有其他很多的变种,比如Egalitarian-Paxos、Generalized-Paxos、Byzantine-Paxos等,这些变种就不在文章中做过多的介绍了(主要是我也没看明白),有兴趣的同学可以自行了解。

4.Raft整体介绍

​ 由于篇幅有限在本文中我想主要介绍Raft的大体原理和使用,在后续的文章中会有关于Raft协议的精读内容。

​ 有了上文中关于Paxos的介绍,Raft就会好理解很多,Raft主要是提供了工业的实现细节从而能够具体将算法落地,比如Raft中会从数据同步以及leader选举等方面进行全面设计,让整个流程更加具象。

​ 上文中提到Raft是Multi-Paxos的简化版、工业版,那么肯定还是会和Paxos是有联系和区别的的,让我们来看下Raft都有什么?

1.Raft重新定义了角色

  • Leader(领导者)
  • Follower(跟随者)
  • Candidate(候选者)

2.将整个流程进行拆解,拆解为三个字问题

  • 领导选举:当现存的领导人宕机的时候,一个新的领导人需要被选举出来。
  • 日志复制:领导人必须从客户端接收日志然后复制到集群中的其他节点,并且强制要求其他节点的日志保持和自己相同。
  • 安全性:如果有任何的服务器节点已经应用了一个确定的日志条目到它的状态机中,那么其他服务器节点不能在同一个日志索引位置应用一个不同的指令。

我们本篇主要介绍Raft是如何选举leader和如何进行日志的复制,以及在节点宕机的情况下如何进行重新选举等一些实现过程。

用两个动画来简单演示:

https://raft.github.io/

http://thesecretlivesofdata.com/raft/

参考资料:

https://raft.github.io/

http://thesecretlivesofdata.com/raft/

https://zhuanlan.zhihu.com/p/145044486

https://cloud.tencent.com/developer/article/1015442

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值