Skip to content

Commit

Permalink
+ paxos workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
Zeb-D committed Oct 9, 2024
1 parent be35dcb commit 0985a5e
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 14 deletions.
151 changes: 137 additions & 14 deletions distributtion/什么是PAXOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@

例如网络问题、进程挂掉、机器挂掉、进程很慢没有响应、进程重启等情况,这就会造成消息重复、一段时间内部不可达等现象。Paxos 协议是帮助我们解决分布式系统中一致性问题的一个方案。



### Paxos 算法的诞生之路

Paxos 算法,是由Leslie Lamport(就是大名鼎鼎的LaTeX中的“La”)提出的一种基于消息传递的协商共识算法。

现在,Paxos 算法已经成了分布式系统最重要的理论基础,几乎就是“共识”这两字的代名词了。这个极高的评价来自提出 Raft 算法的论文“In Search of an Understandable Consensus Algorithm”,更是显得分量十足。

关于 Paxos 在分布式共识算法中的地位,还有这么一种说法:

> There is only one consensus protocol, and that's “Paxos” — all other approaches are just broken versions of Paxos.世界上只有一种共识协议,就是 Paxos,其他所有共识算法都是 Paxos 的退化版本。—— Mike Burrows,Inventor of Google Chubby
虽然我认为“世界上只有 Paxos 一种分布式共识算法”的说法有些夸张,但是如果没有 Paxos,那后续的 Raft、ZAB 等算法,ZooKeeper、etcd 这些分布式协调框架,Hadoop、Consul 这些在此基础上的各类分布式应用,都很可能会延后好几年面世。

但 Paxos 算法从被第一次提出,到成为分布式系统最重要的理论基础,可谓是经历了一番波折。我们来具体看看。



### 拜占庭将军问题

使用Paxos 协议有一个前提,那就是不存在拜占庭将军问题。
Expand All @@ -28,40 +46,145 @@

拜占庭将军问题就此形成。也就是说,拜占庭将军问题是一个没有办法保证可信的通信环境的问题,Paxos 的前提是有一个可信的通信环境,也就是说信息都是准确的,没有被篡改。



### Paxos 算法 角色

Paxos 算法的提出过程是,虚拟了一个叫做Paxos 的希腊城邦,并通过议会以决议的方式介绍Paxos 算法。
Paxos 算法将分布式系统中的节点分为提案节点、决策节点和记录节点三类。

提案节点:称为 Proposer,提出对某个值进行设置操作的节点,设置值这个行为就是提案(Proposal)。

值一旦设置成功,就是不会丢失也不可变的。需要注意的是,Paxos 是典型的基于操作转移模型而非状态转移模型来设计的算法,所以这里的“设置值”不要类比成程序中变量的赋值操作,而应该类比成日志记录操作。

因此,我在后面介绍 Raft 算法时,就索性直接把“提案”叫做“日志”了。

首先把议员的角色分为了Proposers 、Acceptors 和Learners ,议员可以身兼数职,介绍如下。

Proposers ,提出议案者,就是提出议案的角色。

Acceptors ,收到议案后进行判断的角色。Acceptors 收到议案后要选择是否接受(Accept )议案,若议案获得多数Acceptors 的接受,则该议案被批准(Chosen )。
决策节点:称为 Acceptor,是应答提案的节点,决定该提案是否可被投票、是否可被接受。提案一旦得到过半数决策节点的接受,就意味着这个提案被批准(Accept)。

提案被批准,就意味着该值不能再被更改,也不会丢失,且最终所有节点都会接受它。



记录节点:被称为 Learner,不参与提案,也不参与决策,只是单纯地从提案、决策节点中学习已经达成共识的提案。比如,少数派节点从网络分区中恢复时,将会进入这种状态。

在使用 Paxos 算法的分布式系统里,所有的节点都是平等的,它们都可以承担以上某一种或者多种角色。


Learners ,只能“学习”被批准的议案,相当于对通过的议案进行观察的角色。在Paxos 协议中,有两个名词介绍如下。Proposal ,议案,由Proposers 提出,被Acceptors 批准或否决。

Value ,决议,议案的内容,每个议案都是由一个{ 编号,决议} 对组成。在角色划分后,可以更精确地定义问题,如下所述:决议(Value )只有在被Proposers 提出后才能被批准(未经批准的决议称为“议案(Proposal )”)。在Paxos 算法的执行实例中,一次只能批准(Chosen )一个Value 。Learners 只能获得被批准(Chosen )的Value 。

### 协议过程

