其中:
Promise(N_A,V_A):
向Proposer保证不再接受编号不大于N_H的提案;
Accepted向Proposer发送决议被通过信息;
V_A为Acceptor之前审批过的决议(允许为空);
N_H为Acceptor之前接收提案的最高编号。
2.1.5Learner行为描述
相对来说,Learner的行为理解更简单一些:
学习value,开始执行任务。
2.1.6整体描述
算法在执行过程中,实例和实例之间是异步的;角色和角色之间既有同步的行为(因为一个完全异步的系统中,一致性问题将是无法被解决的[6]),又有异步的行为。
下表2.1中按照我的理解以全局的角度来说明这其中的关系:
表2.1一次Paxos实例
角色/时段
Phase1
Phase2
Phase3
Proposer(P)
[1]竞争Leader:
向A发送prepare
[1]接收A的promise
[2]选取一个value并发送accept
(*)
Acceptor(A)
[1]接收处理P的prepare
[2]回复reject或promise
[1]接收处理accept
[2]回复accepted或nack
[3]通知L
(*)
Learner(L)
无
无
[1]接收广播学习value或
[2]创建Proposer对象学习value
2.1.7算法总结
该算法巧妙之处在于利用了Majority机制保证了2F+1的容错能力,即具备2F+1个结点的系统最多允许F个结点同时出现故障,并且通过数学归纳的思想步步保障Majority机制。
并且从概率上展示了出现错误(无限循环竞争)的极小可能性。
2.2算法伪代码描述
这个代码换了一种角度来理解本算法,更详细的代码请见[3]。
PaxosPhase1
//proposer状态
anode(maybemorethanone...)decidestobeleader(neednotbeincurrentview):
//静态实现proposal编号唯一性
my_n=max(n_h,my_n)+1,appendnodeID
done=fals
//向所有acceptor发送提案proposal
sendsprepare(vid_h+1,my_n)toallnodesin{views[vid_h],initialcontactnode,itself}
//切换到acceptor状态
ifnodereceivesprepare(vid,n):
//如果收到的是以前的实例(譬如说一个结点刚从故障中恢复,它从日志中获得它出现故障前最近的那个任务,但它并不知道该任务已经由集群中的其他结点集完成)
ifvid<=vid_h:
returnoldview(vid,views[vid])
//如果是新的任务
elseifn>n_h:
n_h=n
done=false
//n_a过去接受的proposer的编号,如果以前都没有接受过,那么n_a=v_a=0
returnprepareres(n_a,v_a)
else:
//拒绝
returnreject()
PaxosPhase2
//proposer角色
//获得信息:
该任务已经过时
ifleadergetsoldview(vid,v):
views[vid]=v
vid_h=vid
//转如下一个paxos实例
viewchange
restartpaxos
elseifleadergetsreject():
//被拒绝,延迟,重复本实例,暗示着leader还没确定
delayandrestartpaxos
//从大多数的Acceptor获得回应
elseifleadergetsprepareresfrommajorityofnodesinviews[vid_h]:
//判断是否已经有value被确定,相当重要
ifanyprepareres(n_i,v_i)existssuchthatv_iisnotempty:
//确定value的过程
v=non-emptyvaluev_icorrespondingtohighestn_ireceived
elseleadergetstochooseavalue:
v=setofpingablenodes(includingself)
//回复Accept!
信息
sendaccept(vid_h+1,my_n,v)toallresponders
else:
//准备再次竞争
delayandrestartpaxos
//描述acceptor状态
ifnodegetsaccept(vid,n,v):
//如果是旧的任务
ifvid<=vid_h:
returnoldview(vid,views[vid])
elseifn>=n_h:
//新的任务,保存该任务,回复accepted!
信息,表明一次选举成功
n_a=n
v_a=v
returnacceptres()
else
//选举中间出了问题,说明有编号更大的提案被接受,所以该提案的proposer的accept!
信息应该被否决
returnreject()
PaxosPhase3
//proposer中的leader角色,获得旧的任务信息
ifleadergetsoldview(vid,v):
views[vid]=v
vid_h=vid
viewchange
restartpaxos
//获得最终确认信息,即proposer获知选举结束
elseifleadergetsacceptresfromamajorityofnodesinviews[vid_h]:
//开始广播
senddecide(vid_h+1,v_a)toall(includingself)
else:
delayandrestartpaxos
//learner角色
ifnodegetsdecide(vid,v):
ifvid<=vid_h:
returnoldview(vid,views[vid])
//开始学习
else:
done=true
primaryislowest-numberednodeinv
views[vid]=v
vid_h=vid
viewchange
2.3算法运行实例
这里例举了4个实例。
第一个为结点全部正常的实例;第二个为一个Acceptor出现故障的实例;第三个为一个Proposer成为Leader后发生故障的实例;第四个为两个Proposer进入无限循环竞争的实例。
2.3.1结点全部正常
该实例简单地说明了算法在实际运行中的一般情况:
ClientProposerAcceptorLearner
|||||||
X-------->||||||Request
|X--------->|->|->|||Prepare(N)
||<---------X--X--X||Promise(N,{})
|X--------->|->|->|||Accept!
(N,Vn)
||<---------X--X--X------>|->|Accepted(N,Vn)
|<---------------------------------X--XResponse
|||||||
2.3.2一个Acceptor出现故障
该实例简单地说明了在算法运行过程中一个Acceptor出现故障的实例。
值得注意的,由于一共有2F+1=3(F=1)个Acceptor,所以仅有一个Acceptor出现故障是在故障容忍范围之内的,不会影响算法的完成:
ClientProposerAcceptorLearner
|||||||
X-------->||||||Request
|X--------->|->|->|||Prepare(N)
||||!
||!
!
FAIL!
!
||<---------X--X||Promise(N,{})
|X--------->|->|||Accept!
(N,Vn)
||<---------X--X--------->|->|Accepted(N,Vn)
|<---------------------------------X--XResponse
||||||
2.3.3一个Proposer成为Leader后发生故障
该实例简单地说明了在算法运行过程中一个Proposer成为Leader后发生故障的实例。
由于是Leader出现故障,那么要比一个Acceptor出现故障要复杂得多(但不会影响算法的完成),投票表决过程会被重复,从下图也可以看出:
ClientLeaderAcceptorLearner
|||||||
X----->||||||Request
|X------------>|->|->|||Prepare(N)
||<------------X--X--X||Promise(N,{})
|||||||
|||||||!
!
Leaderfails!
!
|X------------>|||||Accept!
(N,Vn)
|!
|||||
|||||||!
!
NEWLEADER!
!
|X--------->|->|->|||Prepare(N+1)
||<---------X--X--X||Promise(N+1,{Vn})
|X--------->|->|->|||Accept!
(N+1,Vn)
||<---------X--X--X------>|->|Accepted(N+1,Vn)
|<---------------------------------X--XResponse
|||||||
2.3.4两个Proposer进入无限循环竞争
该实例简单地说明了在算法运行过程中一个Proposer发生故障后两个Proposer进入无限循环竞争的实例。
由于该实例由于出现了最坏的情况,算法将永远都不可能完成。
不过该实例的出现的可能性非常小,因为该实例出现无限循环竞争的条件是:
至少存在两个Proposer的Accept!
信息和Prepare信息恰好是按照某种固定顺序相继发送,并且这种顺序永远不能被打乱。
从概率上讲,这在异步通信的环境中是不可能的。
另外,在后面的优化中已经比较详细的介绍了降低出现无限循环竞争情况可能性的优化手段,见3..3。
ClientProposerAcceptorLearner
|||||||
X----->||||||Request
|X------------>|->|->|||Prepare(N)
||<------------X--X--X||Promise(N,{})
|!
|||||!
!
LEADERFAILS
|||||||!
!
NEWLEADER(knowsN)
|X--------->|->|->|||Prepare(N+1)
||<---------X--X--X||Promise(N+1,{})
||||||||!
!
OLDLEADERrecovers
||||||||!
!
OLDLEADERtriesN+1,denied
|X------------>|->|->|||Prepare(N+1)
||<------------X--X--X||Nack(N+1)
||||||||!
!
OLDLEADERtriesN+2
|X------------>|->|->|||Prepare(N+2)
||<------------X--X--X||Promise(N+2,{})
||||||||!
!
NEWLEADERproposes,denied
||X--------->|->|->|||Accept!
(N+1,Vn)
|||<---------X--X--X||Nack(N+2)
||||||||!
!
NEWLEADERtriesN+3
||X--------->|->|->|||Prepare(N+3)
|||<---------X--X--X||Promise(N+3,{})
||||||||!
!
OLDLEADERproposes,denied
|X------------>|->|->|||Accept!
(N+2,Vn)
||<------------X--X--X||Nack(N+3)
||||||||...andsoon...
三算法的思考及优化
3.1体系结构优化
由于分布式系统的体系结构设计引入了一致性问题,并且需要Paxos算法去解决,那么通过对Paxos算法代价的计算可以反过来思考对体系结构的优化。
假设现有函数f(X)用来评价体系结构X的可靠性,函数g(X)来评价体系结构X所带来一致性问题,即X上运行Paxos算法所需要的代价,那么可以定义“增加一台客户端所增加可靠性”函数
:
其中n为X中结点数量(对应于ATM系统中S的数量)。
同样也可以定义“增加一台客户端所增加一致性问题”函数
:
那么对
与
进行比较分析后可以设计出最适合应用场景的体系结构。
这只是个人非常粗浅的想法(见笑)。
3.2Reconfiguration优化
在介绍Paxos算法中提到简单的Paxos算法的容错能力是2F+1。
即在总共有2F+1个服务端的情况,只要不超过F个服务端同时出现损毁,采用Paxos算法的分布式系统都能正常运行。
这种2F+1特性是由算法的核心思路Majority投票机制所决定的,我们目前还没有想到提升允许同时损毁的服务端个数,但是可以通过引入合适的Reconfiguration[5]技术降低整个系统处于无法运行的概率。
因为通过Reconfiguration可以减少F个服务端同时损毁的概率。
而事实上Paxos算法的几个改进版本已经采用了这个方法,例如CheapMulti-Paxos[1]。
3.3算法细节优化
可以对算法中提到的Acceptor,Proposer,Learner三个角色的行为进行优化:
1.Proposer:
在一个Proposer在被告之它的proposal编号不是当前最大的时候,它的行为是立即创建另一个具有更大编号的proposal,并发给所有的Acceptors。
这里可以引入一个随机等待机制,即在一个Proposer的proposal被否决后,它随机等待一定时间然后再创建一个具更大编号的proposal进行竞争。
经过实验证明[5],引入随机等待机制以一定概率降低了出现循环竞争的可能性。
我觉得预先设置一组具有一定规律的数字为等待时间,譬如T={2n,2n+1,2n+2…}会比随机效果更好(这个问题下次再给结论了)。
2.Acceptor:
在一个Acceptor收到更大编号的proposal时,可以向它已经promise过的Proposer提早发出Nack信息,这样会降低通信负载。
因为如果对方在发出Accept信息之前收到Nack信息,那么可以减少一次一定会被拒绝的Accept信息的发送;即便对方已经发送了Accept信息,那么也不会增加任何不必要的通信,因为改Nack信息可以被认为是回复Accept的信息(异步的)。
3.Learner:
在一个proposal被通过后,Learner可以通过创建Proposer角色并向Acceptors发送proposal信息来获得当前最