前置说明:
不论在哪个阶段,客户端在没有成功执行完命令时,“再次尝试”都是进入阶段1而非初始化阶段。
逻辑伪码如下:
---------------------------------------------------------------------------------------------------------------------------------
客户端(提案者) 服务器(接收者)
初始化---------------------------------------------------------------------------
c #等待执行的命令 T_max = 0 #当前已发布的最大票号
t = 0 #当前尝试的票号
C = _ #当前存储的命令
T_store = 0 #用来存储命令C的票
阶段1 ---------------------------------------------------------------------------
1. t = t + 1
2. 向所有服务器发消息,请求得到
编号为t的票
3. if t > T_max then
4. T_max = t
5. 回复:ok(T_store, C)
6. end
阶段2 ---------------------------------------------------------------------------
7. if 过半数服务器回复ok then
8. 选择T_store值最大的(T_store, C)
9. if T_store > 0 then
10. c = C
11. end if
12. 向这些回复了ok的服务器发送消息:
propose(t, c)
13. end if
14. if t == T_max then
15. C = c
16. T_store = t
17. 回复:success
18. end if
阶段3 ---------------------------------------------------------------------------
19. if 过半数服务器回复success then
20. 向每个服务器发送消息:execute(c)
21. end if
---------------------------------------------------------------------------------------------------------------------------------
由上逻辑可知,申请的票号递增,只要申请成功一张票,则该票号唯一。这主要通过:
3. if t > T_max then
7. if 过半数服务器回复ok then
这两个判断来保证。
不可能有两个相同的编号同时获得超过半数的ok回复,所以申请成功的票号一定是唯一的,且后续申请成功的票号是一直增加的。
如果有超过半数的服务器存储了某个提案,则称该提案被选中;此后所有客户端的提案将与该提案相同。
有被选中的提案,则说明在某个时刻存在(T_store, C)存储在过半的服务器中,且此时在这些服务器中,满足T_store== T_max(阶段2,服务器逻辑);那么当再次有一个票号申请成功时,此时的票号t’必然大于前面的T_max(因为已经有超过一半的服务器保存T_max编号了,小于该编号的票号不可能在阶段1的判断中获取超过半数的ok回复)。由阶段2客户端判断 T_store > 0 的逻辑分支可知,最后t’提案的内容和当前已经被选中的内容是一致的。
为什么能保证这一点呢?
因为T_store是在提案被接受的时候设置的(阶段2服务器逻辑),而且此时T_store== T_max将成立。已经有过半的服务器,当前票号的最大值是T_store了。其他服务器保存的最大票号可能大于这个值,也可能小于这个值,但是不论是那种情况,他们的数量加起来都是小于半数的,也就是说不可能有获得客户端提案的机会(客户端提案时,只会向回复了ok的服务器发送提案,且回复ok的服务器数量要超过半数)。小于该值的服务器虽然能更早的满足3. if t > T_max then条件,然而回复ok的数量不足;而大于该值的服务器甚至没有机会回复ok,客户端已经向其他服务器发出提案了,因为有过半的票号小于他们的服务器会回复ok,此时已经满足提案条件了。所以最后通过8.选择T_store值最大的(T_store, C)选出来的提案依然是原来选中的提案。
此外也可以用反证法:
假设存在一个提案t_在提案t已经选中的情况下接着被选中,且他的提案内容c_不同于之前被选中的提案内容c。则,由8.选择T_store值最大的(T_store, C)的逻辑可知,有必然存在一个票号t’; t’ > t,且c’ !=c; 因为被选中的提案t_是之后选中的,所以有t_ > t’; 不能满足假设(永远需要一个t’,而这个t’是不存在的)。
这样就可以保证,只要服务器还能正常提供服务,那么最后执行的都是同一个提案内容。