一个简单直接的Learner的实现是,获取所有Acceptor接受过的提议,然后看哪个提议被多数的Acceptor接受,那么该提议对应的值就是被选定的。
另外,也可以把Learner看作一个Proposer,根据协议流程,发起一个正常的proposal。
Phase 1的Acceptor的响应有两层含义:
如果过去已经达成共识,那么Proposer提出的proposal的value一定是那个共识value,称这个行为是修复。
如果过去没有任何写入,那么Proposer提出的proposal的value可以由Proposer自行决定,称这个行为为写入。
如果过去虽然对acceptors有写入,但是没有达成共识,那么Proposer的行为取决于Proposer收到了哪些acceptors的响应。比如下面情况,proposer1只写入acceptor1后就退出,共识虽然没有达成,proposer2再次运行,联系到了acceptor1,也只能接着写入没有达成共识的v。 这种现象在raft中也有,切主时,新主上如果有些log没有提交,也会继续把这些log提交(会随着新主的日志提交)。
对于Paxos来说,如果想写入新的值,只能新开一个Paxos run。
原子性
并发安全
响应之前先写stable storage
在Basic Paxos协议中,对于决议(value)的读取也是需要联系大多数Acceptor的。
acceptor需要能接受多个proposal。(否则如果第一轮没有达成共识,就再也达不成了)
多个proposal需要被区分开。
有了 total ordered之后,acceptor可以对proposal做排序,也就能知道哪些是“过去的”proposal。
一个简单的Proposal Number定义(来自braft):
那么不同的proposal需要有全局唯一的编号吗?不需要。
假设两个不同的proposer同时提出了两个具有相同编号的proposal,也只有一个proposal能获得大多数Acceptors的promise。因为Acceptor对于同一个编号只会回复一次promise,先到先得。
就像Raft的leader election,可以有两个candidate发起相同term的RequestVote,但是follower对同一个term只会投票一次,确保了一个term只有一个leader。
所以Acceptor需要持久化“它已经回复的所有proposal的最大编号”,Raft的follower需要持久化“它投过的最大term”。
所以,proposer在生成proposal number的时候,不需要保证全局唯一,只要能保证已知唯一就好了。
在fast-paxos论文中,proposal number有了一个更正式的名字——round(简称做rnd),相同的proposal number属于同一个round。
至少需要两轮交互——学习和写入。
所以“为什么需要两轮多数派交互?”这个问题,可以改成“为什么在写入前需要学习?”。
如果没有了学习那一轮,那么无法保证多个proposal有相同的value。比如proposal1已经被accepted了,后来的proposal2也需要有相同的value。
这也是论文中“Learning about proposals already accepted is easy enough; predicting future acceptances is hard.”的意思,因为无法做到预测未来,所以需要在写入前了解过去有哪个value被chosen,来保证多个proposal有相同的value。
如果Phase 1中大多数Acceptors返回的value都是null,那么proposer可以自主选择value,就可以进入fast-paxos。
因为phase1 也仅要求请求发送给多数派acceptor,所以一旦proposer收到了value,就意味着这个value可能已经达成共识——返回value的这个acceptor和剩余的少数派组成多数派。那么,为了安全,proposer就不能自己想写入什么就写入什么了。
比如有三个acceptor——a,b和c,proposer1仅写入a之后就退出了。proposer2的prepare 请求发送给了a和b,proposer2收到a的value之后,判断这个value可能已经被a和c接受而达成共识,所以proposer1后面提出的proposal是proposer1写入的value。
如果phase1的prepare请求发送给足够多的节点(至少是majority,最多是全部节点),了解到目前没有值达成共识,那么proposer也可以提出具有任意值的proposal。
Classic-Paxos使用若干round(至少一round)来对一个value达成共识。每round由两个阶段组成:Phase 1和Phase 2。
每个Phase都请求大多数(majority)Acceptor。所以不同round的Phase 1和Phase 2是可以互相感知的,因为两个majority一定有交集,也就保证已经达成共识的value在后续round中依然是共识。
quorum选择majority的原因是:
如果每轮round使用不同的quorum会怎么样呢?这里我们把round i的quorum称作i-quorum。
Fast-Paxos中有两种round——fast round和classic round。fast round和classic round只有Phase 2是不同的,对于fast round,
所以对于fast round来说,不同的acceptors可能接受不同的value,打破了classic paxos中的约束——只有一个proposer会获得这个 round的写入权。(但还是会保留一个acceptor在一个round只会被写一次(或者叫vote for)的原则)
这个约束的打破是很严重的,需要重新思考整个算法的安全性。
此时有两种quorum:fast round quorum和classic round quorum。
fast-paxos论文经过推导,得到了保证safety的Quorum Requirement:
条件1是比较明显的,从classic paxos一脉相承,是一个最基础的保证。
条件2是怎么意思呢?是为了保证在fast round达成共识的value,一定能被classic round选出来。假设在fast round有两个proposer向acceptors写入value,最终有一个proposer达到了fast round quorum。
那么两个quorum该如何取值呢?假设acceptor数量固定为N,根据Quorum Requirement写入几个不等式:
简化得到:
2*f-quorum + c-quorum > 2N
如果希望f-quorum最小(fast round 的fault-tolerance更好),那么f-quorum = c-quorum = N*2/3+1
。也就是说,f-quorum最小是N*2/3+1
,这种情况下,c-quorum最小是N*2/3+1
。 如果希望c-quorum最小(classic round 的fault-tolerance更好),那么c-quorum = N/2+1, f-quorum = N*3/4+1
。也就是说,c-quorum最小是N/2+1
,这种情况下,f-quorum最小是N*3/4+1
。
classic paxos需要6个或者7个消息延迟:client -> proposer -> Phase 1 && Phase 2 -> acceptor -> learner -> client.
fast paxos正常情况下需要四个消息延迟:client -> acceptor -> coordinator -> learner -> client.