Paxos算法
Paxos算法是一种基于消息传递且具有高度容错特性的一致性算法,它于1990年由莱斯利·兰伯特提出。有名 的Paxos工程包括Google的Chubby、ZAB、微信的PhPaxos等。
一般来说,分布式各个节点之间的通讯模型有两种:共享内存(Shared Memory)和消息传递(Message Passing),Paxos基于消息传递通讯模型的。
一、Paxos简介
Paxos角色
Paxos算法中有三种角色,分别具有三种不同的行为,但他们可能在一个线程中充当着多种角色。
- Proposer:提案proposal的提议者
- Acceptor:提案的表决者,决定是否接受该提案,只有当半数以上的表决者接受提案时,此提案才会被选定。
- Learners:提案的学习者,当提案选定后,提案内容的执行者。
在一个集群中,会有多个提议者提出不同的提案,而一个提案会有多个表决者,相应的在天被选定后也会有多个学习者。Paxos算法有以下几个特点:
- 只有被提出的提案才会被选定
- 每个提议者在提出提案时,都会获取一个在整个集群中全局唯一的递增提案编号N,将编号赋予提出的提案。
- 每个表决者在接受某一提案后,会将提案编号N记录在本地,即表决者会存在一个编号最大的提案,记为maxN,每个表决者只会accept编号大于本地maxN的提案。
- 当某一提案被选定后,会将该提案同步(learn)到其他服务器
- 相同操作的提案只能有一个提案被选定
二、算法过程
Paxos算法的执行过程互粉为两个阶段:准备阶段(prepare)与接受阶段(accept)。如图所示对下图过程进行分析。
1、prepare阶段
1)提议者proposer提出一个编号为N的提议,它首先向多有表决者acceptor发送请求prepare(N),用于测试集群是否支持该提案。
2)每个acceptor接收到表决者发过来的提案请求,会先从本地拿出曾经accept过提案的最大编号maxN,比较N与maxN。会有以下情况:
- N小于maxN,说明该提案已过时,acceptor会回应Error或不回应 的方式拒绝prepare请求。
- N大于maxN,此时该提案可以接受,表决者会首先将该提案的N记录下来,并将曾将accept过的编号最大的提案提案以proposal(myid,maxN,value)的方式反馈给提议者,表示自己支持该提案。其中myid是表决者的表示id,maxN为曾接受的提案最大编号,value表示编号最大提案的内容。若表决者此前未accept任何提案,则反馈的消息为proposal(myid,null,null)。
注意:
在prepare阶段N不可能等于maxN,这是因为N 是采用同步锁增一的方式产生的,N是全局唯一递增的。
2、accept阶段
1)当提议者proposer发出prepare(N)之后,若收到超过半数表决者接受提案的反馈,该提议者会将其真正的提案以proposal(N,value)的方式发送给所有表决者。
2)当acceptor接受提议者发送的proposal(N,value)提案后,会再次拿出曾经accept过的提案最大编号maxN,及曾经记录下的prepare的最大编号,将他们与N 比较,若N大于等于这两个编号,则表决者accept该提案,并反馈给提议者。若N小于这两个编号,则采取不回应或回应error的方式拒绝提案。
3)当提议者未收到超过半数表决者accept的反馈,会递增新的提案号,重新进入prepare阶段。若接收到的accept反馈超过半数,则会向提议者广播消息:
- 向accept提案的表决者发送“可执行数据同步信号”,让他们执行accept的提案
- 向未发送accept反馈消息的表决者发送"提案 + 可执行数据同步信号",让他们接受提案并立即执行。
三、算法举例
现有这样一个场景:假设有三台主机server-1、server-2、server-3,他们依次充当提案者proposer提出leader提案,他们的身份为proposer-1、proposer-2、proposer-3,且分别获取的提案号N为:2,1,3,将提案发给三个表决者acceptor-1、acceptor-2、acceptor-3进行表决,但由于网络原因,三个提案开始进行prepare的次序为proposer-1、proposer-2、proposer-3,且每次提案总有一个表决者收不到提案:proposer-1提案acceptor-1、acceptor-2接收成功,proposer-2、proposer-3提案acceptor-2、acceptor-3接收成功.
这里假设他们均prepare阶段完成再进行accept阶段,且prepare失败会再次进行获取提案号进行prepare。
1、prepare阶段
proposer-1的prepare(2)提案请求到达acceptor-1、acceptor-2,他们之前未接收过prepare提案请求,他们直接接受该提案,反馈消息为proposal(acceptor1-id,null,null)、proposal(acceptor2-id,null,null),同时将本地的maxN记录为2,接受反馈超过半数。
proposer-2的prepare(1)提案请求到达acceptor-2、acceptor-3,其中acceptor-3之前未接收过prepare提案请求,同意提案反馈消息为proposal(acceptor3-id,null,null),将本地的maxN记录为1。acceptor-2已经接收提案,maxN为2,1小于2,acceptor-2拒绝该提案,反馈未超过半数。
proposer-3的prepare(3)提案请求到达acceptor-2、acceptor-3,其中acceptor-3接收过prepare,其maxN为1,3大于1,acceptor-3同意提案,反馈消息为proposal(acceptor3-id,null,null),修改maxN为3,类似的,server-2的maxN为2,server-2同意提案,反馈消息为proposal(acceptor2-id,null,null),修改maxN为3,反馈超过半数。
proposer-2的提案未过半,重新生成N=4,并重新prepare(4)到acceptor-2、acceptor-3,此时acceptor-2的maxN为3,acceptor-3的maxN为3,均小于4,他们反馈消息proposal(acceptor2-id,null,null)与proposal(acceptor3-id,null,null)到proposer-2,并修改各自的maxN为4,proposer-2反馈超过半数。
此时:server-1、server-2、server-3的maxN依次为:2、4、4
注意:
此处的proposer并不是均过半了才进入accept阶段,且提案失败也不一定重新进入prepare,这里讨论的只是一种情况。
2、accept阶段
a、proposer提交
proposer-1收到过半反馈,但反馈的value为null,proposer-1直接向acceptor-1、acceptor-2提交proposal(server1-id,2,server1)
proposer-2收到过半反馈,但反馈的value为null,proposer-2直接向acceptor-2、server-3提交proposal(server2-id,4,server2)
proposer-3收到过半反馈,但反馈的value为null,proposer-3直接向acceptor-2、acceptor-3提交proposal(server3-id,4,server3)
b、aceptor表决
acceptor-1,acceptor-2接收proposer-1的提案的请求proposal(server1-id,2,server1),他们均拒绝该请求
acceptor-2,acceptor-3接收proposer-2的提案的请求proposal(server2-id,4,server2),他们均通过该请求
acceptor-2,acceptor-3接收proposer-3的提案的请求proposal(server3-id,4,server3),他们均拒绝该请求
提案请求proposal(server2-id,4,server2)达成超过半数一致,server-2即将成为leader,选举结束,server-2发布广播给所有learner,通知他们同步数据,同步结束,集群进入正常服务。
四、paxos算法问题
paxos算法在实际应用中存在“活锁问题”,paxos算法的每个线程均可以提交提案,但前提是获取一个全局唯一的编号N,为保证N的唯一性,N的操作需要放到同步锁中,N值成为了竞争资源,若在此过程中,某一进程一直不停的申请资源N,但每一次都恰巧未分配给它,则该进程处于”活锁“状态。
在实际应用中,出现很多paxos算法的优化算法,如Fast Paxos算法,他只允许一个进程处理些请求,解决了活锁问题。
1、活锁
活锁指执行者没有被阻塞,但由于条件未被满足,一直重复"尝试-失败-尝试-失败"过程。处于活锁的执行者状态实在不断改变的,活锁也有可能自行解开。若某一进程处于“竞争资源”的申请一直没被满足,多次重复持续申请,此进程所处的状态称为“活锁”。
2、死锁
提到活锁就会想到死锁,现假设一个场景,有两个线程A和B与两个资源a和b,线程A占用着资源a,但由于等待资源b而阻塞,同时,线程B占用着资源b,但有线程等待资源a而阻塞,双方均陷入不能释放自己所持有的资源而陷入死锁状态。死锁必须具备以下条件才能产生:
- 互斥条件:一个资源只能被一个线程使用
- 请求与保护:一个进程因请求资源而阻塞时,对已有资源保持持有状态
- 不剥夺条件:进程已经获得的资源,子啊为使用完前不能强行剥夺
- 循环等待条件:若干线程形成一种头尾相接的循环等待资源状态
3、死锁与活锁的区别
活锁与死锁的区别在于:处于活锁状态的实体没有被阻塞,处于不断改变状态;而处于死锁状态的实体则表现为等待、阻塞。活锁有可能自行解开,死锁则不能。
从CPU角度来说,进程有三种基本状态:执行态、就绪态、阻塞态。活锁状态的线程拥有获取CPU资源的权限,其一直处于“执行态-就绪态‘转换过程中,而死锁状态的进程处于阻塞状态,不具有CPU的权限。
五、paxos算法思考
思考一:prepare阶段与accept阶段工作过程差不多,为什么需要prepare过程,二部直接进行accept阶段呢?
-
prepare阶段是“征求意见”阶段,询问所有的Acceptor是否同意该提案,同意者反馈给Proposer一个ACK,该阶段向Acceptor发送的是一个全局唯一的额编号N。
-
accept阶段是提案执行阶段,只有收到超过半数的ACK后才会让所有Learner同步提案,该阶段向Acceptor发送的是包含编号N的提案。
-
两个阶段功能是不同的,发送消息也是不同的。若将两个消息合并为一个阶段则无法进行“意见征求”,无法统计ACK。
思考二:prepare阶段propersor发送消息仅为一个编号N,accept阶段为proposal,为什么?可以反过来么?
prepare阶段发送消息是编号N,数据量小;accept阶段发送消息为proposal,数据量可能较大。而prepare阶段可能是通不过的,若通过大量数据的proposal征求意见,但没通过,会浪费网络带宽等资源。