概述
这篇blog主要探讨常见的分布式一致性算法:
- paxos
- zab (zookeeper atomic broadcast)
- raft
- gossip
重点在于对比这些算法的相同点和差异。思考算法的设计和取舍.
paxos
paxos是最早提出的分布式一致性算法.
见http://lamport.azurewebsites....
重要概念
Roles
实践中, 节点往往同时承载proposer/acceptors/learner的功能.
proposer
提案人,A proposer sends a proposed value to a set of acceptors. 可以理解为可以执行写入的master节点。注意paxos协议并没有限制Proposer的数量. 在实践中为了避免中心节点生成全序proposal number的单点故障, 使用了选主协议. 参考下面的leader.
leader
见论文的第三节:Implementing a State Machine
必须有一个中心节点生成全序的 proposal number. 被选为主节点的proposer才可发起proposal.
In normal operation, a single server is elected to be the leader, which acts as the distinguished proposer (the only one that tries to issue proposals) in all instances of the consensus algorithm.
acceptors
投票人,An acceptor may accept the proposed value. 即构成majority或quorum的节点。
learner
被动接受选举结果的角色。可以理解为只读的从库。
client
客户端. 发起请求和接收返回.
work flow
Basic Paxos without failures
下图来自: https://en.wikipedia.org/wiki...
Client Proposer Acceptor Learner
| | | | | | |
X-------->| | | | | | Request
| X--------->|->|->| | | Prepare(1)
| |<---------X--X--X | | Promise(1,{Va,Vb,Vc})
| X--------->|->|->| | | Accept!(1,V)
| |<---------X--X--X------>|->| Accepted(1,V)
|<---------------------------------X--X Response
| | | | | | |
算法核心
- acceptor不会接受proposal number更小的prepare, 会返回accept过的highest-numbered.
- 被提交的value只有在阶段2才会被选择.
因为所有最高的proposal number, 都是经过了majority的accept的, 所以绝对不会发生如下情况:
更低版本议题覆盖更高版本议题的情况, 如,
(proposal number 1, V1) 覆盖 (proposal number 2, V2).
为什么paxos要有prepare阶段
paxos允许同时存在多个proposer, prepare阶段可以确保不覆盖higher proposal number.
paxos算法是线性的吗?
paxos算法没有明确要求proposal number的生成是严格有序的. 也没有规定learner严格有序地按proposal number读取.
在严格有序的情况下, paxos才是线性的.
zab
zab是zookeeper实现的分布式一致性算法.
见: http://www.cs.cornell.edu/cou...
why not paxos
线性一致性
It is important in our setting that we enable multiple outstanding ZooKeeper operations and that a prefix of operations submitted concurrently by a ZooKeeper client are committed according to FIFO order.
paxos没有直接支持线性一致性. 如果多个primaries同时执行多个事务, 事务提交的顺序是无法保证的, 这个将导致最终的结果不一致.
下图说明了这种不一致, 27A -> 28B 变成了 27C -> 28B. 如果希望避免这种不一致, 只能限制一次只能执行一个proposal. 显然这样会导致系统吞吐低下. 若使用合并transactions的方式优化的话, 又会导致系统延迟提高. 合并transactions的size也很难选择.
快速恢复
在多个primaries同时执行多个事务的情况下, paxos不同的processes在同一个sequence number下可能有不同的值. 新的master必须对所有未learned的value走一遍paxos phases 1, 即通过majories获得正确的值.
重要概念
Roles
leader
zab 是通过单主节点, 即leader广播来实现线性, 即全序(论文中的PO, primary order)的广播.
follower
选主, abdeliver 主节点的广播.
epoch
新纪元, 每次选主成功, 都会有有 epoch_new > epoch_current.
proposes 和 commit
proposes和commit对应paxos的Promise/Accept.
proposes和commit 都包含 (e, (v, z)):
e: epoch 对应master的任期number
v: commit transaction value, 对应实际的值
z: commit transaction identifier (zxid), 对应提交版本号.
正是commit的全序线性化保证, 保证了各个节点的value变化具有线性一致性.
work flow
选主(discovery 和 synchronization)
如何确认prospective leader?
尚未在了论文中找到prospective leader的精确描述,有模糊描述在 V. Z AB IN DETAIL 如下:
When a process starts, it enters the ELECTION state. While in this state the process tries to elect a new leader or become a leader. If the process finds an elected leader, it moves to the FOLLOWING state and begins to follow the leader.
为什么选主需要discovery 和 synchronization 分两个阶段?
- discovery 阶段通过多数follower的cepoch确保leader有最新的commit和history(f.p和h.f).
- synchronization 阶段确保leader将最新的commit/history同步到了多数节点.
broadcast
多数成员ack后,即可安全commit和返回client. 参照discovery阶段,因为会选择所有节点中最新的history,故,只需要多数节点ack,即可返回写入成功.
返回失败不一定意味着写入不成功,可能是
- 成功,但返回未被收到。
- 失败,leader 广播之前崩溃。
- 失败,leader 在多数成员ack前崩溃,之后的选主未选中ack的follower的history。
写入失败不一定失败,写入成功可以确保成功。
raft
raft 是目前最易于实现,最易懂的分布式一致性算法,和zab一样,raft必须先选出主节点。
why not paxos
- 难懂
- 不容易构建具有实际意义的实现.
paxos指single-decree paxos, 在工程中价值不大, 而multi-paxos的诸多细节并未给出.
和 zab 的异同
异
- 选主逻辑不同, raft通过随机的选主超时触发选主. 只有一个阶段
- broadcast逻辑不同, 见下面的log.
从论文出发, raft考虑到了更多工程上的细节, 如日志压缩, 和改变集群节点都是zab/paxos未提及的.
同
- single master and multi followers
- 任期概念 raft (term) zab (epoch)
核心抽象
Roles
leader
同zab, raft有一个strong leader. 必须先选出主节点, 才可提供写入服务.
follower
同zab, follower可投票, 写入主节点广播的log(by AppendEntries rpc)
term
同zab, 每选出一个leader, 都对应一个全序的term.
log
日志, 即zab的propose和commit.
AppendEntries设计得极为精巧. 同时可以作为
- heartbeat
- propose
- commit (leaderCommit字段可以commit所有小于等于leaderCommit的log)
work flow
Leader election
https://raft.github.io/
有详细的选主过程. 可以随意操作节点, 观察各个情况下的边界. 参照paxos的文本化流程, 下面是一个正常情况下的选主:
Node1 Node2 Node3
| | |
timeout | |
X----RequestVote---->| |
X--------------------|-----RequestVote---->|
|<-------vote--------X |
|<-------vote--------|-------------- ------X
become leader | |
Log replication
Figure 6: Logs are composed of entries, which are numbered
sequentially. Each entry contains the term in which it was
created (the number in each box) and a command for the state
machine. Anentryisconsidered committed if itissafeforthat
entry to be applied to state machines.
仔细思考AppendEntries RPC的Receiver implementation:
- 不接受小于当前term的log. 广播AppendEntries的节点只可能有两种, 一, 误以为自己仍是leader的老leader. 二, 当前leader, 一个分区中的Node可能不停提高term, 但它获得多数节点投票成为leader之前, 不会发出广播. 这一点保证了, 只接受当前任期term的log.
- 不接受prevLogIndex和prevLogTerm不匹配的log. 若prevLogIndex和prevLogTerm不匹配. 说明prevLogIndex之上有多个leader广播, leader的log和follower的log不一致. leader通过减少nextIndex重试来修复和follower的不一致. 直到所有未commit的log都达成一致.
- 如果当前entry的值不一致, 使用leader的. leader的log是可信的.
- 如果有新的entries, 添加.
- 如果leaderCommit > commitIndex, 设置 commitIndex = min(leaderCommit, index of last new entry)
当多数节点都返回对应success时, AppendEntries的entries即可认为是写入, 可以返回给client. 这是通过在选主时施加的额外限制来保证的, 具有更新(比较log index和term). log的节点才可赢得选举. 那么, 只要一个log在多数节点写入了(就算没有commit), 选主时, 一定会选择一个具有该entry的节点:
- 不切换leader的情况下, 不会写入更新的log
- 切换leader需要多数节点的同意, 一定会选择一个具有该log的节点. 因为该log是最新的.
Cluster membership changes
Figure11: Timeline for a configuration change. Dashed lines
show configuration entries that have been created but not
committed,and solidlinesshow thelatestcommittedconfigu-
ration entry. The leader first creates theC old,new configuration
entry in its log and commits it to C old,new (a majority of C old
and a majority of C new ). Then it creates the C new entry and
commits it to a majority of C new . There is no point in time in
whichC old and C new can both make decisions independently.
关键在于能够做决定的majority的切换.
gossip
gossip一致性算法和paxos/zab/raft 有较大差异, 不存在主节点, 没有线性化保证. 通过无限重试的广播, 确保变化在一段时间后, 能够同步到所有节点.
work flow
实现算法的关键在与, 如何将所有节点的gossip广播合并成一个状态, 即, 节点a可以声明节点a上有k1=v1, 节点b声明节点b上有k1=v2. 那么合并的状态是 a上有k1=v1, b上有k1=v2. 只要合并的算法是一致的, 如, 可以选择并存, 优先a或优先b, 那么收到对应广播的节点, 状态可保持一致.
因为没有线性化保证, 广播的内容不能用差量, 而应该用全量.
Active thread (peer P): Passive thread (peer Q):
(1) selectPeer(&Q); (1)
(2) selectToSend(&bufs); (2)
(3) sendTo(Q, bufs); -----> (3) receiveFromAny(&P, &bufr);
(4) (4) selectToSend(&bufs);
(5) receiveFrom(Q, &bufr); <----- (5) sendTo(P, bufs);
(6) selectToKeep(cache, bufr); (6) selectToKeep(cache, bufr);
(7) processData(cache); (7) processData(cache)
Figure 1: The general organization of a gossiping protocol.
分布式共识的关键
不管是paxos, zab, raft,实现一致性的核心原理都类似:
- 多数节点同意后才可提交.
- 同意后不可撤回.
- 这个过程是幂等的.
不管是节点崩溃后继续提交,还是由其它节点继续提交,会在多数节点达成共识.
参考
https://en.wikipedia.org/wiki...
http://lamport.azurewebsites....
https://stackoverflow.com/que...
http://www.cs.cornell.edu/cou...
https://raft.github.io/
https://web.stanford.edu/~ous...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。