diff --git a/.vuepress/config.js b/.vuepress/config.js
index a990c4fc..bda20e68 100644
--- a/.vuepress/config.js
+++ b/.vuepress/config.js
@@ -223,7 +223,6 @@ export default defineUserConfig({
children: [
'/consensus/Paxos-history.md',
'/consensus/Basic-Paxos.md',
- '/consensus/Multi-Paxos.md',
]
},
{
diff --git a/consensus/Basic-Paxos.md b/consensus/Basic-Paxos.md
index 22fed5bb..8f172a79 100644
--- a/consensus/Basic-Paxos.md
+++ b/consensus/Basic-Paxos.md
@@ -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
@@ -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 算法本质上是一个支持多次提案的二阶段提交协议。
@@ -54,46 +52,53 @@ Paxos 算法的第二个阶段称“批准阶段”(Accept)。在该阶段
:::center
![](../assets/paxos.svg)
- 图 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)
- 图 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)
- 图 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)
- 图 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)
- 图 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
diff --git a/consensus/Multi-Paxos.md b/consensus/Multi-Paxos.md
deleted file mode 100644
index f321c278..00000000
--- a/consensus/Multi-Paxos.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# 6.3.3 Multi Paxos
-
-既然 Paxos Basic 可以确定一个值,想确定多个值那就运行多次 Paxos Basic —— 这就是 Multi Paxos 。
-
-来看一个具体的例子,当 S~1~ 收到客户端的请求 jmp(提案)时(假设此时是 S~3~ 宕机),运行多次 Paxos Basic 会出现什么情况:
-
-- 第一轮 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(提案)时情况
-:::
-
-决议jump 提案时经历了 3 轮 Basic Paxos,花费 6 个 RTT(日志顺序不一致,以及 Basic Paxos 本身就需要 2 个 RTT)。此外,当多个节点同时发起提案时,还导致频繁出现活锁。
-
-形成活锁的原因是 Paxos 算法中“节点众生平等”,每个节点都可以并行的发起提案。如何不破坏 Paxos 的“节点众生平等”基本原则,又能在提案节点中实现主次之分,限制每个节点都有不受控的提案权利?这是共识算法从理论研究走向实际工程的第一步;
-如何就多个值形成决议,并在过程成解决网络通信效率问题?,这是共识算法走向实际工程的第二步;解决了上述两个问题,并在过程中保证安全性。就认为是一个可“落地”的共识系统。
-
-Multi Paxos 算法对此的改进是增加“选主”机制。节点之间就要维持 T 间隔的心跳,如果一个节点在 2T 时间内没收到“提案节点”的心跳,那该节点通过 Basic Paxos 中的准备阶段、批准阶段,向其他节点广播“我希望成为提案节点”,如果得到决策节点的多数派批准,则宣告竞选成功。选主成功后,“提案节点”处理所有的客户端请求,其他节点如果收到客户端的请求,要么丢弃要么将请求重定向到 Leader 节点。
-
-一旦选举出多数节点接受的领导者。那领导者就可以跳过 Basix Paxos 中 Prepare 多数派承诺阶段,直接向其他节点广播 Accept 消息即可。这样一个提案达成共识,只需要一轮 RPC。
-
-不过呢,Lamport 的论文主要关注的是 Basic Paxos 的算法基础和正确性证明,虽然在 Basic Paxos 之上做了 Multi Paxos 扩展,但没有深入描述如何通过领导者角色解决多轮提案的效率问题,且没有给出充分的优化细节。2014 年,斯坦福的学者 Diego Ongaro 和 John Ousterhout 发表了论文《In Search of an Understandable Consensus Algorithm》,提出了 Multi-Paxos 思想上简化和改进的 Raft 算法,明确提出了“选主”、“日志复制”等概念以及实现细节的描述。该论文斩获 USENIX ATC 2014 大会 Best Paper 荣誉,Raft 算法更是成为 etcd、Consul 等分布式系统的实现基础。
-
-
diff --git a/consensus/raft-ConfChange.md b/consensus/raft-ConfChange.md
index 1e2d2f89..868c506b 100644
--- a/consensus/raft-ConfChange.md
+++ b/consensus/raft-ConfChange.md
@@ -19,7 +19,7 @@
:::center
![](../assets/raft-ConfChange.png)
- 图 6-21 某一时刻,集群存在两个 Quorum
+ 图 6-15 某一时刻,集群存在两个 Quorum
:::
产生这个问题的根本原因是:成员变更的过程中,产生了两个没有交集的 Quorum,也就是 [Server1,Server2] 和 [Server3, Server4, Server5] 两个 Quorum 各自为营。
@@ -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)
- 图 6-22 穷举集群添加节点的情况
+ 图 6-16 穷举集群添加节点的情况
:::
目前,绝大多数 Raft 算法的实现或系统,如 Hashicrop Raft、Etcd 等都是使用单节点变更方案。联合共识方案由于其复杂性和落地难度笔者就不再过多介绍,有兴趣的读者可以阅读 Raft 论文了解相关内容。
\ No newline at end of file
diff --git a/consensus/raft-leader-election.md b/consensus/raft-leader-election.md
index fbe6a624..34827fd5 100644
--- a/consensus/raft-leader-election.md
+++ b/consensus/raft-leader-election.md
@@ -1,6 +1,8 @@
# 6.4.1 领导者选举
-Raft 是一个强领导者算法,节点间并不平等。不同角色的节点承担不用的职责。
+Paxos 算法中“节点众生平等”,每个节点都可以并行的发起提案。并行的发起提案,是导致活锁、以及其他异常问题的主要原因。那如何不破坏 Paxos 的“节点众生平等”基本原则,又能在提案节点中实现主次之分,限制每个节点都有不受控的提案权利?
+
+Raft 对此的改进是明确领导者角色,增加选举机制分享提案权利。Raft 算法中,节点分为以下三种角色:
- **Leader(领导者)**:负责处理所有客户端请求,将请求转换为“日志”复制到其他节点,确保日志在多数节点(Quorum)上被提交并生效。其次,定期向所有节点发送心跳,维持自己的领导地位。
- **Follower(跟随者)**:接收 Leader 发送的日志条目,确认日志条目的写入情况,并向 Leader 发送响应。
@@ -17,11 +19,10 @@ 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,结束结束当前的选举。
@@ -29,9 +30,10 @@ Raft 是一个强领导者算法,节点间并不平等。不同角色的节点
:::center
![](../assets/raft-election.svg)
- 图 6-16 Raft 选举过程
+ 图 6-12 Raft 选举过程
:::
+使用 Quorum 机制选举出的 Leader 代表了整个集群的意志。现在,你思考:“代表集群意志的 Leader 发起提案,是否还需要 Paxos 第一轮中 “准备阶段” 呢?”。
diff --git a/consensus/raft-log-replication.md b/consensus/raft-log-replication.md
index c6c6664a..bb9a517e 100644
--- a/consensus/raft-log-replication.md
+++ b/consensus/raft-log-replication.md
@@ -1,8 +1,8 @@
# 6.4.2 日志复制
-使用 Quorum 机制选举出的 Leader 代表了整个集群的意志,Leader 顺理其章地承担起“**处理系统发生的所有变更,并将变更复制到所有 Follower 节点**”的职责。
+一旦选出一个公认的 Leader,那 Leader 顺理其章地承担起“**处理系统发生的所有变更,并将变更复制到所有 Follower 节点**”的职责。
-在 Raft 算法中,日志承载着系统所有变更。图 6-17 展示了 Raft 集群的日志模型,每个“日志条目”(log entry)包含索引、任期、指令等关键信息:
+在 Raft 算法中,日志承载着系统所有变更。图 6-13 展示了 Raft 集群的日志模型,每个“日志条目”(log entry)包含索引、任期、指令等关键信息:
- **指令**: 表示客户端请求的具体操作内容,也就是待“状态机”(State Machine)执行的操作。
- **索引值**:日志条目在仓库中的索引值,单调递增。
@@ -10,7 +10,7 @@
:::center
![](../assets/raft-log.svg)
- 图 6-17 Raft 集群的日志模型(x ← 3 代表x赋值为3)
+ 图 6-13 Raft 集群的日志模型(x ← 3 代表x赋值为3)
:::
Raft 通过 RPC 消息将日志复制到各个 Follower 节点,该 RPC 被称为 AppendEntries RPC。其结构大致如下:
@@ -27,7 +27,7 @@ Raft 通过 RPC 消息将日志复制到各个 Follower 节点,该 RPC 被称
}
```
-根据图 6-17,当 Raft 集群收到客户端请求(例如 set x=4)时,日志复制的过程如下:
+根据图 6-14,当 Raft 集群收到客户端请求(例如 set x=4)时,日志复制的过程如下:
- 如果当前节点不是 Leader,节点会将请求转发给 Leader;
- Leader 接收请求后:
@@ -38,7 +38,7 @@ Raft 通过 RPC 消息将日志复制到各个 Follower 节点,该 RPC 被称
:::center
![](../assets/raft-append-entries.svg)
- 图 6-17 日志项概念
+ 图 6-14 日志项概念
:::
Leader 向客户端返回结果,并不意味着日志复制的过程就此结束,Follower 并不知道日志条目是否被大多数节点确认。Raft 的设计是:Leader 通过心跳或下一次日志复制过程中携带 leaderCommit,通知 Follower 已提交日志条目的最高索引。这个设计的把“日志条目达成共识的过程优化成一个阶段”,从而降低客户端请求一半的延迟时间。
diff --git a/consensus/raft.md b/consensus/raft.md
index 4791a082..a900bd3b 100644
--- a/consensus/raft.md
+++ b/consensus/raft.md
@@ -8,7 +8,7 @@ Raft 是由 Re{liable|plicated|dundant} And Fault-Tolerant 组合起来的单词
Raft 出现之前,绝大多数共识系统都是基于 Paxos 或者受其影响。同时,Paxos 也成为教学领域里讲解共识问题时的范例。但不幸的是,Paxos 理解起来非常晦涩。此外,虽然论文中提到了 Multi Paxos,但缺少实现细节的描述。因此,无论是学术界还是工业界普遍对 Paxos 算法感到十分头疼。
-那段时期,虽然所有的共识系统都是从 Paxos 开始的。但工程师们发现:实现过程中很多难以逾越的难题,往往不得已又开发出与 Paxos 完全不一样的算法。这导致 Lamport 的证明并没有太大价值。很长的一段时间内,实际上并没有一个被大众广泛认同的 Paxos 算法。
+那段时期,虽然所有的共识系统都是从 Paxos 开始的。但工程师们发现:实现过程中很多难以逾越的难题,往往不得已又开发出与 Paxos 完全不一样的算法,这导致 Lamport 的证明并没有太大价值。实际上,很长的一段时间内,并没有一个被大众广泛认同的 Paxos 算法。
:::tip Chubby 作者评论 Paxos
@@ -17,7 +17,7 @@ Paxos 算法的理论描述与实际工程实现之间存在巨大鸿沟,最
考虑到共识问题在分布式系统的重要性,同时为了提供一种更易于理解的教学方法,斯坦福大学的学者们决定重新设计一个替代 Paxos 的共识算法,该算法的首要目的是能够被多数人理解。
-2013 年,斯坦福的学者 Diego Ongaro 和 John Ousterhout 发表了论文 《In Search of an Understandable Consensus Algorithm》[^1],提出了 Raft 算法。Raft 论文开篇第一句描述了 Raft 的证明和 Paxos 等价,然后详细描述了算法如何实现,也就是说 Raft 天生就是 Paxos 算法的工程化。
+2013 年,斯坦福大学的学者 Diego Ongaro 和 John Ousterhout 发表了论文 《In Search of an Understandable Consensus Algorithm》[^1],提出了 Raft 算法。Raft 论文开篇第一句描述了 Raft 的证明和 Paxos 等价,内容详细描述了算法如何实现。也就是说,Raft 天生就是 Paxos 算法的工程化。
:::tip 《In Search of an Understandable Consensus Algorithm》开篇
Raft is a consensus algorithm for managing a replicated log. It produces a result **equivalent to (multi-)Paxos, and it is as efficient as Paxos,** but its structure is different from Paxos;
@@ -25,6 +25,6 @@ Raft is a consensus algorithm for managing a replicated log. It produces a resul
此后,Raft 算法成为分布式系统领域的首选共识算法。
-接下来,笔者将以领导者角色、选举机制、日志提交等机制,讲解在 Paxos 难以落地的问题,Raft 算法是如何设计和妥善解决的。
+接下来,笔者将以领导者角色、选举、日志提交等设计机制,讲解 Raft 算法是如何妥善解决分布式系统一致性需求的。
[^1]: 论文参见 https://raft.github.io/raft.pdf
\ No newline at end of file
diff --git a/container/image.md b/container/image.md
index 24bfc0af..50883f90 100644
--- a/container/image.md
+++ b/container/image.md
@@ -4,18 +4,16 @@
## 7.3.1 什么是容器镜像
-所谓的“容器镜像”,其实就是一个特殊的压缩包。
+所谓的“容器镜像”,其实就是一个特殊的压缩包,内含应用程序及其运行所需的全部依赖。容器系统创建一个隔离的“沙盒”环境,在“沙盒”内解压镜像,就可以运行你的应用程序了。
-容器系统创建一个“沙盒”,在“沙盒”内解压镜像,然后就可以运行你的应用程序了。
-
-事实上,大部分开发者对应用依赖的理解通常局限于编程语言层面。例如,某个 Java 应用依赖特定版本的 JDK、某个 Python 程序依赖 Python2.7 等等。但一个极容易忽视的事实是:“操作系统本身才是应用运行所需的最完整的依赖环境”。
+事实上,大部分开发者对应用依赖的理解通常局限于编程语言层面。例如,某个 Java 应用依赖特定版本的 JDK、某个 Python 应用依赖 Python2.7 等等。但一个常被忽视的事实是:“**操作系统本身才是应用运行所需的最完整的依赖环境**”。
所以,你只需提供好操作系统文件与目录,然后使用它制作一个“压缩包”。在 Docker 中,这个操作是:
```bash
$ docker build 镜像名称
```
-一旦镜像制作完成,用户就可以让 Docker 创建一个“沙盒”来解压镜像,得到一个包含了应用程序运行所需的库、资源、配置的目录。然后,调用 chroot 或 pivot_root 命令为应用程序切换到此目录,也就转变为容器内 rootfs(Root Filesystem,根文件系统)文件系统。然后,在“沙盒”内就能运行自己的应用程序。在 Docker 中,这个操作是:
+一旦镜像制作完成,用户就可以让 Docker 创建一个“沙盒”来解压镜像,得到一个包含应用程序运行所需的依赖库、配置的目录。接着,调用 chroot 或 pivot_root 命令,将该目录转变为容器内 rootfs(Root Filesystem,根文件系统)文件系统。然后,在“沙盒”内就能运行自己的应用程序。在 Docker 中,这个操作是:
```bash
$ docker run 镜像名称
diff --git a/http/latency.md b/http/latency.md
index 062310e7..081f907c 100644
--- a/http/latency.md
+++ b/http/latency.md
@@ -72,9 +72,9 @@ time_total=0.088744
- -o /dev/null:把响应的内容丢弃。我们并不关心 HTTPS 的返回内容,只关心请求的耗时情况。
- -s:不输出请求的进度条。
-不过,得注意 curl 打印的各个耗时都是从请求发起的那一刻开始计算,我们得将其转换成 HTTPS 各阶段耗时,例如域名解析耗时、TCP 建立耗时、TTFB 耗时[^3]等。
+不过,得注意 curl 打印的耗时数据都是从请求发起的那一刻开始计算的,我们得将其重新转换成 HTTPS 请求各个阶段耗时。例如,转换成域名解析耗时、TCP 建立耗时、TTFB 耗时[^3]等。
-表 2-3 为 curl 内各个步骤耗时与 HTTPS 指标计算关系。
+表 2-3 为 curl 内各个步骤耗时与 HTTPS 各阶段耗时转换关系。
:::center
表 2-3 HTTPS 请求各阶段耗时计算