Skip to content

Commit

Permalink
删除 multi paxos 算法
Browse files Browse the repository at this point in the history
  • Loading branch information
isno committed Dec 5, 2024
1 parent e3b4293 commit bab51e9
Show file tree
Hide file tree
Showing 9 changed files with 44 additions and 67 deletions.
1 change: 0 additions & 1 deletion .vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,6 @@ export default defineUserConfig({
children: [
'/consensus/Paxos-history.md',
'/consensus/Basic-Paxos.md',
'/consensus/Multi-Paxos.md',
]
},
{
Expand Down
37 changes: 21 additions & 16 deletions consensus/Basic-Paxos.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ Paxos 是基于 Quorum 的算法,仅依靠“数服从多数原则”,一定

注意,你会发现矛盾点在于 S~3~,S~3~ 是两个多数派中的交集点,它的时间线上有两个不同的值被选中,这实际上是一个“并发”操作问题。

我们知道,程序设计中的一个基本常识是:并发操作一个共享变量,一定要加上互斥锁,不然会出现各种意外情况。所以,S~3~ 的问题本质是:“在分布式环境下并发操作共享变量”。
我们知道,程序设计中的一个基本常识是:多个线程并发操作一个共享变量,一定要加上互斥锁,不然会出现各种意外情况。所以,S~3~ 的问题本质是:“在分布式环境下并发操作共享变量的问题”。

但我们不能简单“套用”进程加锁的方式,分布式环境下可能随时会出现通信故障,如果一个节点获得锁之后,释放锁之前故障了,那么整个系统都会被无限期等待阻塞。因此,必须提供一种可供其他节点抢占锁的机制,避免因通信故障出现的死锁问题。分布式抢占锁的设计思想和 5.4.2 节提到的“乐观锁”有“异曲同工之妙”。回顾乐观锁的示例 SQL,WHERE 条件的作用是判断在它操作之前,数据是否被修改。如果修改过,则请求最新的数据,更新版本号,然后用重试的方式再次修改。
我们不能简单“套用”进程加锁的方式,分布式环境下可能随时会出现通信故障,如果一个节点获得锁之后,释放锁之前故障了,整个系统都会被无限期等待阻塞。因此,必须提供一种可供其他节点抢占锁的机制,避免因通信故障出现的死锁问题。分布式抢占锁的设计思想和 5.4.2 节提到的“乐观锁”有“异曲同工之妙”。回顾乐观锁的示例 SQL,WHERE 条件的作用是判断在它操作之前,数据是否被修改。如果修改过,则请求最新的数据,更新版本号,然后用重试的方式再次修改。

```SQL
UPDATE accounts
Expand All @@ -36,8 +36,6 @@ WHERE id = ? AND version = ?;
我们延续“乐观锁”的思路,我们讨论图 6-7 所示的因延迟导致的冲突如何解决。S~1~ 发起提案,S~3~ 收到 S~1~ 提案时,应该意识到 blue 的 Quorum 达成,S~1~ 的提案 red 太老。所以,为了提案达成一致,S~1~ 应该更新提案,把 red 修改为 blue(相当于乐观锁中的操作失败,请求最新数据),然后再次重试。




## 2. Paxos 算法描述

相信通过以上铺垫,你已经具备了足够的前置知识来理解 Paxos 算法。简而言之,Paxos 算法本质上是一个支持多次提案的二阶段提交协议。
Expand All @@ -54,46 +52,53 @@ Paxos 算法的第二个阶段称“批准阶段”(Accept)。在该阶段

:::center
![](../assets/paxos.svg) <br/>
图 6-8 Basic Paxos 流程
图 6-6 Basic Paxos 流程
:::

## 3. Paxos 算法验证

Paxos 难以理解是指其推导过程复杂,证明 Paxos 的正确性比重新实现 Paxos 算法还难。我们没必须推导 Paxos 的正确性,通过以下几个例子来验证 Paxos 算法的运行机制。

假设有五个节点 S~1~、S~2~、S~3~、S~4~、S~5~,其中 S~1~、S~5~ 扮演 Proposer,它们同是又都是 Acceptor 角色。我们根据上述 Paxos 的二阶段算法,分析它们就一个“提案选取值 X 还是 Y ”进行决议,会出现什么情况。
下面的示例中,X、Y 代表客户端,S~1~ 到 S~5~ 是服务端,它们既是 Proposer 又都是 Acceptor,图中的 P 代表 “Prepare 阶段”,A 代表“Accept 阶段”。这里为了防止重复,Proposer 提出的编号 n 由两部分组成:自增序号.Server ID。例如,S~1~ 提出的提案编号,就是 1.1、2.1、3.1...。

下面我们分析它们就一个“提案选取值 X 还是 Y ”进行决议,会出现什么情况。

**情况一:提案已达成**,假设 S~1~ 选定提案编号 3.1(节点编号加上自增 ID),优先取得了多数 Acceptor 的 Promise 和 Accepted 应答。另外,S~5~ 选定提案编号 4.5,广播 Prepare 消息,收到的应答中至少会包含 1 个此前应答过 S~1~ 的 Acceptor,假设是 S~3~,那么 S~3~ 的响应 Promise 中必将包含 S~1~ 已设定好的值 X。那么,S~5~ 就必须无条件地用 X 代替 Y 作为自己提案的值,由此整个系统对“取值为 X”这个事实达成一致。整个流程如下图所示。
**情况一:提案已批准**。如图 6-7 所示,S~1~ 收到客户端的提案 X,于是 S~1~ 作为 Proposer,向 S~1~...S~3~ 广播 Prepare(3.1) 请求,由于 Acceptor S~1~...S~3~ 没有接受过任何提案,所以接受该提案。接着,Proposer S~1~ 广播 Accept(3.1, X) 请求,提案 X 成功被批准成为决议。

在提案 X 被批准后,S~5~ 收到客户端的提案 Y,S~5~ 向 S~3~...S~5~ 广播 Prepare(4.5) 请求。对 S~3~ 来说,4.5 比 3.1 大,且已经接受了 X,它会回复提案 (3.1, X)。S~5~ 收到 S~3~...S~5~ 的回复后,使用 X 替换自己的 Y,于是广播 Accept(4.5, X) 请求。S~3~...S~5~ 接受提案。最终所有 Acceptor 达成一致,都拥有相同的值 X。

:::center
![](../assets/paxos-p1.png) <br/>
图 6-9 提案已 Chosen,P 代表 “Prepare 阶段”,A 代表“Accept 阶段”
图 6-7 提案已批准
:::

**情况二:事实上,对于情况一,也就是“取值为 X”并不是一定需要多数派批准,S~5~ 提案时 Promise 应答中是否包含了批准过 X 的 Acceptor 也影响决策**例如。图 6-3 所示,S~5~ 广播 Prepare 请求,X 并未获得多数派批准,但由于 S~3~ 已经批准的关系,最终共识的结果仍然是 X。
**情况二:事实上,对于情况一,也就是“取值为 X”并不是一定需要多数派批准,S~5~ 发起提案时,Prepare 阶段的应答中是否包含了批准过 X 的 Acceptor 也影响决策**如图 6-8 所示,S~3~ 接受了提案 (3.1, X),但 S~1~、S~2~ 还没有收到 Accept(3.1, X) 消息。此时 S~3~、S~4~、S~5~ 收到 Prepare(4.5) 消息,S~3~ 会回复已经接受的提案 (3.1, X),S~5~ 将提案值 Y 替换成 X,广播 Accept(4.5, X) 消息给 S~3~、S~4~、S~5~,对 S~3~ 来说,编号4.5 大于 3.1,所以会接受这个提案,最终共识的结果仍然是 X。

:::center
![](../assets/paxos-p2.png) <br/>
图 6-10 提案未 Chosen,Proposer 可见
图 6-8 提案部分接受,新 Proposer 可见
:::

**情况三:另外一种可能得情况是 S~5~ 提案时 Promise 应答中并未包含批准过 X 的决策节点**例如 S~5~ 提案期间,节点 S~2~ 、S~3~ 返回 Promise 应答,节点 S~1~ 批准了 X。此时,S~5~ 以更大的提案 ID 获得了 S~3~、S~4~、S~5~ 的 Promise,这 3 个节点均未批准过任何值。那么,因为 S~1~ 提案编号已经不是最大的了,S~3~ 将拒绝 S~1~ 的 Accept 请求,这 3 个节点将批准 Y 的取值,整个系统最终对“取值为 Y”达成一致,如下图所示
**情况三:另外一种可能的情况是 S~5~ 发起提案时 Prepare 阶段的应答中未包含批准过 X 的决策节点**S~1~ 接受了提案 (3.1, X),S~3~ 先收到 Prepare(4.5),后收到 Accept(3.1, X),由于 3.1 小于 4.5,会直接拒绝这个提案。所以提案 X 无法收到超过半数的回复,这个提案就被阻止了。提案 Y 顺利通过,整个系统最终对“取值为 Y”达成一致。

:::center
![](../assets/paxos-p3.png) <br/>
图 6-11 提案未提交,Proposer 不可见
图 6-9 提案部分接受,新 Proposer 不可见
:::

**情况四:从情况三可以推导出另一种极端的情况,如果 2 个提案节点交替使用更大的提案编号使得“准备阶段成功,但是批准阶段失败”的话,这个过程理论上可以无限持续下去,形成活锁**。例如,S~3~、S~4~、S~5~ 拿着更高的提案编号导致 S~1~、S~2~、S~3~ 的 accept 阶段失败重新发起的 Prepare 阶段,又把 S~3~、S~4~、S~5~ 给拒绝了,Proposer 没有看到先前提议的情况下,当 S~1~ 发现自己的提议没有通过,就会发起新一轮 Prepare RPC,然后就有可能又封锁了 S~5~ 的提议,S~5~ 又会回到 Prepare 阶段,有概率双方都轮流封锁对方的协议,导致无法达成共识。

**情况四:从情况三可以推导出另一种极端的情况**,两个或多个 Proposer 同时发起提议,在 Prepare 阶段互相抢占,反复刷新 Acceptor 上的提案编号,导致任何一方都无法达到多数派决议,这个过程理论上可以无限持续下去,形成活锁(livelock)。

解决这个问题并复杂,是把重试时间进行一些随机化,减少这种巧合发生。
:::center
![](../assets/paxos-p4.png) <br/>
图 6-12 出现活锁问题
图 6-10 出现活锁问题
:::

在计算机工程领域解决这个问题并复杂,随机化重试时间减少这种巧合发生,或者把重试的时间指数增长等等。
Paxos 算法的的价值在于扩展了分布式共识算法的发展思路,但它有以下缺陷:只能对决议一个提案,决议形成至少需要两次网络来回,高并发情况还有可能形成活锁。因此,Basic Paxos 几乎只是用来做理论研究,并不直接应用在实际工程中。后来,Lamport 在“Paxos Made Simple” 论文中,针对多次提案和大规模分布式系统中的实际需求,提出了 Paxos 的优化变体 Multi Paxos。Multi Paxos 的思想是增加“选主”机制,通过多次运行 Paxos 算法来处理一系列提案。

不过,Lamport 的论文主要关注的是 Paxos 的算法基础和正确性证明,对于领导者选举以及解决多轮提案的效率问题,并没有给出充分的实现细节。2014 年,斯坦福的学者 Diego Ongaro 和 John Ousterhout 发表了论文《In Search of an Understandable Consensus Algorithm》,该论文基于 Multi-Paxos 思想上,提出了简化和改进版的 Raft 算法。Raft 算法明确提出了“选主”、“日志复制”等概念以及实现细节的描述。该论文斩获 USENIX ATC 2014 大会 Best Paper 荣誉,Raft 算法更是成为 etcd、Consul 等分布式系统的实现基础。

Paxos Basic 的价值在于扩展了分布式共识算法的发展思路,但它有以下缺陷:只能对一个值形成决议,而且决议形成至少需要两次网络来回,高并发情况还有可能形成活锁,因此 Basic Paxos 几乎只是用来做理论研究,并不直接应用在实际工程中。

[^1]: 讲解作者是斯坦福教授 John Ousterhunt,他还指导了 Diego Ongaro 写出了 Raft 的论文。本章配图也多来源于 John Ousterhunt 所发表的内容。
[^2]: 参见 https://lamport.azurewebsites.net/pubs/time-clocks.pdf
27 changes: 0 additions & 27 deletions consensus/Multi-Paxos.md

This file was deleted.

6 changes: 3 additions & 3 deletions consensus/raft-ConfChange.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

:::center
![](../assets/raft-ConfChange.png) <br/>
图 6-21 某一时刻,集群存在两个 Quorum
图 6-15 某一时刻,集群存在两个 Quorum
:::

产生这个问题的根本原因是:成员变更的过程中,产生了两个没有交集的 Quorum,也就是 [Server1,Server2][Server3, Server4, Server5] 两个 Quorum 各自为营。
Expand All @@ -29,13 +29,13 @@
单成员变更方案很容易穷举出所有情况,如图 6-22 所示,穷举集群奇/偶数节点下添加/删除情况。如果每次只操作一个节点,那么 **C~old~ 的 Quorum 和 C~new~ 的 Quorum 之间一定存在交集**。同一个 term 中,C~old~ 和 C~new~ 中交集的那个节点只会进行一次投票,要么投票给 C~old~,要么投票给 C~new~,不可能出现两个符合条件的 Quorum,也就不会出现两个 Leader。


以图 6-22 第二种情况为例,C~old~[Server1、Server2、Server3],该集群的 Quorum 为(N/2)+1 = 2,C~new~[Server1、Server2、Server3、Server4],该集群的 Quorum 为(N/2)+1 = 3。假设 Server1、Server2 比较迟钝,还在用 C~old~ ,其他节点的状态机已经“apply” C~new~
以图 6-16 第二种情况为例,C~old~[Server1、Server2、Server3],该集群的 Quorum 为(N/2)+1 = 2,C~new~[Server1、Server2、Server3、Server4],该集群的 Quorum 为(N/2)+1 = 3。假设 Server1、Server2 比较迟钝,还在用 C~old~ ,其他节点的状态机已经“apply” C~new~
- 假设 Server1 触发选举,赢得 Server1,Server2 的投票,满足 C~old~ Quorum 要求,当选 Leader;
- 假设 Server3 也触发选举,赢得 Server3,Server4 的投票,但**不满足 C~new~ 的 Quorum 要求,选举失效**

:::center
![](../assets/raft-single-server.svg) <br/>
图 6-22 穷举集群添加节点的情况
图 6-16 穷举集群添加节点的情况
:::

目前,绝大多数 Raft 算法的实现或系统,如 Hashicrop Raft、Etcd 等都是使用单节点变更方案。联合共识方案由于其复杂性和落地难度笔者就不再过多介绍,有兴趣的读者可以阅读 Raft 论文了解相关内容。
12 changes: 7 additions & 5 deletions consensus/raft-leader-election.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# 6.4.1 领导者选举

Raft 是一个强领导者算法,节点间并不平等。不同角色的节点承担不用的职责。
Paxos 算法中“节点众生平等”,每个节点都可以并行的发起提案。并行的发起提案,是导致活锁、以及其他异常问题的主要原因。那如何不破坏 Paxos 的“节点众生平等”基本原则,又能在提案节点中实现主次之分,限制每个节点都有不受控的提案权利?

Raft 对此的改进是明确领导者角色,增加选举机制分享提案权利。Raft 算法中,节点分为以下三种角色:

- **Leader(领导者)**:负责处理所有客户端请求,将请求转换为“日志”复制到其他节点,确保日志在多数节点(Quorum)上被提交并生效。其次,定期向所有节点发送心跳,维持自己的领导地位。
- **Follower(跟随者)**:接收 Leader 发送的日志条目,确认日志条目的写入情况,并向 Leader 发送响应。
Expand All @@ -17,21 +19,21 @@ Raft 是一个强领导者算法,节点间并不平等。不同角色的节点

:::center
![](../assets/raft-term.svg)
图 6-15 Raft term 与成员状态变更
图 6-11 Raft term 与成员状态变更
:::


图 6-16 概述了 Raft 集群 Leader 选举过程。每个 Follower 在本地维持一个选举时钟,选举时钟到期时,如果没有收到 Leader 的日志或者心跳,follower 增加自己的 term,将身份切换为 candidate。向集群其它节点发送“请给自己投票”的消息(RequestVote RPC)。会发生下面的情况:
图 6-12 概述了 Raft 集群 Leader 选举过程。每个 Follower 在本地维持一个选举时钟,选举时钟到期时,如果没有收到 Leader 的日志或者心跳,follower 增加自己的 term,将身份切换为 candidate。向集群其它节点发送“请给自己投票”的消息(RequestVote RPC)。会发生下面的情况:

- **选举超时**:在固定时间内未收到其他节点的响应,或者收到的响应(同意票)的节点数量未达到 Quorum N/2+1 的要求。此时,触发选举超时进入下一轮选举;
- **选举失败**:选举过程中,收到 Leader 心跳,或者发现有更高的 term,说明有新任期的 Leader,结束结束当前的选举。
- **选举成功**:选举过程中,收到的响应节点的数量达到 Quorum 要求,选举成功成为集群的 Leader。之后,Leader 周期性的向所有节点发送心跳包来维持自己的权威。

:::center
![](../assets/raft-election.svg)
图 6-16 Raft 选举过程
图 6-12 Raft 选举过程
:::

使用 Quorum 机制选举出的 Leader 代表了整个集群的意志。现在,你思考:“代表集群意志的 Leader 发起提案,是否还需要 Paxos 第一轮中 “准备阶段” 呢?”。



Expand Down
Loading

0 comments on commit bab51e9

Please sign in to comment.