diff --git a/assets/paxos.svg b/assets/paxos.svg new file mode 100644 index 00000000..55fe2c8b --- /dev/null +++ b/assets/paxos.svg @@ -0,0 +1,4 @@ + + + +
Proposer
Proposer
Acceptor
Acceptor
Acceptor
Acceptor
Prepare(n)
Prepare(n)
Promise(n,val)
Promise(n,val)
Prepare(n)
Prepare(n)
Promise(n,val)
Promise(n,val)
操作请求
操作请求
Actor
Act...
Prepare 阶段
Prepare 阶段
Accept(n,val)
Accept(n,val)
Accepted(n)
Accepted(n)
Accept(n,val)
Accept(n,val)
Accepted(n)
Accepted(n)
Accept 阶段
Accept 阶段
Learner
Learner
形成决议,同步至 Learner
形成决议,同步至 Learner
构造提案 n
构造提案 n
Text is not SVG - cannot display
\ No newline at end of file diff --git a/consensus/Basic-Paxos.md b/consensus/Basic-Paxos.md index 2366d4d4..570814c4 100644 --- a/consensus/Basic-Paxos.md +++ b/consensus/Basic-Paxos.md @@ -2,57 +2,13 @@ 希望你没有对前篇 Paxos 的“复杂”做的铺垫所吓倒,共识问题已经算是一个古老的领域,30 余年间已经有无数简洁直白的视频、论文等资料进行过解读。譬如在网络中流传甚广的 raft 和 paxos 视频讲解[^1],即使没有多少技术背景,也能通俗地理解 Paxos。 -这一节,我们从故事回到算法本身,正式开始学习 Paxos。Paxos 算法包含两个部分: -- 其中一部分是**核心算法**(Basic Paxos); -- 另外一部分是基于核心算法扩展的**完整算法**(Multi Paxos)。 - -在笔者看来 Basic Paxos 是 Multi-Paxos 思想的核心,说直接点 Multi-Paxos 就是多执行几次 Basic Paxos,所以掌握了 Basic Paxos,我们便能更好的理解后面基于 Multi-Paxos 思想的共识算法(譬如 raft 算法)。 - -那么接下来我们就看看 Basic Paxos 是如何解决共识问题的。 - -:::tip 背景设定 - -很久以前,在遥远的爱琴海上,有一座与世隔绝的小岛,叫做 Paxos。。。算了,还是换个更对口味的例子吧。 - -从前有个村,老村长退休了,需要选一个新村长,选取新村长的事件称之为提案(Proposal)。张三、李四都想当村长(张三、李四我们称为提议节点,Proposer),但当村长需要多位村委(决策节点,Acceptor)的投票同意,村委使用少数服从多数投票机制。选举结束之后,要把结果同步给村民(记录节点,Learner)。 -::: - -如果继续把问题讲下去,笔者似乎通篇都要讲张三、李四的段子,我们还是把背景再转换为分布式工程问题吧。 - -假如我们设计一个由三个节点 A、B、C(3 个村委)组成分布式集群,提供只读的 KV 存储服务。既然要创建一个只读服务,必须先要对只读变量赋值,而且后续不能对该变量进行修改(村长选定了,结果就不能再更改)。所以,多个节点中,所有的节点必须要先对只读变量的值(提案)达成共识,然后所有的节点再一起创建这个只读变量。 - -如图 6-3 所示,当有多个客户端(张三、李四,Proposer)访问这个系统,试图创建同一个只读变量(发起一个提案 Proposal,set x=1,提议张三当村长)时,集群中所有的节点(村委)该如何达成共识,实现各个节点中的 x 值的一致呢(所有的民村一致认为张三是村长)? - -:::center - ![](../assets/paxos-consensus.svg)
- 图 6-3 如何实现多个节点 x 值一致性 -::: 实现多个节点 x 值一致的复杂度主要来源于以下两个因素的共同影响: -1. 系统内部各个节点的**通信是不可靠的**,不论是系统中企图设置数据的提议节点,还是决定操作是否批准的决策节点,其发出、收到的信息都可能存在延迟、丢失的情况。 -2. 客户端**写入是并发的**,如果是串行的修改数据,仅单纯使用少数服从多数原则,就足以保证数据被正确读写。而并发访问就变成了“**分布式环境下多个节点并发操作共享数据**”的问题。 - -我们把上面的背景问题总结转化,其实就是下面 2 个核心需求: - -1. **安全性** Safety: - - 一个变量只会被确定一个值(只能一个人当村长); - - 一个变量只有值被确定之后,才能被学习。 -2. **活性** Liveness: - - 提案最终会被接受(一定能选出个村长); - - 一个提案被接受之后,最终会被所有的村民(Learner)学习到; - - 必须在有限时间内做出决议(不能有太多轮投票)。 - -Basic Paxos 问题背景相信已经讲清楚了,那怎么解决? - -简单的方案如同图 6-4 所示,多个提议节点、单个决策节点,决策节点接受第一个发给它的值,作为被选中的值。但如果决策节点故障,整个系统就会不可用。 - -:::center - ![](../assets/quorum-base.png)
- 图 6-4 只有一个决策节点会有单点故障 -::: +- 分布式系统是不可靠的:节点可能宕机,节点间通信可能延迟、丢失、乱序; +- 操作是并发的:如果是串行操作,单纯使用少数服从多数原则,就足以保证正确读写数据。但并发操作就变成了“**分布式环境下多个节点并发操作共享数据**”的问题。 -为了克服单点故障问题,借鉴多数派的机制,思路是写入一半以上的节点,如果集群中有 N 个节点,客户端需要写入 W >= N/2 + 1 个节点。使用多数派的机制后最多可容忍 (N-1)/2 个节点故障。 +第一问题相对好解决。一个节点不可靠无所谓,只要一群节点中多个节点可靠就行。按照少数服从多数原则,也就是 Quorum 机制。 :::tip Quorum @@ -113,34 +69,33 @@ Once a value has been chosen, future proposals must propose the same value. ## 1. Basic Paxos 算法描述 -Basic Paxos 对上述问题的解决方案是定义一个 Proposal Number 标识唯一的提案。 -一个简单的 Proposal Number 的定义为:``,seq_id 可以是一个自增的 ID,同时为了避免崩溃重启,必须能在本地持久化存储,最后再拼接上 server_id,确保是分布式系统中唯一 ID。 +“准备阶段”(Prepare)相当于抢占锁的过程。某个提案节点准备发起提案,必须先向所有的决策节点广播一个许可申请(称为 Prepare 请求)。Prepare 请求附带一个全局唯一且单调递增的数字 n 作为提案 ID。决策节点收到请求后,将给予提案节点两个承诺与一个应答: +- [承诺1]:不再接受提案 ID <= n 的 Prepare 请求; +- [承诺2]:不再接受提案 ID < n 的 Accept 请求; +- [应答]:不违背历史承诺的前提下,回复形成决议最大的那个提案所设定的值和提案 ID: + - 如果这个值没有被任何提案设定过,则返回空值 + - 如果违反历史承诺,即收到的提案 ID 并不是决策节点收到的最大的 ID,对 Prepare 请求不予理会。 -当决策节点收到这个提案后,将会给两个承诺一个应答。 +提案节点收到多数决策节点的应答(称为 Promise 应答)后,就可以开始“批准”(Accept)阶段了。 -- **两个承诺**: - - 承诺不会再接受提案 ID 小于或者等于 n 的 Prepare 请求; - - 承诺不会再接受提案小于 n 的 Accept 请求。 -- **一个应答**: - - 不违背以前作出的承诺下,回复已经 Accept 过的提案中提案 ID 最大的那个提案的值和提案 ID,没有则返回空值。 +提议者向所有接受者广播 accept(n, v) 请求,请求接受提案编号为 n 和提案值为 v。 +- 如果提案编号 n >= 该接受者之前所承诺的编号,它将接受该提案,并返回 accepted(n, v) 消息。 +- 如果提案编号 n < 该接受者所承诺的编号,它将拒绝该提案。 -再具体一点描述 Basic Paxos 算法: -- 首先是**准备阶段**,选择一个提交号 n,提交 Prepare(n),接受者需要返回自己接受的值和已经接受的提交号。当从大多数收到回复以后就可以做判断了,如果有返回接受值,选择提交号最大的值进行下一阶段(这个行为对应的是发现有值可能被接受了,尝试服从或者学习这个接受),不然就可以用自己的值进行下一阶段。 -- 下一阶段就是**接受阶段** accept(value,n),如果接受者发现自己目前收到的 n,没有比 accpet 给的 n 大,就接受这个值,并且更新自己的 n,否则就拒绝(这里就保证提交者能够发现自己变老了或者被拒绝了)。如果接受者发现提交号大于自己当前的最大提交号,就接受这个值,不然就拒绝。当提交者从大多数人那里接受到返回以后发现有拒绝的情况,就进行重试拿一个新的 n 开始,否则这个值就被接受了。 +如果提案节点收到了大多数决策节点的应答(accepted),协商结束,共识协议形成,然后将形成的决议发送给所有记录节点进行学习。 -总结 Basic Paxos 中的值就是设置一次,不存在再设置一次的情况,整个流程如下图 6-8 所示。 :::center - ![](../assets/basic-paxos.png)
+ ![](../assets/paxos.svg)
图 6-8 Basic Paxos 流程 ::: ## 2. Basic Paxos 验证 -通常说 Paxos 算法是复杂算法难以理解是指其推导过程复杂。理论证明一个 Paxos 的实现,比实现这个 Paxos 还要难。我们假设下面几种情况看看能不能解决前面的问题。 +那这样的二阶段提交算法是否可以解决前面提到的问题?我们通过具体的例子来分析。 -假设一个分布式系统中有五个节点,分别是 S~1~、S~2~、S~3~、S~4~、S~5~,这 5 个节点同时扮演着提案节点和决策节点的角色。此时,有两个并发请求希望将同一个值分别设定为 X(由 S~3~ 作为提案节点提出)和 Y(由 S~5~ 作为提案节点提出),以 P 代表准备阶段,以 A 代表批准阶段,这时会发生以下几种情况。 +假设共有五个节点 S~1~、S~2~、S~3~、S~4~、S~5~,这 5 个节点同时扮演着提案节点和决策节点的角色。此时,有两个并发请求希望将同一个值分别设定为 X(由 S~3~ 作为提案节点提出)和 Y(由 S~5~ 作为提案节点提出),图中的 P 代表准备阶段,A 代表批准阶段,这时会发生以下几种情况。 **情况一:提案已 Chosen** 譬如,S~1~ 选定的提案 ID 是 3.1(全局唯一 ID 加上节点编号),先取得了多数派决策节点的 Promise 和 Accepted 应答,此时 S~5~ 选定提案 ID 是 4.5,发起 Prepare 请求,收到的多数派应答中至少会包含 1 个此前应答过 S~1~ 的决策节点,假设是 S~3~,那么 S~3~ 提供的 Promise 中必将包含 S~1~ 已设定好的值 X,S~5~ 就必须无条件地用 X 代替 Y 作为自己提案的值,由此整个系统对“取值为 X”这个事实达成一致。整个流程如下图所示。