从Paxos到Raft,分布式一致性算法解析
一、CAP理论和BASE理论
1. CAP理论
(1)一致性(Consistency)
(2)可用性(Availablity)
(3)分区容忍性(Partition-torlerance)
如果要保证C(一致性),那么它需要把消息复制到所有节点,但是网络分区导致无法成功复制到n3~n5,所以它只能返回"处理失败"的结果给客户端。(这时系统就处于不可用状态,即丧失了A)
如果要保证可用性A,那么n1就只能把消息复制到n2,而不用复制到n3~n5(或者无视复制失败/超时),但n3同时也可能在处理客户端请求,n3也为了保证A而做了同样的处理。那么 [n1、n2]和[n3~n5]的状态就不一致了,于是就丧失了C(一致性)。
如果不容忍网络分区,则P(分区容忍性)不能满足。
2. BASE理论
BASE理论有三项指标:
基本可用(Basically Available):是指分布式系统在出现不可预知故障的时候,允许损失部分可用性:比如响应时间、功能降级等;
软状态( Soft State):也称为弱状态,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点之间进行数据同步的过程存在延时;
最终一致性( Eventual Consistency):强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达成一致的状态。
二、经典分布式算法
1. PAXOS算法
该算法的目标是:在不同的节点间,对一个key的取值达成共识(同一个值)。
(1)角色
提案者:对key的值提出自己的值;
决议者:对提案者的提议进行投票,选定一个提案,形成最终决策;
学习者:学习决议者达成共识的决策。
(2)约束条件推导
本小节将通过推导来引出Paxos算法的约束条件,故事灵感来自于分布式理论:深入浅出Paxos算法【1】,在原文基础上优化了可读性。 https://my.oschina.net/u/150175/blog/2992187
在会议上,由提案者P提出姓氏,然后决议者A来决定选择哪一个。
场景1: Pa向 A1提出“赵”, Pb向A2,A3提出“钱”。现在有超过半数的决策者接受了“钱”,所以最终的决策结果就是“钱”。
这里引出了一个基础的约束条件:
P0. 当集群中,超过半数的Acceptor接受了一个议案,那我们就可以说这个议案被选定了(Chosen)
场景2 :Pa向 A1提出“赵”, Pb向A2提出“钱”, Pc向A3提出“孙”,这样就出现了三个决议者各执一票,无法形成多数派决议的局面。
因此,必须允许一个决策者能够接受多个议案,后接受的议案可以覆盖之前的议案。这样,Pc可以向所有决策者提起议案“孙”,形成一个多数派决议。
我们提炼一下问题:在不同版本的提案中,选择一个固定的值作为全局决议。
-
T1:一次选举必须要选定一个议案(不能出现所有议案都被拒绝的情况); T2:一次选举必须只选定一个议案(不能出现两个议案有不同的值,却都被选定的情况)。
P1:一个Acceptor必须接受它收到的第一个议案。
P2:如果一个值为v的议案被选定了,那么被选定的更大编号的议案,它的值必须也是v。
a:如果一个值为v的议案被选定了,那么Acceptor接受的更大编号的议案,它的值必须也是v。
b:如果一个值为v的议案被选定了,那么Proposer提出的更大编号的议案,它的值必须也是v。
那么,如何才能满足P2呢,P2a约束是在场景2的问题决策域提出的。为了能够满足P2a,自然可以在问题的产生域也提出约束, 既P2b。如果算法能够满足P2b,也就是将解决“产生一致性问题”的时机提前。所以,P2b是P2a的充分条件,只要能满足P2b,那P2a就自动满足。
P2c: 在所有Acceptor中,任意选取半数以上的集合,称这个集合为S。Proposal新的提案Pnew (Nnew , Vnew )必须符合下面两个条件之一: 1)如果S中所有Acceptor都没有接受过议案,那么Pnew的编号保证唯一性和递增,Pnew的值可以是任意值。 2)如果S中有Acceptor曾经接受过议案,找出编号最大的议案PN (N,V) 。那么Pnew的编号必须大于N,Pnew的值必须等于V。
反之,如果多数派集合中没有被决议的议案,表示此时系统中还没有一个被选定的决议。
因此,满足P2c的算法也满足P2b算法。
P3:议案(n,v)在提出前,必须将自己的编号通知给半数以上的Acceptor。收到通知的Acceptor将n跟自己之前收到的通知进行比较,如果n更大,就将n确认为最大编号。当半数以上Acceptor确认n是最大编号时,议案(n,v)才能正式提交。
P4:Acceptor收到一个新的编号n,在确认n比自己之前收到的编号大时,必须承诺不再接受比n小的议案。
至此,Paxos算法的约束条件就介绍完了。
(3)协议流程
-
Proposer向超过半数(n/2+1)Acceptor发起prepare消息(发送编号); 如果Acceptor 收到一个编号为 N 的 Prepare 请求,且 N 大于该 Acceptor 已经响应过的所有 Prepare 请求的编号,那么它就会将它已经接受过的编号最大的提案(如果有的话)作为响应反馈给 Proposer,同时该Acceptor 承诺不再接受任何编号小于 N 的提案。否则拒绝返回。
如果超过半数Acceptor回复promise,Proposer向Acceptor发送accept消息。注意此时的V:V 就是收到的响应中编号最大的提案的,如果响应中不包含任何提案,那么V 就由 Proposer 自己决定;
Acceptor检查accept消息是否符合规则,只要该 Acceptor 没有对编号大于 N 的 Prepare 请求做出过响应,它就接受该提案。
PBFT(实用拜占庭容错)算法:是一种共识算法,较高效地解决了拜占庭将军问题;
Multi-Paxos算法:优化了prepare阶段的效率,同时允许多个Leader并发提议;以及FastPaxos、EPaxos等,这些演变是针对某些问题进行的优化,内核思想还是依托于Paxos。
2. Raft算法
Leader选举:当前leader跪了或集群初始化的情况下,新leader被选举出来。
日志复制:leader必须能够从客户端接收请求,然后将它们复制给其他机器,强制它们与自己的数据一致。
安全性:如何保证上述选举和日志复制的安全,使系统满足最终一致性。
(1)概念介绍
Leader:处理与客户端的交互和与follower的日志复制等,一般只有一个Leader;
Follower:被动学习Leader的日志同步,同时也会在leader超时后转变为Candidate参与竞选;
Candidate:在竞选期间参与竞选;
(2)协议流程
这个转变过程中会发生四件事:
增加本地节点的Current Term值;
将状态切换为Candidate;
该节点投自己一票;
批量向其他节点发送拉票请求(RequestVote RPC)。
在一个任期Term中只能投一张票;
候选人的Term值大于Current Term,且候选人已执行的Log序号不低于本地Log(Log及已执行的概念见《日志复制》小节),则拉票请求是合法的;
只选择首先到达的合法拉票请求;
如果一次选举中,Candidate在选举超时时间内没有收到超过半数的投票,也没有收到其他Leader的请求,则认为当前Term选举失败,进入下一个选举周期。
复制状态机(Replicated state machines)是指:不同节点从相同的初始状态出发,执行相同顺序的输入指令集后,会得到相同的结束状态。
If two identical, deterministic processes begin in the same state and get the same inputs in the same order, they will produce the same output and end in the same state.
在Raft算法中,节点初始化后具有相同初始状态。为了提供相同的输入指令集这个条件,raft将一个客户端请求(command)封装到一个log entry中。Leader负责将这些log entries 复制到所有的Follower节点,然后节点按照相同的顺序应用commands,达到最终的一致状态。
当Leader收到客户端的写请求,到将执行结果返回给客户端的这个过程,从Leader视角来看经历了以下步骤:
本地追加日志信息;
并行发出 AppendEntries RPC请求;
等待大多数Follower的回应。收到查过半数节点的成功提交回应,代表该日志被复制到了大多数节点中(committed);
在状态机上执行entry command。既将该日志应用到状态机,真正影响到节点状态(applied);
回应Client 执行结果;
确认Follower也执行了这条command;如果Follower崩溃、运行缓慢或者网络丢包,Leader将无限期地重试AppendEntries RPC,直到所有Followers应用了所有日志条目。
raft step by step入门演示: http://thesecretlivesofdata.com/raft/
raft 官方演示: https://raft.github.io/
3. 安全性及约束
在raft中,通过两点保证了这个属性:
一个节点某一任期内最多只能投一票;而节点B的term必须比A的新,A才能给B投票。
只有获得多数投票的节点才会成为leader。
其次,一致性检查。leader在AppendEntries请求中会包含最新log entry的前一个log的term和index,如果follower在对应的term index找不到日志,那么就会告知leader日志不一致, 然后开始同步自己的日志。同步时,找到日志分叉点,然后将leader后续的日志复制到本地。
Raft的日志机制提供两个保证,统称为Log Matching Property:
不同机器的日志中如果有两个entry有相同的偏移和term号,那么它们存储相同的指令。
如果不同机器上的日志中有两个相同偏移和term号的日志,那么日志中这个entry之前的所有entry保持一致。
如果一个log entry在某个任期被提交(committed),那么这条日志一定会出现在所有更高term的leader的日志里面 。选举人必须比自己知道的更多(比较term,log index)如果日志中含有不同的任期,则选最新的任期的节点;如果最新任期一致,则选最长的日志的那个节点。
选举安全性中包含了Leader完备性
三、总语
“这个世界上只有一种一致性算法,那就是 Paxos”
https://my.oschina.net/u/150175/blog/2992187
https://zh.wikipedia.org/wiki/Paxos算法
-
https://blog.51cto.com/12615191/2086264
https://web.stanford.edu/~ouster/cgi-bin/papers/raft-atc14
https://raft.github.io/
http://thesecretlivesofdata.com/raft/
https://www.cnblogs.com/xybaby/p/10124083.html