Skip to content

Commit

Permalink
更新 Paxos 算法
Browse files Browse the repository at this point in the history
  • Loading branch information
isno committed Dec 1, 2024
1 parent fe3df4c commit 9a8c4a8
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 45 deletions.
22 changes: 8 additions & 14 deletions consensus/Basic-Paxos.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@
- **决策者(Acceptor)**:接受或拒绝 Proposer 的提案,如果一个提案被超过半数的 Acceptor 接受,那提案意味着被“批准”(accepted)。提案一旦被批准,意味着在所有节点中达到共识,便不可改变、永久生效。
- **记录者(Learner)**:记录者发不起提案,也不参与决策提案,但它们需要学习并知道哪些提案被接受作为最终决策。

在 Paxos 算法中,节点都是平等的,它们都可以承担一种或者多种角色。例如,一个节点即可发起提案,也可以就一个提案进行表决,但为了达成 Quorum,表决提案的角色要保证有奇数个
在 Paxos 算法中,节点都是平等的,它们都可以承担一种或者多种角色。例如,一个节点即可发起提案,也可以就一个提案进行表决,但为了Quorum 的计算更加明确,表决提案的角色最好要有奇数个

Paxos 是基于 Quorum 的算法,仅依靠“数服从多数原则”,一定会遇到提案冲突的问题。如图所示:。S~1~ 看到其他服务器没有接受某个值,之后提出 red 值。S~5~ 也没有看到其他服务器接受某个值,于是提出 blue 值。它们的提案 Quorum 都达成了,也就是说一个提案有两个值选中,这显然破坏了一致性原则。
Paxos 是基于 Quorum 的算法,仅依靠“数服从多数原则”,一定会遇到提案冲突的问题。如图 6-5 所示,S~1~ 看到其他服务器没有接受某个值,之后提出 red 值。S~5~ 也没有看到其他服务器接受某个值,于是提出 blue 值。它们的提案 Quorum 都达成了,也就是说一个提案有两个值选中,这显然破坏了一致性原则。

:::center
![](../assets/paxos_2pc_choice.png) <br/>
图 6-7 网络延迟导致冲突
图 6-5 网络延迟导致冲突
:::

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

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

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

```SQL
UPDATE accounts
Expand All @@ -33,15 +33,12 @@ SET balance = balance + ?,
WHERE id = ? AND version = ?;
```

对于 图 6-7 所示的因延迟导致的冲突,根本的解决方式是“抢占”。S~1~ 发起提案时,S~3~ 收到 S~1~ 提案时,应该意识到 blue 的 Quorum 达成,S~1~ 的提案 red 太老。为了就提案达成一致,S~1~ 应该更新提案,把 red 修改为 blue(相当于乐观锁中的操作失败,请求最新数据),然后再次重试。
我们延续“乐观锁”的思路,我们讨论图 6-7 所示的因延迟导致的冲突如何解决。S~1~ 发起提案,S~3~ 收到 S~1~ 提案时,应该意识到 blue 的 Quorum 达成,S~1~ 的提案 red 太老。所以,为了提案达成一致,S~1~ 应该更新提案,把 red 修改为 blue(相当于乐观锁中的操作失败,请求最新数据),然后再次重试。

:::tip 思考:3 个节点的容忍度是 1,那么 4 节点的容忍度是多少?

答案也是 1,因为要形成发现矛盾的交集对于 4 来说,要达到 3/4,才能构成大多数,这就是为什么集群选单数的原因,因为双数从算法的角度来说没什么帮助。
:::


## 1. Paxos 算法描述
## 2. Paxos 算法描述

相信通过以上铺垫,你已经具备了足够的前置知识来理解 Paxos 算法。简而言之,Paxos 算法本质上是一个支持多次提案的二阶段提交协议。

Expand All @@ -60,7 +57,7 @@ Paxos 算法的第二个阶段称“批准阶段”(Accept)。在该阶段
图 6-8 Basic Paxos 流程
:::

## 2. Paxos 算法验证
## 3. Paxos 算法验证

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

Expand Down Expand Up @@ -96,10 +93,7 @@ Paxos 难以理解是指其推导过程复杂,证明 Paxos 的正确性比重

在计算机工程领域解决这个问题并复杂,随机化重试时间减少这种巧合发生,或者把重试的时间指数增长等等。

总结 Paxos 中保证一致性的最核心的两个原则其实就是**少数服从多数****后者认同前者**。Paxos Basic 只能对一个值形成决议,而且决议形成至少需要两次网络来回,高并发情况还有可能形成活锁,因此 Basic Paxos 几乎只是用来做理论研究,并不直接应用在实际工程中。

2014 年,斯坦福的学者 Diego Ongaro 和 John Ousterhout 发表了论文《In Search of an Understandable Consensus Algorithm》,提出了 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
35 changes: 12 additions & 23 deletions consensus/Multi-Paxos.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,29 @@
# 6.2.3 Multi Paxos

