mirror of
https://github.com/CyC2018/Interview-Notebook.git
synced 2026-10-24 02:03:41 +00:00
co-authored by
Claude Fable 5
parent
b70121d377
commit
88b5227bb7
+132
-55
@@ -3,11 +3,14 @@
|
||||
* [分布式](#分布式)
|
||||
* [一、分布式锁](#一分布式锁)
|
||||
* [数据库的唯一索引](#数据库的唯一索引)
|
||||
* [Redis 的 SETNX 指令](#redis-的-setnx-指令)
|
||||
* [Redis 的 SET NX 指令](#redis-的-set-nx-指令)
|
||||
* [Redis 的 RedLock 算法](#redis-的-redlock-算法)
|
||||
* [Zookeeper 的有序节点](#zookeeper-的有序节点)
|
||||
* [三种实现的对比](#三种实现的对比)
|
||||
* [二、分布式事务](#二分布式事务)
|
||||
* [2PC](#2pc)
|
||||
* [TCC](#tcc)
|
||||
* [Saga](#saga)
|
||||
* [本地消息表](#本地消息表)
|
||||
* [三、CAP](#三cap)
|
||||
* [一致性](#一致性)
|
||||
@@ -24,7 +27,7 @@
|
||||
* [六、Raft](#六raft)
|
||||
* [单个 Candidate 的竞选](#单个-candidate-的竞选)
|
||||
* [多个 Candidate 竞选](#多个-candidate-竞选)
|
||||
* [数据同步](#数据同步)
|
||||
* [日志复制](#日志复制)
|
||||
* [参考](#参考)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
@@ -46,26 +49,46 @@
|
||||
|
||||
存在以下几个问题:
|
||||
|
||||
- 锁没有失效时间,解锁失败的话其它进程无法再获得该锁;
|
||||
- 锁没有失效时间,如果持有锁的进程崩溃导致解锁失败,其它进程就无法再获得该锁;
|
||||
- 只能是非阻塞锁,插入失败直接就报错了,无法重试;
|
||||
- 不可重入,已经获得锁的进程也必须重新获取锁。
|
||||
|
||||
### Redis 的 SETNX 指令
|
||||
### Redis 的 SET NX 指令
|
||||
|
||||
使用 SETNX(set if not exist)指令插入一个键值对,如果 Key 已经存在,那么会返回 False,否则插入成功并返回 True。
|
||||
SETNX(set if not exist)指令插入一个键值对,如果 Key 已经存在,那么插入失败并返回 0,否则插入成功并返回 1。它和数据库的唯一索引类似,保证了只存在一个 Key 的键值对,那么可以用一个 Key 的键值对是否存在来判断是否处于锁定状态。
|
||||
|
||||
SETNX 指令和数据库的唯一索引类似,保证了只存在一个 Key 的键值对,那么可以用一个 Key 的键值对是否存在来判断是否存于锁定状态。
|
||||
EXPIRE 指令可以为一个键值对设置一个过期时间,从而避免了释放锁失败的问题。但是 SETNX 和 EXPIRE 是两条指令,不具有原子性,如果执行完 SETNX 之后进程崩溃,那么这个锁就永远不会过期。因此应该使用 SET 指令的 NX 和 PX 选项一次性完成加锁和设置过期时间:
|
||||
|
||||
EXPIRE 指令可以为一个键值对设置一个过期时间,从而避免了数据库唯一索引实现方式中释放锁失败的问题。
|
||||
```
|
||||
SET lock_key unique_value NX PX 30000
|
||||
```
|
||||
|
||||
还需要注意以下几点:
|
||||
|
||||
- 释放锁:value 设置为客户端的唯一标识,释放锁时先判断 value 是否为自己设置的再删除,避免误删其它客户端的锁,判断和删除需要使用 Lua 脚本保证原子性;
|
||||
- 锁续期:如果业务执行时间超过了过期时间,锁会被提前释放,可以使用后台线程定时延长过期时间,例如 Redisson 的看门狗机制默认每 10 秒把过期时间重置为 30 秒;
|
||||
- 主从切换:Redis 的主从复制是异步的,如果主节点加锁后还未同步到从节点就宕机,从节点晋升为主节点之后锁就丢失了。
|
||||
|
||||
```lua
|
||||
if redis.call("get", KEYS[1]) == ARGV[1] then
|
||||
return redis.call("del", KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end
|
||||
```
|
||||
|
||||
Redis 8.4 新增了 `DELEX lock_key IFEQ unique_value` 指令,可以直接原子地完成比较并删除的操作,不再需要 Lua 脚本。
|
||||
|
||||
### Redis 的 RedLock 算法
|
||||
|
||||
使用了多个 Redis 实例来实现分布式锁,这是为了保证在发生单点故障时仍然可用。
|
||||
|
||||
- 尝试从 N 个互相独立 Redis 实例获取锁;
|
||||
- 依次尝试从 N 个互相独立的 Redis 实例获取锁,每次请求的超时时间远小于锁的过期时间;
|
||||
- 计算获取锁消耗的时间,只有时间小于锁的过期时间,并且从大多数(N / 2 + 1)实例上获取了锁,才认为获取锁成功;
|
||||
- 如果获取锁失败,就到每个实例上释放锁。
|
||||
|
||||
RedLock 依赖各个节点的时钟基本同步,并且无法应对进程暂停(例如 GC)导致锁过期后客户端仍然认为自己持有锁的情况,因此存在争议。更可靠的做法是让锁服务返回一个单调递增的令牌(Fencing Token),被访问的资源拒绝令牌更小的请求。
|
||||
|
||||
### Zookeeper 的有序节点
|
||||
|
||||
#### 1. Zookeeper 抽象模型
|
||||
@@ -88,16 +111,26 @@ Zookeeper 提供了一种树形结构的命名空间,/app1/p_1 节点的父节
|
||||
|
||||
- 创建一个锁目录 /lock;
|
||||
- 当一个客户端需要获取锁时,在 /lock 下创建临时的且有序的子节点;
|
||||
- 客户端获取 /lock 下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁;否则监听自己的前一个子节点,获得子节点的变更通知后重复此步骤直至获得锁;
|
||||
- 客户端获取 /lock 下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁;否则监听自己的前一个子节点,获得子节点的变更通知后重复此步骤直至获得锁;
|
||||
- 执行业务代码,完成后,删除对应的子节点。
|
||||
|
||||
#### 5. 会话超时
|
||||
|
||||
如果一个已经获得锁的会话超时了,因为创建的是临时节点,所以该会话对应的临时节点会被删除,其它会话就可以获得锁了。可以看到,这种实现方式不会出现数据库的唯一索引实现方式释放锁失败的问题。
|
||||
|
||||
但是如果客户端因为 GC 等原因长时间没有发送心跳,也会导致会话超时,因此同样可能出现两个客户端都认为自己持有锁的情况。
|
||||
|
||||
#### 6. 羊群效应
|
||||
|
||||
一个节点未获得锁,只需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应,一只羊动起来,其它羊也会一哄而上),而我们只希望它的后一个子节点收到通知。
|
||||
一个节点未获得锁,只需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应,一只羊动起来,其它羊也会一哄而上),而实际上只需要通知它的后一个子节点。
|
||||
|
||||
### 三种实现的对比
|
||||
|
||||
- 数据库:实现简单,但是性能低;
|
||||
- Redis:性能最高,适合高并发场景,但是在主从切换、进程暂停等极端情况下可能出现两个客户端同时持有锁;
|
||||
- Zookeeper:写操作需要同步到大多数节点,性能较低,但是可靠性最高,适合一致性要求高、并发量不大的场景。
|
||||
|
||||
实际使用中一般直接采用 Redisson、Curator 等成熟的客户端实现。
|
||||
|
||||
## 二、分布式事务
|
||||
|
||||
@@ -126,7 +159,7 @@ Zookeeper 提供了一种树形结构的命名空间,/app1/p_1 节点的父节
|
||||
|
||||
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
|
||||
|
||||
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
|
||||
需要注意的是,在准备阶段,参与者执行了事务(写入了 Undo 和 Redo 日志,并且持有锁),但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/d2ae9932-e2b1-4191-8ee9-e573f36d3895.png" width="550px"> </div><br>
|
||||
|
||||
@@ -134,7 +167,7 @@ Zookeeper 提供了一种树形结构的命名空间,/app1/p_1 节点的父节
|
||||
|
||||
##### 2.1 同步阻塞
|
||||
|
||||
所有事务参与者在等待其它参与者响应的时候都处于同步阻塞等待状态,无法进行其它操作。
|
||||
所有事务参与者在等待其它参与者响应的时候都处于同步阻塞等待状态,并且一直持有锁,无法进行其它操作。
|
||||
|
||||
##### 2.2 单点问题
|
||||
|
||||
@@ -148,16 +181,46 @@ Zookeeper 提供了一种树形结构的命名空间,/app1/p_1 节点的父节
|
||||
|
||||
任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
|
||||
|
||||
三阶段提交(3PC)在 2PC 的基础上增加了一个询问阶段(CanCommit),并且在参与者端也引入了超时机制,参与者在等待提交通知超时后会默认提交,从而减少了阻塞,但是仍然可能出现数据不一致。
|
||||
|
||||
### TCC
|
||||
|
||||
TCC(Try-Confirm-Cancel)是在业务层面实现的两阶段提交,每个操作都需要实现三个接口:
|
||||
|
||||
- Try:检查业务并预留资源,例如冻结库存;
|
||||
- Confirm:使用预留的资源真正执行业务,例如扣减冻结的库存;
|
||||
- Cancel:释放预留的资源,例如解冻库存。
|
||||
|
||||
如果所有参与者的 Try 都执行成功,就执行 Confirm,否则执行 Cancel。Confirm 和 Cancel 失败后都需要重试,因此必须实现幂等。
|
||||
|
||||
相比于 2PC,TCC 不需要长时间持有数据库的锁,性能更好,但是对业务的侵入性较强。
|
||||
|
||||
实现时还需要处理两个问题:
|
||||
|
||||
- 空回滚:Try 没有执行,协调者超时后就调用了 Cancel,Cancel 需要识别出这种情况并直接返回成功;
|
||||
- 悬挂:Cancel 执行之后,被延迟的 Try 才到达并预留了资源,这些资源再也不会被释放,因此 Try 执行前需要检查该事务是否已经被 Cancel。
|
||||
|
||||
通常用一张事务控制表记录每个事务的执行状态来解决这两个问题,幂等也可以通过它实现。
|
||||
|
||||
### Saga
|
||||
|
||||
Saga 将一个分布式事务拆分为多个本地事务,每个本地事务直接提交,并且都有一个对应的补偿操作。依次执行各个本地事务,如果某一个失败,就逆序执行前面已经提交的本地事务的补偿操作。
|
||||
|
||||
Saga 没有预留资源的 Try 阶段,中间状态对其它事务可见,因此没有隔离性。它适合流程长、无法长时间锁定资源的业务,正向操作和补偿操作同样需要实现幂等。
|
||||
|
||||
### 本地消息表
|
||||
|
||||
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
|
||||
|
||||
1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
|
||||
2. 之后将本地消息表中的消息转发到消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
|
||||
2. 之后将本地消息表中的消息转发到消息队列中,如果转发成功则将消息从本地消息表中删除(或者标记为已发送),否则继续重新转发。
|
||||
3. 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/476329d4-e2ef-4f7b-8ac9-a52a6f784600.png" width="740px"> </div><br>
|
||||
|
||||
由于重新转发会导致消息被重复投递,因此另一方执行的操作需要实现幂等。并且第一方的事务已经提交,无法回滚,所以如果另一方执行失败,只能不断重试或者人工介入处理。
|
||||
|
||||
RocketMQ 的事务消息把本地消息表的工作交给了消息队列:生产者先发送一条对消费者不可见的半消息,再执行本地事务,然后根据执行结果通知 Broker 提交或者回滚这条消息。如果 Broker 长时间没有收到通知,会主动回查生产者的本地事务状态。
|
||||
|
||||
## 三、CAP
|
||||
|
||||
@@ -167,30 +230,32 @@ Zookeeper 提供了一种树形结构的命名空间,/app1/p_1 节点的父节
|
||||
|
||||
### 一致性
|
||||
|
||||
一致性指的是多个数据副本是否能保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。
|
||||
CAP 中的一致性指的是强一致性(线性一致性):对系统的一个数据更新成功之后,所有用户都能够读取到最新的值,就像系统中只有一个数据副本一样。
|
||||
|
||||
对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。
|
||||
注意它和 ACID 中的一致性不是同一个概念,ACID 中的一致性指的是事务执行前后,数据都满足完整性约束。
|
||||
|
||||
### 可用性
|
||||
|
||||
可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99% 的时间是可用的。
|
||||
可用性指的是每一个发送给未故障节点的请求,都能够在有限的时间内得到非错误的响应,但是不保证响应的是最新的数据。
|
||||
|
||||
在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
|
||||
平时所说的 4 个 9 的可用性(系统 99.99% 的时间是可用的),是衡量系统可用程度的指标,和 CAP 中的可用性并不完全相同。
|
||||
|
||||
### 分区容忍性
|
||||
|
||||
网络分区指分布式系统中的节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信。
|
||||
|
||||
在分区容忍性条件下,分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障。
|
||||
分区容忍性指的是在出现网络分区,也就是节点之间的消息丢失或者延迟的情况下,系统仍然能够继续运行。
|
||||
|
||||
### 权衡
|
||||
|
||||
在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP 理论实际上是要在可用性和一致性之间做权衡。
|
||||
在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP 理论实际上是在发生网络分区时,要在可用性和一致性之间做权衡。而在没有发生网络分区时,一致性和可用性是可以同时满足的。
|
||||
|
||||
可用性和一致性往往是冲突的,很难使它们同时满足。在多个节点之间进行数据同步时,
|
||||
例如两个节点之间发生分区后,一个节点完成了写入,另一个节点收到读请求时,要么返回旧数据(放弃一致性),要么拒绝响应(放弃可用性),不存在第三种选择。
|
||||
|
||||
- 为了保证一致性(CP),不能访问未同步完成的节点,也就失去了部分可用性;
|
||||
- 为了保证可用性(AP),允许读取所有节点的数据,但是数据可能不一致。
|
||||
- 为了保证一致性(CP),无法和其它节点同步的节点需要拒绝请求,也就失去了部分可用性,例如 Zookeeper、etcd;
|
||||
- 为了保证可用性(AP),所有节点都继续提供服务,但是读取到的数据可能不一致,例如 Eureka、Cassandra。
|
||||
|
||||
PACELC 是对 CAP 的扩展:发生分区(P)时在可用性(A)和一致性(C)之间权衡,没有分区(E)时则在延迟(L)和一致性(C)之间权衡,因为保证强一致性需要等待节点之间同步数据,会增加响应延迟。
|
||||
|
||||
## 四、BASE
|
||||
|
||||
@@ -198,7 +263,6 @@ BASE 是基本可用(Basically Available)、软状态(Soft State)和最
|
||||
|
||||
BASE 理论是对 CAP 中一致性和可用性权衡的结果,它的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
|
||||
|
||||
|
||||
### 基本可用
|
||||
|
||||
指分布式系统在出现故障的时候,保证核心可用,允许损失部分可用性。
|
||||
@@ -219,53 +283,52 @@ ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE
|
||||
|
||||
## 五、Paxos
|
||||
|
||||
用于达成共识性问题,即对多个节点产生的值,该算法能保证只选出唯一一个值。
|
||||
用于解决共识问题,即让多个节点对某个值达成一致。该算法能保证只会选出唯一一个值,并且一旦选定就不会再改变。
|
||||
|
||||
主要有三类节点:
|
||||
|
||||
- 提议者(Proposer):提议一个值;
|
||||
- 接受者(Acceptor):对每个提议进行投票;
|
||||
- 告知者(Learner):被告知投票的结果,不参与投票过程。
|
||||
- 学习者(Learner):被告知投票的结果,不参与投票过程。
|
||||
|
||||
一个节点可以同时担任多种角色。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b988877c-0f0a-4593-916d-de2081320628.jpg"/> </div><br>
|
||||
|
||||
### 执行过程
|
||||
|
||||
规定一个提议包含两个字段:[n, v],其中 n 为序号(具有唯一性),v 为提议值。
|
||||
规定一个提议包含两个字段:[n, v],其中 n 为序号(全局唯一并且递增),v 为提议值。
|
||||
|
||||
#### 1. Prepare 阶段
|
||||
|
||||
下图演示了两个 Proposer 和三个 Acceptor 的系统中运行该算法的初始过程,每个 Proposer 都会向所有 Acceptor 发送 Prepare 请求。
|
||||
Proposer 选择一个序号 n,向所有 Acceptor 发送 Prepare(n) 请求,该请求不需要包含提议值。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1a9977e4-2f5c-49a6-aec9-f3027c9f46a7.png"/> </div><br>
|
||||
Acceptor 接收到 Prepare(n) 请求时:
|
||||
|
||||
当 Acceptor 接收到一个 Prepare 请求,包含的提议为 [n1, v1],并且之前还未接收过 Prepare 请求,那么发送一个 Prepare 响应,设置当前接收到的提议为 [n1, v1],并且保证以后不会再接受序号小于 n1 的提议。
|
||||
|
||||
如下图,Acceptor X 在收到 [n=2, v=8] 的 Prepare 请求时,由于之前没有接收过提议,因此就发送一个 [no previous] 的 Prepare 响应,设置当前接收到的提议为 [n=2, v=8],并且保证以后不会再接受序号小于 2 的提议。其它的 Acceptor 类似。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/fb44307f-8e98-4ff7-a918-31dacfa564b4.jpg"/> </div><br>
|
||||
|
||||
如果 Acceptor 接收到一个 Prepare 请求,包含的提议为 [n2, v2],并且之前已经接收过提议 [n1, v1]。如果 n1 \> n2,那么就丢弃该提议请求;否则,发送 Prepare 响应,该 Prepare 响应包含之前已经接收过的提议 [n1, v1],设置当前接收到的提议为 [n2, v2],并且保证以后不会再接受序号小于 n2 的提议。
|
||||
|
||||
如下图,Acceptor Z 收到 Proposer A 发来的 [n=2, v=8] 的 Prepare 请求,由于之前已经接收过 [n=4, v=5] 的提议,并且 n \> 2,因此就抛弃该提议请求;Acceptor X 收到 Proposer B 发来的 [n=4, v=5] 的 Prepare 请求,因为之前接收到的提议为 [n=2, v=8],并且 2 \<= 4,因此就发送 [n=2, v=8] 的 Prepare 响应,设置当前接收到的提议为 [n=4, v=5],并且保证以后不会再接受序号小于 4 的提议。Acceptor Y 类似。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2bcc58ad-bf7f-485c-89b5-e7cafc211ce2.jpg"/> </div><br>
|
||||
- 如果 n 大于它之前响应过的所有 Prepare 请求的序号,那么就承诺以后不会再接受序号小于 n 的提议,并发送 Prepare 响应。如果它之前已经接受(Accept)过提议,那么响应中需要包含其中序号最大的那个提议;
|
||||
- 否则,丢弃该请求。
|
||||
|
||||
#### 2. Accept 阶段
|
||||
|
||||
当一个 Proposer 接收到超过一半 Acceptor 的 Prepare 响应时,就可以发送 Accept 请求。
|
||||
当一个 Proposer 接收到超过一半 Acceptor 的 Prepare 响应时,就可以发送 Accept(n, v) 请求,其中 v 的取值规则为:
|
||||
|
||||
Proposer A 接收到两个 Prepare 响应之后,就发送 [n=2, v=8] Accept 请求。该 Accept 请求会被所有 Acceptor 丢弃,因为此时所有 Acceptor 都保证不接受序号小于 4 的提议。
|
||||
- 如果响应中包含已经被接受过的提议,那么 v 取其中序号最大的提议对应的值;
|
||||
- 否则,v 可以取 Proposer 自己的提议值。
|
||||
|
||||
Proposer B 过后也收到了两个 Prepare 响应,因此也开始发送 Accept 请求。需要注意的是,Accept 请求的 v 需要取它收到的最大提议编号对应的 v 值,也就是 8。因此它发送 [n=4, v=8] 的 Accept 请求。
|
||||
Acceptor 接收到 Accept(n, v) 请求时,只要它没有对序号大于 n 的 Prepare 请求做过承诺,就接受该提议。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/9b838aee-0996-44a5-9b0f-3d1e3e2f5100.png"/> </div><br>
|
||||
如果 Proposer 没有得到大多数 Acceptor 的接受,就使用更大的序号重新开始 Prepare 阶段。
|
||||
|
||||
#### 3. Learn 阶段
|
||||
|
||||
Acceptor 接收到 Accept 请求时,如果序号大于等于该 Acceptor 承诺的最小序号,那么就发送 Learn 提议给所有的 Learner。当 Learner 发现有大多数的 Acceptor 接收了某个提议,那么该提议的提议值就被 Paxos 选择出来。
|
||||
Acceptor 接受一个提议之后,会将该提议发送给所有的 Learner。当 Learner 发现有大多数的 Acceptor 接受了同一个提议,那么该提议的提议值就被 Paxos 选择出来。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/bf667594-bb4b-4634-bf9b-0596a45415ba.jpg"/> </div><br>
|
||||
#### 4. 示例
|
||||
|
||||
考虑两个 Proposer A、B 和三个 Acceptor X、Y、Z:
|
||||
|
||||
- A 发送 Prepare(2),X 和 Y 做出承诺并响应。A 得到大多数响应之后发送 Accept(2, 8),X 和 Y 接受该提议,此时值 8 已经被选定;
|
||||
- 之后 B 发送 Prepare(4),它收到的大多数响应中至少有一个来自 X 或 Y,响应中包含已经接受过的提议 [2, 8],因此 B 只能发送 Accept(4, 8),而不能使用自己的提议值,从而保证了选定的值不会改变。
|
||||
|
||||
### 约束条件
|
||||
|
||||
@@ -273,23 +336,25 @@ Acceptor 接收到 Accept 请求时,如果序号大于等于该 Acceptor 承
|
||||
|
||||
指只有一个提议值会生效。
|
||||
|
||||
因为 Paxos 协议要求每个生效的提议被多数 Acceptor 接收,并且 Acceptor 不会接受两个不同的提议,因此可以保证正确性。
|
||||
任意两个大多数集合之间至少有一个公共的 Acceptor。一旦某个提议值被大多数 Acceptor 接受,之后的任何 Proposer 在 Prepare 阶段都一定会从这个公共的 Acceptor 得知该值,从而只能继续提议该值,因此可以保证正确性。
|
||||
|
||||
#### 2\. 可终止性
|
||||
|
||||
指最后总会有一个提议生效。
|
||||
|
||||
Paxos 协议能够让 Proposer 发送的提议朝着能被大多数 Acceptor 接受的那个提议靠拢,因此能够保证可终止性。
|
||||
Paxos 并不能严格保证可终止性:如果两个 Proposer 交替地使用更大的序号发送 Prepare 请求,那么双方的 Accept 请求都会一直被拒绝,形成活锁。实际中通常会选出一个主 Proposer,只由它来发起提议(Multi-Paxos 的做法),或者在重试前随机等待一段时间。
|
||||
|
||||
## 六、Raft
|
||||
|
||||
Raft 也是分布式一致性协议,主要是用来竞选主节点。
|
||||
Raft 也是分布式共识算法,相比于 Paxos 更容易理解和实现。它将共识问题分解为 Leader 选举、日志复制和安全性三个子问题,etcd、Consul 等系统都使用了 Raft。
|
||||
|
||||
- [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft)
|
||||
|
||||
### 单个 Candidate 的竞选
|
||||
|
||||
有三种节点:Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms,如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate,进入竞选阶段。
|
||||
有三种节点:Follower、Candidate 和 Leader。Raft 把时间划分为多个任期(Term),任期号单调递增,每个任期从一次选举开始。
|
||||
|
||||
Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms,如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate,将任期号加 1 并给自己投一票,进入竞选阶段。
|
||||
|
||||
- 下图展示一个分布式系统的最初阶段,此时只有 Follower 没有 Leader。Node A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。
|
||||
|
||||
@@ -299,7 +364,7 @@ Raft 也是分布式一致性协议,主要是用来竞选主节点。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/111521118445538.gif"/> </div><br>
|
||||
|
||||
- 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
|
||||
- 每个节点在一个任期内最多只能投一票,先到先得,并且只会投给日志至少和自己一样新的 Candidate,这保证了新的 Leader 一定包含所有已提交的日志。如果 Candidate 获得超过一半节点的选票,就会变成 Leader。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/111521118483039.gif"/> </div><br>
|
||||
|
||||
@@ -309,7 +374,7 @@ Raft 也是分布式一致性协议,主要是用来竞选主节点。
|
||||
|
||||
### 多个 Candidate 竞选
|
||||
|
||||
- 如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票。例如下图中 Node B 和 Node D 都获得两票,需要重新开始投票。
|
||||
- 如果有多个 Follower 同时成为 Candidate,选票可能会被瓜分,导致没有 Candidate 获得超过一半的选票,那么等待竞选超时之后进入下一个任期重新开始投票。例如下图中 Node B 和 Node D 都获得两票,需要重新开始投票。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/111521119203347.gif"/> </div><br>
|
||||
|
||||
@@ -317,7 +382,9 @@ Raft 也是分布式一致性协议,主要是用来竞选主节点。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/111521119368714.gif"/> </div><br>
|
||||
|
||||
### 数据同步
|
||||
- 如果 Candidate 在竞选过程中收到了任期号不小于自己的 Leader 发来的心跳包,就会承认该 Leader 并变回 Follower。
|
||||
|
||||
### 日志复制
|
||||
|
||||
- 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。
|
||||
|
||||
@@ -327,24 +394,34 @@ Raft 也是分布式一致性协议,主要是用来竞选主节点。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/91550414131331.gif"/> </div><br>
|
||||
|
||||
- Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。
|
||||
- Leader 会等待大多数的节点都写入了该日志,然后才将修改提交,并向客户端返回结果。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/101550414151983.gif"/> </div><br>
|
||||
|
||||
- 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
|
||||
- 此时 Leader 会在之后的心跳包中通知所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
|
||||
|
||||
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/111550414182638.gif"/> </div><br>
|
||||
|
||||
如果 Follower 的日志和 Leader 不一致,Leader 会找到两者最后一致的位置,然后用自己的日志覆盖 Follower 在该位置之后的日志。
|
||||
|
||||
发生网络分区时,少数派一侧的旧 Leader 得不到大多数节点的确认,无法提交日志,多数派一侧会选出新的 Leader。分区恢复后,旧 Leader 发现更大的任期号就会退回 Follower,它未提交的日志被覆盖,因此不会出现脑裂。
|
||||
|
||||
## 参考
|
||||
|
||||
- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015.
|
||||
- [Distributed locks with Redis](https://redis.io/topics/distlock)
|
||||
- [How to do distributed locking](https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html)
|
||||
- [Locks and synchronizers - Redisson](https://redisson.pro/docs/data-and-services/locks-and-synchronizers/)
|
||||
- [深度剖析 Seata TCC 模式](https://www.seata.io/zh-cn/blog/seata-tcc/)
|
||||
- [事务消息 | RocketMQ](https://rocketmq.apache.org/zh/docs/featureBehavior/04transactionmessage/)
|
||||
- [An Illustrated Proof of the CAP Theorem](https://mwhittaker.github.io/blog/an_illustrated_proof_of_the_cap_theorem/)
|
||||
- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023)
|
||||
- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/10/23/zookeeper-distributed-lock.html)
|
||||
- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html)
|
||||
- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html)
|
||||
- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be)
|
||||
- [What is CAP theorem in distributed database system?](http://www.colooshiki.com/index.php/2017/04/20/what-is-cap-theorem-in-distributed-database-system/)
|
||||
- [Paxos Made Simple](https://lamport.azurewebsites.net/pubs/paxos-simple.pdf)
|
||||
- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/)
|
||||
- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/)
|
||||
- [In Search of an Understandable Consensus Algorithm](https://raft.github.io/raft.pdf)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user