对议员来说,每个议员有一个结实耐用的本子和擦不掉的墨水来记录议案,议员会把表决信息记在本子的背面,本子上的议案永远不会改变,但是背面的信息可能会被划掉。每个议员必须(也只需要)在本子背面记录如下信息:LastTried[p] ,由议员p 试图发起的最后一个议案的编号,如果议员p 没有发起过议案,则记录为负无穷大。PreviousVote[p] ,由议员p 投票的所有表决中,编号最大的表决对应的投票,如果没有投过票则记录为负无穷大。NextBallot[p] ,由议员p 发出的所有LastVote (b,v )消息中,表决编号b 的最大值。

基本协议的完整过程如下。
不过,为了便于确保有明确的多数派,决策节点的数量应该被设定为奇数个,且在系统初始化时,网络中每个节点都知道整个网络所有决策节点的数量、地址等信息。另外,在分布式环境下,如果说各个节点“就某个值(提案)达成一致”,代表的意思就是“不存在某个时刻有一个值为 A,另一个时刻这个值又为 B 的情景”。



而如果要解决这个问题的复杂度,主要会受到下面两个因素的共同影响:

- 系统内部各个节点间的通讯是不可靠的。不论对于系统中企图设置数据的提案节点,抑或决定是否批准设置操作的决策节点来说,它们发出、收到的信息可能延迟送达、也可能会丢失,但不去考虑消息有传递错误的情况。

- 系统外部各个用户访问是可并发的。如果系统只会有一个用户,或者每次只对系统进行串行访问,那单纯地应用 Quorum 机制,少数节点服从多数节点,就已经足以保证值被正确地读写了。

第一点“系统内部各个节点间的通讯是不可靠的”,是网络通讯中客观存在的现象,也是所有共识算法都要重点解决的问题。

所以我们重点看下第二点“系统外部各个用户访问是可并发的”,即“分布式环境下并发操作的共享数据”问题。

> 为了方便理解,我们可以先不考虑是不是在分布式的环境下,只考虑并发操作。
>
> 假设有一个变量 i 当前在系统中存储的数值为 2,同时有外部请求 A、B 分别对系统发送操作指令,“把 i 的值加 1”和“把 i 的值乘 3”。
>
> 如果不加任何并发控制的话,将可能得到“(2+1)×3=9”和“2×3+1=7”这两种结果。
>
> 因此,对同一个变量的并发修改,必须先加锁后操作,不能让 A、B 的请求被交替处理。这,可以说是程序设计的基本常识了。
但是,在分布式的环境下,还要同时考虑到分布式系统内,可能在任何时刻出现的通讯故障。如果一个节点在取得锁之后、在释放锁之前发生崩溃失联,就会导致整个操作被无限期的等待所阻塞。

(1 )议员p 选择一个比LastTried[p] 大的表决编号b ,设置LastTried[p] 的值为b ,然后将NextBallot (b )消息发送给某些议员
因此,算法中的加锁,就不完全等同于并发控制中以互斥量来实现的加锁,还必须提供一个其他节点能抢占锁的机制,以避免因通讯问题而出现死锁的问题

(2 )从p 收到一个b 大于NextBallot[q] 的NextBallot (b )消息后,议员q 将NextBallot[q] 设置为b ,然后发送一个LastVote (b,v )消息给p ,其中v 等于[q] (b≤NextBallot[q] 的NextBallot (b )消息将被忽略)。

(3 )在某个多数集合Q 中的每个成员都收到一个LastVote (b,v )消息后,议员p 发起一个编号为b 、法定人数集为Q 、议案为d 的新表决。然后它会给Q 中的每一个牧师发送一个BeginBallot (b,d )消息。

(4 )在收到一个b=NextBallot[q] 的BeginBallot (b,d )消息后,议员q 在编号为b 的表决中投出他的一票,设置[p] 为这一票,然后向p 发送Voted (b,q )消息。
### Paxos 算法的工作流程

(5 )p 收到Q 中每一个q 的Voted (b,q )消息后(这里Q 是表决b 的法定人数集合,b=LastTried[p] ),将d (这轮表决的法令)记录到他的本子上,然后发送一条Success (d )消息给每个q
Paxos 算法包括“准备(Prepare)”和“批准(Accept)”两个阶段

(6 )一个议员在接收到Success (d )消息后,将决议d 写到他的本子上。


**第一阶段“准备”(Prepare)**就相当于抢占锁的过程。

如果某个提案节点准备发起提案,必须先向所有的决策节点广播一个许可申请(称为 Prepare 请求)。

提案节点的 Prepare 请求中会附带一个全局唯一的数字 n 作为提案 ID,决策节点收到后,会给提案节点**两个承诺和一个应答**