既然 Paxos Basic 可以确定一个值,**想确定多个值(日志)那就运行多个 Paxos Basic ,然后将这些值列成一个序列( Replicated log),在这个过程中并解决效率问题** —— 这就是 Multi Paxos 。
既然 Paxos Basic 可以确定一个值,想确定多个值那就运行多次 Paxos Basic —— 这就是 Multi Paxos 。

Replicated log 类似一个数组,因此我们需要知道当次请求是在写日志的第几位,Multi Paxos 做的第一个调整就是要添加一个日志的 index 参数到 Prepare 和 Accept 阶段,表示这轮 Paxos 正在决策哪一条日志记录。
来看一个具体的例子,当 S~1~ 收到客户端的请求 jmp(提案)时(假设此时是 S~3~ 宕机),运行多次 Paxos Basic 会出现什么情况:

下面我们举个例子说明,当 S~1~ 收到客户端的请求命令 jmp(提案)时(假设此时是 S~3~ 宕机):

- 会先尝试日志项 3,发现已经写入了 cmp(但 cmp 还未被选中),于是运行 Basic Paxos 在日志项 3 放置 cmp。
- 继续找下一个没有选中的位置 —— 也就是第 4 位,由于 S~2~ 已经存在 sub,产生冲突,最终 sub 在日志项 4 达成共识。
- S~1~ 继续尝试下一个日志项,直到给 jmp 找到一个可以达成共识的位置(日志项 4)。
- 第一轮 Paxos Basic:尝试在索引 4 处写入提案(cmp),但 Prepare 阶段发现 S~2~ 的索引 3 已经存在 sub 提案,S~1~ 的索引 4 处,写入提案(sub)。
- 第二轮 Paxos Basic:S1 继续尝试下一个日志索引 5,本轮就S~1~、S~2~ 的索引 5 的提案达成共识;
- 此外,上述过程中还发现 S~2~ 存在空洞日志。S~1~ 发起新一轮 Paxos Basic,S~2~ 的索引 3 处,cmp 提案达成共识。

:::center
![](../assets/multi_paxos.png) <br/>
图 6-14 当节点收到客户端的请求命令 jmp(提案)时情况
:::

我们思考一下上面的流程中的一些问题:
1. jump 提案经历了 3 轮 Basic Paxos,共花费 6 个 RTT。
2. 当多个节点同时进行提议的时候,对于 index 的争抢会比较严重,会出现冲突导致活锁。

Paxos 想要从理论走向现实,必须要解决这两类问题。

Lamport 的第一个想法是**选择一个 Leader,任意时刻只有一个 Proposer,这样就可以避免冲突**。关于 Leader 的选举,Lamport 提出了一种简单的方式:让 server_id 最大的节点成为 Leader(在上篇说到提案编号由自增 id 和 server_id 组成,就是这个 server_id)。如此,节点之间就要维持 T 间隔的心跳,如果一个节点在 2T 时间内没有收到比自己 server_id 更大的心跳,那它自己就转为 Leader,担任 Proposer 和 Acceptor 的职责,并开始处理客户端请求。那些非 Leader 的节点如果收到客户端的请求,要么丢弃要么将请求重定向到 Leader 节点。

Lamport 提出的选举方案是一种很简单的策略,有同时出现 2 个 Leader 的概率,不过即使是系统中有 2 个 Leader,Paxos 也是能正常工作的,只是冲突的概率就大了很多。
决议jump 提案时经历了 3 轮 Basic Paxos,花费 6 个 RTT(日志不顺序,以及 Basic Paxos 本身就需要 2 个 RTT)。此外,当多个节点同时发起提案时,还导致频繁出现活锁。

除了选主的优化方式,另外一个思路就是**优化二阶段提交的次数**,具体的方式是减小 Prepare 请求之前,在此之前,我们再来回顾 Prepare 阶段的作用:
形成活锁的原因是 Paxos 算法中“节点众生平等”,每个节点都可以并行的发起提案。如何不破坏 Paxos 的“节点众生平等”基本原则,又能在提案节点中实现主次之分,限制每个节点都有不受控的提案权利?这是共识算法从理论研究走向实际工程的第一步;
如何就多个值形成决议,并在过程成解决网络通信效率问题?,这是共识算法走向实际工程的第二步;解决了上述两个问题,并在过程中保证安全性。就认为是一个可“落地”的共识系统。

1. 阻止那些更老的还未完成的提案被选中
2. 看看有没有已经被选中的值,如果有,提议者的提议就应该使用这个值
Multi Paxos 算法对此的改进是增加“选主”机制。节点之间就要维持 T 间隔的心跳,如果一个节点在 2T 时间内没收到“提案节点”的心跳,那该节点通过 Basic Paxos 中的准备阶段、批准阶段,向其他节点广播“我希望成为提案节点”,如果得到决策节点的多数派批准,则宣告竞选成功。选主成功后,“提案节点”处理所有的客户端请求,其他节点如果收到客户端的请求,要么丢弃要么将请求重定向到 Leader 节点。

