多个参与者针对某一件事达成完全一致:一件事,一个结论。 已达成一致的结论,不可推翻。
Proposal: 提议, 记作 P Proposal Value : 提议的值,记作 V Proposal Number: 提议编号
在整个系统中,一共有三种角色:
Paxos 协议中 Proposer 有两个行为:
Acceptor则根据协议规则,对Proposer的请求作出承诺(Promised)和接受提议(Accepted);
最后Learner可以根据Acceptor的状态,学习最终被确定的值
方便讨论,记:
{n,v}
为提议编号为n,提议的值为v的提议(m,{n,v})
为承诺了 Prepare(m)请求,并接受了提议{n,v}
Proposer选择一个提议编号n,向所有的Acceptor广播Prepare(n)请求。
Acceptor 接收到 Prepare(n)请求,若提议编号n比之前接受的 Prepare 请求都要大,则承诺(promised)将不会接受提议编号比 n 小的提议,并且带上之前 Accept 的提议中编号小于n的最大的提议,否则不予理会。
acceptor 之前没有 accept 过请求, 不用返回接受过的提议:
如果接受过提议,要带上之前Accept的提议中编号小于n的最大的提议:
proposer 得到了多数 Acceptor 的承诺(promised)后,如果没有发现有一个 Acceptor 接受过(accepted) 一个值,那么向所有的Acceptor发起自己的值和提议编号n
如果 Prepare 收到了应答, 应答中携带了 acceptor 之前接受过的提议如 {n1, v1}, {n2, v2}。 Proposer 则根据 n1,n2 的大小关系, 选择较大的哪个提议对应的值, 如 n1>n2, 则选择 v1 作为提议的值, 广播出自己的提议 {n, v1}
Acceptor接收到提议后,如果该提议编号不违反自己做过的承诺,则接受该提议。
当一个提议被多数派接受后,这个提议对应的值被Chosen(选定),一旦有一个值被Chosen,那么只要按照协议的规则继续交互,后续被Chosen的值都是同一个值,也就是这个Chosen值的一致性问题。
paxos 原命题:
如果一个提议{n0,v0}被大多数 Acceptor 接受,那么不存在提议{n1,v1}被大多数 Acceptor 接受,其中n0 < n1,v0 != v1
paxos 原命题加强
如果一个提议{n0,v0}被大多数Acceptor接受,那么不存在 Acceptor 接受提议{n1,v1},其中 n0 < n1,v0 != v1
paxos 进一步加强原命题
如果一个提议{n0,v0}被大多数 Acceptor 接受,那么不存在 Proposer 发出提议{n1,v1},其中n0 < n1,v0 != v1
如果证明”原命题进一步加强”成立, 那么”原命题”显然成立
所以命题成立。
1、为什么要被多数派接受?
因为两个多数派之间必有交集,所以Paxos协议一般是2F+1个Acceptor,然后允许最多F个Acceptor停机,而保证协议依然能够正常进行,最终得到一个确定的值。
2、为什么需要做一个承诺?
可以保证第二阶段A中Proposer的选择不会受到未来变化的干扰。另外,对于一个Acceptor而言,这个承诺决定了它回应提议编号较大的Prepare请求,和接受提议编号较小的Accept请求的先后顺序。
3、为什么第二阶段A要从返回的协议中选择一个编号最大的?
这样选出来的提议编号一定不小于已经被多数派接受的提议编号,进而可以根据假设得到该提议编号对应的值是Chosen的那个值。
basic paxos 的价值在于开拓了分布式共识算法的思路, 一般不会用于实践
Basic Paxos是为了确定一个不变量的取值, 保证一个值只要确定之后就不会在变化, 并且存在活锁、效率低下等问题。 multi paxos 是 paxos 的改进版,保证了节点”平等”, 又在提议节点中实现了主从, 限制了每个节点都有不受控的提议权利。
multi paxos 的核心改进是增加了选主流程, 提议节点通过心跳发现当前网络中无主节点存在,节点会使用 basic paxos 中的prepare、accept 两轮请求发出选主广播, 得到多数派批准便宣布选主成功。 当选主成功后, 在任期内就只有主节点才能提出提议, 这样只需要一轮协商就能对某个值达成一致了
multi paxos 将"分布式系统中如何对某个值达成一致"
这个问题划分成了三个子问题, 当三个子问题被解决时基本等同于达成共识:
Raft 也是一种共识算法, 可以理解为 multi paxos 上发展的一种派生实现(multi paxos 没有给出实现细节)
Raft 解决了 multi paxos 的三个问题, 即 “leader election”、”log replication”、”safety”,一篇以”一种可以让人理解的共识算法”为题的论文提出了 Raft 算法, 成为了 etcd、consul 等重要分布式程序的实现基础
包括 Zookeeper 的 ZAB 算法与 Raft 思路也十分相似
对于一个无限增长的序列a[1, 2, 3…],如果对于任意整数i, a[i]的值满足分布式一致性, 这个系统就满足一致性状态机的要求
基本上所有的真实系统都会有源源不断的操作,这时候单独对某个特定的值达成一致显然是不够的。
为了让真实系统保证所有的副本的一致性,通常会把操作转化为 write-ahead-log(WAL)
。然后让系统中所有副本对 WAL
保持一致,这样每个副本按照顺序执行 WAL 里的操作,就能保证最终的状态是一致的。
所有一致性算法都会涉及到状态机,而状态机保证系统从一个一致的状态开始,以相同的顺序执行一些列指令最终会达到另一个一致的状态
Raft有三种角色:Leader,Candidate,Follower。一个 Server 进程在某一时刻,只能是其中 一种类型,但这不是固定的。不同的时刻,它可能拥有不同的类型。
Message 的 3 种类型:
当选出 leader 后,它会开始接受客户端请求,每个请求会带有一个指令,可以被回放到状态机中。
leader 把指令追加成一个 log entry
,然后通过 AppendEntries RPC
并行的发送给其他的 follower.
当改 log entry 被多数派 follower 复制后(leader 将这种日志视为安全的, committed entry),leader 会把该 log entry 回放到状态机中,然后把结果返回给客户端。
AppendEntries RPC
leader 会将新 log entry termId和LogIndex紧接着上一条 entry。如果 Follower 没有在它的日志中找到相同(LogIndex,TermId),它就会拒绝新的entry)上图一个格子表示一个日志条目;格子中的数字是它的任期, 最上面当leader当选后,follower有可能丢失一些 entry(a,b),也可能多一颗未提交的entry(c,d), 或两种情况都有(e,f) 例如场景f在如下情况下就会发生:如果一台服务器在任期2时是Leader并且向它的日志中添加了一些条目,然后在将它们提交之前就宕机了,之后它很快重启了,成为了任期3的 Leader,又向它的日志中添加了一些条目,然后在任期2和任期3中的条目提交之前它又宕机了,并且几个任期内都一直处于宕机状态
raft 通过follower强制复制leader节日的日志来解决 leader 崩溃后日志不一致的问题(Leader 崩溃后日志 AppendEntries 检查):
优化:
上述一次回退一个log entry的方法效率较低,在发生冲突时,可以让follower把冲突的term的第一个日志的index发回给leader,这样leader就可以一次过滤掉该term的所有log entry。
raft 认为实践场景中这种优化不是必要的, 因为 AppendEntries
一致性检查很少失败并且也不太可能出现大量的日志条目不一致的情况。
选举安全性要求一个任期Term内只能有一个leader,即不能出现脑裂现象,否者raft的日志复制原则很可能出现数据覆盖丢失的问题。Raft算法通过规定若干投票原则来解决这个问题:
Raft规定:只有拥有最新提交日志的follower节点才有资格成为leader节点。 具体做法:candidate竞选投票时会携带最新提交日志,follower会用自己的日志和candidate做比较。
因为日志提交需要超过半数的节点同意,所以针对日志同步落后的follower(还未同步完全部日志,导致落后于其他节点)在竞选leader的时候,肯定拿不到超过半数的票,也只有那些完成同步的才有可能获取超过半数的票成为leader。
日志更新判断方式是比较日志项的term和index:
考虑到当前的日志复制规则
所以,Raft对日志提交有额外安全机制:leader只能提交当前任期Term的日志,旧任期Term(以前的数据)只能通过当前任期Term的数据提交来间接完成提交。简单的说,日志提交有两个条件需要满足: