From 9a8c4a88f867768dfa9566fd33add8ee4809e819 Mon Sep 17 00:00:00 2001 From: isno Date: Sun, 1 Dec 2024 21:59:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Paxos=20=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- consensus/Basic-Paxos.md | 22 +++++++------------ consensus/Multi-Paxos.md | 35 +++++++++++-------------------- consensus/consensus.md | 2 -- consensus/raft-leader-election.md | 8 +++---- consensus/raft.md | 2 +- 5 files changed, 24 insertions(+), 45 deletions(-) diff --git a/consensus/Basic-Paxos.md b/consensus/Basic-Paxos.md index f9b26bcd..f6d177ed 100644 --- a/consensus/Basic-Paxos.md +++ b/consensus/Basic-Paxos.md @@ -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)
- 图 6-7 网络延迟导致冲突 + 图 6-5 网络延迟导致冲突 ::: 注意,你会发现矛盾点在于 S~3~,S~3~ 是两个多数派中的交集点,它的时间线上有两个不同的值被选中,这实际上是一个“并发”操作问题。 我们知道,程序设计中的一个基本常识是:并发操作一个共享变量,一定要加上互斥锁,不然会出现各种意外情况。所以,S~3~ 的问题本质是:“在分布式环境下并发操作共享变量”。 -分布式环境下可能随时会出现通信故障,如果一个节点获得锁之后,释放锁之前故障了,那么整个系统都会被无限期等待阻塞。因此,我们不能简单“套用”进程加锁的方式,必须提供一种可供其他节点抢占锁的机制,避免因通信故障出现的死锁问题。分布式抢占锁的机制和 5.4.2 节提到的“乐观锁”有“异曲同工之妙”。回顾乐观锁的 SQL 示例,WHERE 条件的作用是判断在它操作之前,数据是否被修改。如果修改过,则请求最新的数据,更新版本号,然后用重试的方式再次修改。 +但我们不能简单“套用”进程加锁的方式,分布式环境下可能随时会出现通信故障,如果一个节点获得锁之后,释放锁之前故障了,那么整个系统都会被无限期等待阻塞。因此,必须提供一种可供其他节点抢占锁的机制,避免因通信故障出现的死锁问题。分布式抢占锁的设计思想和 5.4.2 节提到的“乐观锁”有“异曲同工之妙”。回顾乐观锁的示例 SQL,WHERE 条件的作用是判断在它操作之前,数据是否被修改。如果修改过,则请求最新的数据,更新版本号,然后用重试的方式再次修改。 ```SQL UPDATE accounts @@ -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 算法本质上是一个支持多次提案的二阶段提交协议。 @@ -60,7 +57,7 @@ Paxos 算法的第二个阶段称“批准阶段”(Accept)。在该阶段 图 6-8 Basic Paxos 流程 ::: -## 2. Paxos 算法验证 +## 3. Paxos 算法验证 Paxos 难以理解是指其推导过程复杂,证明 Paxos 的正确性比重新实现 Paxos 算法还难。我们没必须推导 Paxos 的正确性,通过以下几个例子来验证 Paxos 算法的运行机制。 @@ -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 diff --git a/consensus/Multi-Paxos.md b/consensus/Multi-Paxos.md index 0e6be8f7..5a708f33 100644 --- a/consensus/Multi-Paxos.md +++ b/consensus/Multi-Paxos.md @@ -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)
图 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 等分布式系统的实现基础。 diff --git a/consensus/consensus.md b/consensus/consensus.md index be4687fe..dda77b33 100644 --- a/consensus/consensus.md +++ b/consensus/consensus.md @@ -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 机制。 diff --git a/consensus/raft-leader-election.md b/consensus/raft-leader-election.md index 8fa03212..fbe6a624 100644 --- a/consensus/raft-leader-election.md +++ b/consensus/raft-leader-election.md @@ -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。 diff --git a/consensus/raft.md b/consensus/raft.md index 40096e59..4791a082 100644 --- a/consensus/raft.md +++ b/consensus/raft.md @@ -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 \ No newline at end of file