优化 Prepare 阶段的前提要保证上述功能的完整性。第一点,我们可以通过改变提议序号的含义来解决这个问题,**将提议序号全局化,代表完整的日志 ,而不是为每个日志记录都保留独立的提议序号**。这样的话就只需要一次 prepare 请求就可以 prepare 所有的日志了
一旦选举出多数节点接受的领导者。那领导者就可以跳过 Basix Paxos 中 Prepare 多数派承诺阶段,直接向其他节点广播 Accept 消息即可。这样一个提案达成共识,只需要一轮 RPC

关于第二点,需要拓展 Prepare 请求的返回信息,和之前一样,Prepare 还是会返回最大提案编号的 acceptedValue,除此之外,Acceptor 还会向后查看日志记录,如果要写的这个位置之后都是空的记录,没有接受过任何值,那么 Acceptor 就额外返回一个标志位 noMoreAccepted。

后续,如果 Leader 接收到超过半数的 Acceptor 回复了 noMoreAccepted,那 Leader 就不需要发送 Prepare 请求了,直接发送 Accept 请求即可,这样只需要一轮 RPC。
把上述问题总结为“选主”、“日志复制”
、“安全”三个子问题来思考,就是下一节我们要讨论的 Raft 算法。2014 年,斯坦福的学者 Diego Ongaro 和 John Ousterhout 发表了论文《In Search of an Understandable Consensus Algorithm》,提出了 Multi-Paxos 思想上简化和改进的 Raft 算法,该论文斩获 USENIX ATC 2014 大会 Best Paper 荣誉,Raft 算法更是成为 etcd、Consul 等分布式系统的实现基础。

最后,把上述共识问题分解为领导者选举、日志复制和安全性三个问题来思考,就是下一节我们要讨论的 Raft 算法中的内容。2014 年,斯坦福的学者 Diego Ongaro 和 John Ousterhout 发表了论文《In Search of an Understandable Consensus Algorithm》,提出了 Raft 算法,该论文斩获 USENIX ATC 2014 大会 Best Paper 荣誉,Raft 算法更是成为 etcd、Consul 等分布式系统的实现基础。

2 changes: 0 additions & 2 deletions consensus/consensus.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
- 共识(Consensus):指所有节点就某项操作(如选主、原子事务提交、日志复制、分布式锁管理等)达成一致的过程及其算法。
- 一致性(Consistency):描述多个节点的数据是否保持一致,关注数据最终达到稳定状态的结果。

Paxos、Raft、ZAB 等等属于 consensus 算法,明显使用“共识”描述更准确,而 CAP 定理中的 C 和数据库 ACID 的 C 才是真正的“一致性” —— consistency 问题。

因此,Paxos、Raft 和 ZAB 等算法应归类为“共识”算法,而 CAP 定理中的 C 和数据库 ACID 模型中的 C 则描述的是“一致性”问题。将 Paxos 等算法称为“共识算法”更为准确。

在分布式系统中,节点故障是不可避免的。为提高系统可靠性,可以通过增加节点数量,依据“少数服从多数”的原则确保系统在多数节点(n/2+1)正常工作的情况下仍能达成决策。这种通过多数节点(Quorum)保证系统容错性的机制被称为 Quorum 机制。
Expand Down
8 changes: 3 additions & 5 deletions consensus/raft-leader-election.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
# 6.4.1 领导者选举

Raft 算法中,节点间并不平等。不同角色的节点承担不用的职责。
Raft 是一个强领导者算法,节点间并不平等。不同角色的节点承担不用的职责。

- **Leader(领导者)**
- **Follower(跟随者)**
- **Leader(领导者)**负责处理所有客户端请求,将请求转换为“日志”复制到其他节点,确保日志在多数节点(Quorum)上被提交并生效。其次,定期向所有节点发送心跳,维持自己的领导地位。
- **Follower(跟随者)**接收 Leader 发送的日志条目,确认日志条目的写入情况,并向 Leader 发送响应。
- **Candidate()**:过渡角色 Candidate,Leader 丧失时,Follower 会转为 Candidate 参与选举;

Raft 是一个强领导者算法。强领导意思是:整个集群,只有一个节点接收客户端的所有请求,该节点将客户端的请求操作复制给其他节点(Follower,跟随者),其他节点无条件服从 Leader 的操作。


联想到现实世界中的领导人都有一段不等的任期。自然,Raft 中的 Leader 也对应的概念 —— term。

Expand Down
2 changes: 1 addition & 1 deletion consensus/raft.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ Raft is a consensus algorithm for managing a replicated log. It produces a resul

此后,Raft 算法成为分布式系统领域的首选共识算法。

众所周知,当问题比较复杂时,可以把大问题分解为几个小问题来处理。Raft 也使用分而治之的思想,把算法分为三个子问题:选举(Leader election)、日志复制(Log replication)、安全性(Safety)。接下来,笔者以这三个子问题为例,讲解在 Paxos 难以落地的问题,Raft 算法是如何设计和妥善解决的。
接下来,笔者将以领导者角色、选举机制、日志提交等机制,讲解在 Paxos 难以落地的问题,Raft 算法是如何设计和妥善解决的。

[^1]: 论文参见 https://raft.github.io/raft.pdf

0 comments on commit 9a8c4a8

Please sign in to comment.