> 两个承诺是指:
>
> 承诺不会再接受提案 ID 小于或等于 n 的 Prepare 请求;承诺不会再接受提案 ID 小于 n 的 Accept 请求。
> 一个应答是指:
>
> 在不违背以前作出的承诺的前提下,回复已经批准过的提案中 ID 最大的那个提案所设定的值和提案 ID,
>
> 如果该值从来没有被任何提案设定过,则返回空值。
>
> 如果违反此前做出的承诺,也就是说收到的提案 ID 并不是决策节点收到过的最大的,那就可以直接不理会这个 Prepare 请求。


当提案节点收到了多数派决策节点的应答(称为 Promise 应答)后,可以开始**第二阶段“批准”(Accept)**过程。这时有两种可能的结果:

- 如果提案节点发现所有响应的决策节点此前都没有批准过这个值(即为空),就说明它是第一个设置值的节点,可以随意地决定要设定的值;并将自己选定的值与提案 ID,构成一个二元组 (id, value),再次广播给全部的决策节点(称为 Accept 请求)。

- 如果提案节点发现响应的决策节点中,已经有至少一个节点的应答中包含有值了,那它就不能够随意取值了,必须无条件地从应答中找出提案 ID 最大的那个值并接受,构成一个二元组 (id, maxAcceptValue),然后再次广播给全部的决策节点(称为 Accept 请求)。

当每一个决策节点收到 Accept 请求时,都会在不违背以前作出的承诺的前提下,接收并持久化当前提案 ID 和提案附带的值。如果违反此前做出的承诺,即收到的提案 ID 并不是决策节点收到过的最大的,那允许直接对此 Accept 请求不予理会。

当提案节点收到了多数派决策节点的应答(称为 Accepted 应答)后,协商结束,共识决议形成,将形成的决议发送给所有记录节点进行学习。整个过程的时序图如下所示:

![paxos-workflow.png](../image/paxos-workflow.png)



#### 具体的协议过程

对议员来说,每个议员有一个结实耐用的本子和擦不掉的墨水来记录议案,议员会把表决信息记在本子的背面,本子上的议案永远不会改变,但是背面的信息可能会被划掉。

每个议员必须(也只需要)在本子背面记录如下信息:

LastTried[p] ,由议员p 试图发起的最后一个议案的编号,如果议员p 没有发起过议案,则记录为负无穷大。

PreviousVote[p] ,由议员p 投票的所有表决中,编号最大的表决对应的投票,如果没有投过票则记录为负无穷大。

NextBallot[p] ,由议员p 发出的所有LastVote (b,v )消息中,表决编号b 的最大值。

基本协议的完整过程如下。

> (1 )议员p 选择一个比LastTried[p] 大的表决编号b ,设置LastTried[p] 的值为b ,然后将NextBallot (b )消息发送给某些议员。
>
> (2 )从p 收到一个b 大于NextBallot[q] 的NextBallot (b )消息后,议员q 将NextBallot[q] 设置为b ,然后发送一个LastVote (b,v )消息给p ,其中v 等于[q] (b≤NextBallot[q] 的NextBallot (b )消息将被忽略)。
>
> (3 )在某个多数集合Q 中的每个成员都收到一个LastVote (b,v )消息后,议员p 发起一个编号为b 、法定人数集为Q 、议案为d 的新表决。然后它会给Q 中的每一个牧师发送一个BeginBallot (b,d )消息。
>
> (4 )在收到一个b=NextBallot[q] 的BeginBallot (b,d )消息后,议员q 在编号为b 的表决中投出他的一票,设置[p] 为这一票,然后向p 发送Voted (b,q )消息。
>
> (5 )p 收到Q 中每一个q 的Voted (b,q )消息后(这里Q 是表决b 的法定人数集合,b=LastTried[p] ),将d (这轮表决的法令)记录到他的本子上,然后发送一条Success (d )消息给每个q 。
>
> (6 )一个议员在接收到Success (d )消息后,将决议d 写到他的本子上。
>
从上面的介绍可以看出,Paxos 不是那么容易理解的,不过总结一下核心的原则就是少数服从多数。



#### 示例

![paxos-worfkflow-promise1.png](../image/paxos-worfkflow-promise1.png)

![paxos-worfkflow-promise2.png](../image/paxos-worfkflow-promise2.png)

![paxos-worfkflow-promise3.png](../image/paxos-worfkflow-promise3.png)

![paxos-worfkflow-promise4.png](../image/paxos-worfkflow-promise4.png)

### 总结

大家会发现,如果系统中同时有人提议案的话,可能会出现碰撞失败,然后双方都需要增加议案的编号再提交的过程。而再次提交可能仍然存在编号冲突,因此双方需要再增加编号去提交。这就会产生活锁。
Expand Down
Binary file added image/paxos-worfkflow-promise1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added image/paxos-worfkflow-promise2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added image/paxos-worfkflow-promise3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added image/paxos-worfkflow-promise4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added image/paxos-workflow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 0985a5e

Please sign in to comment.