这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天
本堂课重点内容:
- 概述
- 系统模型
- 理论基础
- 分布式事务
- 共识协议
- 分布式实践
1.分布式概述
1.1 什么是分布式?
分布式系统定义:
跨多个节点的计算机程序的集合,这些程序利用跨多个独立计算节点的计算资源来实共同的目标。可以分为分布式计算、分布式存储、分布式数据库等。
- 使用分布式系统的五大优势:去中心化、低成本、弹性、资源共享、可靠性高
- 分布式系统的挑战:故障、网络、环境、安全
1.2 Why-How-What
1.2.1 使用者视角:大规模计算存储的述求
| Why | How | What |
|---|---|---|
| 1.数据爆炸,对存储和计算有大规模运用的述求 | 1.分布式框架 | 1.理清规模、负载、一致性要求等 |
| 2.成本低,构建在廉价服务器之上 | 2.成熟的分布式系统 | 2.明确稳定性要求,指定技术方案 |
1.2.2 学习者视角:后端开发必备技能
| Why | How | What |
|---|---|---|
| 1.后端开发必备技能 | 1.掌握分布式理论 | 1.把要点深入展开,针对难点搜索互联网资料进行学习 |
| 2.帮助理解后台服务器之间协作的机理 | 2.了解一致性协议 | 2.将所学知识运用于实践 |
1.3 常见的分布式系统
1.3.1 分布式存储:GFS、Ceph、HDFS、Zookeeper
1.Google File System (GFS) : google 分布式文件系统 2.Ceph : 统一的分布式存储系统 3.Hadoop HDFS : 基于 GFS 架构的开源分布式文件系统 4.Zookeeper : 高可用的分布式数据管理与系统协调框架
1.3.2 分布式数据库:Spanner、TiDB、HBase、MangoDB
1.Google Spanner : google 可扩展的、全球分布式数据库
2.TiDB : 开源分布式关系型数据库
3.HBase : 开源 Nosql 数据库
4.MangoDB : 文档数据库
1.3.3 分布式计算:Hadoop、YARN、Spark
1.Hadoop : 基于 MapReduce 分布式计算框架
2.Spark : 在 Hadoop 基础之上,使用内存来存储数据
3.YARN : 分布式资源调度
2.系统模型
2.1 故障模型
六种故障模型,从处理的难易程度分类
| 模型 | 描述 |
|---|---|
| Byzantine failure | 节点可以任意篡改发送给其他节点的数据,是最难处理的故障 |
| Authentication detectable byzantine failure (ADB) | 节点可以篡改数据,但不能伪造其他节点的数据(Byzantine failure的特例) |
| Performance failure | 节点未在特定时间段内收到数据,即时间太早或太晚 |
| Omission failure | 节点收到数据的时间无限晚,即收不到数据 |
| Crash failure | 节点停止响应,持续性的故障(在 Omission failure 的基础上,增加了节点停止响应的假设,也即持续性地 Omission failure) |
| Fail-stop failure | 错误可检测,是最容易处理的故障(在 Crash failure 的基础上增加了错误可检测的假设) |
故障模型举例,按照模型分类
- 磁盘、主板、交换机、网络分区、cpu、内存、线缆、电源等故障详细说明
| 故障 | 描述 | 可能的类型 |
|---|---|---|
| 磁盘故障 | 如:磁头不寻道、盘片不转、磁介质损伤等。年发生率1-2% | Fail-stop |
| 磁盘坏道、坏块 | 磁头划伤引起坏道,或受宇宙射线影响晶体管产生位反转 | Fail-stop,ADB |
| 服务器主板、板卡故障 | 可能是风扇故障,或灰尘引起的短路,或SCSI/RAID卡造成的死机 | Crash |
| 网络故障 | 电源故障、背板故障等,网卡位反转、网络流量大造成大量丢包等 | Byzantine,Omission |
| 网络分区 | 网络引起节点形成不同的子集,子集中网络相通,子集间网络不通 | Performance |
| 内存故障 | 内存出错造成的数据被篡改,分为 UE、CE 两种 | ADB |
| 线缆故障 | 服务器光模块频繁 up 或 down | Performance,Omission |
| 内核崩溃 | 内核内部的致命错误,产生的 kernel panic | Crash |
| CPU故障 | 年故障率接近1% | Omission、Crash |
| 电源故障 | 服务器失去电力支撑 | Omission |
| 软件故障 | 如:进程crash、内存踩坏、状态不一致、配置错误、软件bug等 | Byzantine,Crash等 |
2.2 拜占庭将军问题
2.2.1 两将军问题(Two Generals' Problem):
定义:
- 两支军队的将军只能派信使穿越敌方领土互相通信,以此约定进攻时间。该问题希望求解如何在两名将军派出的任何信使都可能被俘虏的情况下,就进攻时间达成共识。(来自 wikipedia)
结论:
- 两将军问题是被证实无解的电脑通信问题,两支军队理论上永远无法达成共识
原因:
- 方案一:同时发送N个信使,任何一个达到对方军队,都算成功。
- 方案二:设置超时时间,发送后未在一定时间返回,则加派信使。
- 共识与消息传递的不同:即使保证了消息传递成功,也不能保证达成共识
TCP 三次握手是在两个方向确认包的序列号, 增加了超时重试, 是两将军问题的一个工程解。
思考:
- 为何三次握手?而不是两次和四次?
- 挥手过程中,如果 FIN 报文丢失,发生什么?
2.2.2 三将军问题:
拜占庭将军考虑更加普适的场景,例如 3 个将军 A、B、C 互相传递消息,消息可能丢失,也可能被篡改,当有一个将军是“叛徒”(即出现拜占庭故障)时,整个系统无法达成一致。
如果没有“叛徒”,无论各自观察到怎样的敌情,总能达成一致的行动。
由于“叛徒” C 的存在,将军 A 和将军 B 获得不同的信息。这样将军 A 获得 2 票进攻 1 票撤退的信息,将军 B 获得 1 票进攻 2 票撤退的信息,产生了不一致。
2.2.3 四将军问题:
考虑当4个将军,只有1个叛徒的场景。
将军D作为消息分发中枢,约定如果没收到消息则执行撤退。
- 如果 D 为“叛徒”,A、B、C 无论收到任何消息,总能达成一致
- D 为“忠将”,A、B、C 有 2 人将 D 的消息进行正确的传递,同样能保证最终决策符合大多数。
进而能够证明,当有 3m+1 个将军,其中 m 个“叛徒”时,可以增加 m 轮协商,最终达成一致
2.3 共识和一致性
读请求和写请求并发时可能读到旧值:
客户端 A 读到 x = 0,当客户端 C 正在写入时,客户端 A 和 B 可能读到 0 或者 1。但是当 C 写入完成后,A 和 B 最终能读到一致的数据。 我们称这样的一致性为 Eventually consistent(最终一致性)
一旦某个读获取到新值,所有客户端都必须返回新值:
当客户端 A 读到更新的版本 x = 1 后,及时将消息同步给其他客户端,这样其他客户端立即能获取到 x = 1 。我们称这样的一致性为 Linearizability(线性一致性)
- 不同客户端 A 和 B 看到客户端C写入,因为时机的不同,产生数据读取的偏差。引导出最终一致性的详细说明
- 要保证所有客户端看到相同的值,需要多节点进行“协商”,达成共识,来保证线性一致性
- 一致性和可用性是对矛盾
2.4 时间和事件顺序
1978年Leslie Lamport发表《Time, Clocks, and the Ordering of Events in a Distributed System》
定义了计算机系统中的时间和事件顺序,引入happened before和并发的定义,可以以此对分布式系统中的事件进行推导
我们定义 “happened before” 关系,记为 "→" 。其满足如下三个条件:
- 如果 a 和 b 是在相同节点上的两个事件,a 在 b 之前发生,则定义:a → b
- 如果事件 a 表示某个节点发送某条消息,b 是另一个节点接受这条消息,则有 a → b
- 如果有 a → b 且 b → c ,则有 a → c
当且仅当 a↛b 且 b↛a 时,我们称两个事件为并发的(concurrent)。
我们不难在图中找到若干满足条件的事件对,例如 p1 → r4,其由 p1 → q2 → q4 → r3 → r4 推导而来
根据上述推导,创造了 Lamport 逻辑时钟的概念,这个概念在分布式理论中具有革命性的意义,帮助我们在一系列分布式事件当中梳理出逻辑的先后关系。利用逻辑时钟,我们可以对整个系统中的事件进行全序排序
Lamport逻辑时钟
对于每一个节点 Pi 我们定义时钟 Ci 为一个函数,它为任意的事件 a 赋值编号为 Ci(a)
- 如果 a 和 b 是在相同节点 Pi 上的两个事件,a 在 b 之前发生,则有 Ci⟨a⟩ < Ci⟨b⟩
- 如果事件 a 表示节点 Pi 发送某条消息,b 表示节点 Pj 接受这条消息,则有 Ci(a) < Cj(b⟩
- 于是我们可以在时空图中加入类似上图虚线所示的 “tick line”
- 在同一节点内的连续两个事件之间,至少要有一条 tick line
- 利用逻辑时钟,我们可以对整个系统中的事件进行全序排序
3.理论基础
3.1 CAP理论
CAP的定义,分别代表一致性、可用性、分区容错性。三者无法同时达到
| 选项 | 描述 |
|---|---|
| C(Consistence) | 一致性,指数据在多个副本之间能够保持一致的特性(严格的一致性)。 |
| A(Availability) | 可用性,指系统提供的服务必须一直处于可用的状态,每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据。 |
| P(Network partitioning) | 分区容错性,分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障。 |
CAP 理论往往运用于数据库领域,同样可以适用于分布式存储方向
- CA系统:放弃分区容错性,加强一致性和可用性,其实就是传统的单机数据库的选择 (传统数据库的代表)
- AP:放弃一致性(这里说的一致性是强一致性),追求分区容错性和高可用性,例如一些注重用户体验的系统(不少nosql存储系统采用)
- CP:放弃可用性,追求一致性和分区容错性,保证数据一致性,例如与钱财安全相关的系统
举例说明两个分布式进程之间同步数据,当出现故障的时候,如何选择不同的CAP系统,以及带来的影响
- CP系统(上左图):故障发生时,为了避免读到不一致的数据,可能拒绝访问
- AP系统(上右图):故障发生时,为了保证可用性,允许不同进程读到不同的数据
在网络发生分区的情况下,我们必须在可用性和一致性之间做出选择。针对故障场景,可以通过故障转移的方式,做一个相对较优的解决方式:
- 近似解决办法:把故障节点的负载转移给备用节点负责。下图演示了如何做故障转移:
- 即允许一个进程作为 Master,其他进程作为Backup,当故障时将请求转移给Backup进行处理
3.2 ACID 理论
- ACID 理论是针对 CA 系统而言的,通常在数据库中具有广泛意义
- 事务是数据库系统中非常重要的概念,它是数据库管理系统执行过程中的一个逻辑单元,它能够保证一个事务中的所有操作要么全部执行,要么全都不执行
- 数据库事务拥有四个特性 ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)
| 特性 | 描述 |
|---|---|
| 原子性(A) | 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。 |
| 一致性(C) | 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态 |
| 隔离性(I) | 隔离性是当多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离 |
| 持久性(D) | 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。 |
3.3 BASE理论
Base 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大型互联网分布式实践的总结,是基于 CAP 定理逐步演化而来的。其核心思想是:
| 核心思想 | 描述 |
|---|---|
| Basically Available(基本可用) | 假设系统,出现了不可预知的故障,但还是能用,相比较正常的系统而言:响应时间上的损失,或功能上的损失 |
| Soft state(软状态) | 允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。 |
| Eventually consistent(最终一致性) | 系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问最终都能够获取到最新的值。 |
4.分布式事务
4.1 二阶段提交
- 定义:
二阶段提交(Two-phase Commit):为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种演算法。
三个假设:
- 引入协调者(Coordinator)和参与者(Participants),互相进行网络通信
- 预写式日志被保持在可靠的存储设备上
- 所有节点不会永久性损坏,即使损坏后仍然可以恢复
正常流程:Prepare 阶段和 Commit 阶段:
异常流程:
情况1)Coordinator不宕机,Participant宕机。如下图所示,Prepare阶段失败,需要进行回滚操作
情况2)Coordinator宕机,Participant不宕机。协调者宕机,重新启用新的协调者,待查询状态后,重复二阶段提交
情况3)Coordinator宕机,Participant宕机。双故障重启,无法确认状态,需要数据库管理员的介入,防止数据库进入一个不一致的状态。
两阶段提交需解决的问题:
| 问题 | 描述 |
|---|---|
| 性能问题 | 两阶段提交需要多次节点间的网络通信,耗时过大,资源需要进行锁定,徒增资源等待时间。 |
| 协调者单点故障问题 | 如何确定状态选出新协调者,因为如果事务协调者节点宕机,需要另起新的协调者,否则参与者处于中间状态无法完成事务。 |
| Commit阶段网络分区带来的数据不一致 | 一部分参与者收到了Commit消息,另一部分参与者没收到Commit消息,会导致了节点之间数据不一致。 |
-
两个思考:
- 日志被保存在「可靠」的存储设备上。如何保证这一点?
- 参与者 Commit 了,但 Ack 信息协调者没收到。怎么办?
4.2 三阶段提交
针对两阶段提交的补充,将两阶段提交中的 Prepare 阶段,拆成两部分:CanCommit 和 PreCommit 阶段
| 阶段 | ToDo |
|---|---|
| CanCommit 阶段 | 询问是否可以执行 |
| PreCommit 阶段 | 重新确认是否可以执行 |
| DoCommit 阶段 | 向所有人提交事务 |
较二阶段提交,解决了两个问题:
- 单点故障问题
- 阻塞问题
另外引入超时机制,在等待超时之后,会继续进行事务的提交。
- 思考?三阶段缓和了两阶段面临的问题,但依然没有解决:
- 性能问题
- 网络分区场景带来的数据一致性问题
4.3 MVCC
MVCC:多版本并发控制的方法。
- 维持一个数据的多个版本使读写操作没有冲突,所以既不会阻塞写,也不阻塞读。
- MVCC为每个修改保存一个版本,和事务的时间戳相关联。
- 提高并发性能的同时也解决了脏读的问题。
版本的选取:使用物理时钟或逻辑时钟
- 物理时钟:通过 TrueTime API 提供,有 Master 节点维持一个绝对时间,保证各个服务器之间时钟误差控制在 ϵ 内,通常 ϵ < 7 ms。
S1 提交事务时间:s1 = max(15, 7 + 7) = 15 ms
S2 提交事务时间:s2 = max(13, 12 + 7) = 19 ms
- 逻辑时钟:中心化授时的方式--时间戳预言机(TSO),好处是无需硬件的支持
时间戳预言机(TSO),采用中心化的授时方式,所有协调者向中心化节点获取时钟。优点是算法简单,实现方便,但需要每个节点都与他进行交互,会产生一些网络通信的成本。TSO的授时中就需要考虑低延迟,高性能以及更好的容错性。
悲观锁和乐观锁
- 悲观锁(左):操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据
- 乐观锁(右):不会上锁,只是在执行更新时判断别人是否修改数据,只有冲突时才放弃操作
5.共识协议
5.1 Quorum NWR模型
| 三要素 | 描述 |
|---|---|
| N | 在分布式存储系统中,有多少份备份数据 |
| W | 代表一次成功的更新操作要求至少有w份数据写入成功 |
| R | 代表一次成功的读数据操作要求至少有R份数据成功读取 |
- 为了保证强一致性,需要保证 W + R > N
Quorum NWR 模型将 CAP 的选择交给用户,是一种简化版的一致性模型
- 思考?引起的并发更新问题
如果允许数据被覆盖,则并发更新容易引起一致性问题
比如假设 N = 3, W = 2,R = 2
| 副本1 | 副本2 | 副本3 | |
|---|---|---|---|
| 初始值(v) | v1 = 1 | v1 = 1 | v1 = 1 |
| 第一次写 | v2 = 2 | v2 = 2 | v1 = 1 |
| 第二次写 | v3 = 3 | v2 = 2 | v2 = 2 |
那么读者如果读到副本 1 和副本 2,得出 v = 3 的结论如果读到副本 2 和副本3,得出 v = 2 的结论
问题根源:允许数据被覆盖
5.2 RAFT协议
概述
Raft 协议是一种分布式一致性算法(共识算法),即使出现部分节点故障,网络延时等情况,也不影响各节点,进而提高系统的整体可用性。Raft是使用较为广泛的分布式协议。一定意义上讲,RAFT 也使用了 Quorum 机制。
三种角色:
通常一个系统中是一主(Leader)多从(Follower)。
| 角色 | 描述 |
|---|---|
| Leader - 领导者 | Leader 负责处理所有的客户端请求,并向 Follower 同步请求日志,当日志同步到大多数节点上后,通知 Follower 提交日志 |
| Follower - 跟随者 | 接受并持久化 Leader 同步的日志,在 Leader 告知日志可以提交后,提交日志。不会发送任何请求,当 Leader 出现故障时,主动推荐自己为 Candidate。 |
| Candidate - 备选者 | Leader选举过程中的临时角色。向其他节点发送请求投票信息,如果获得大多数选票,则晋升为Leader。 |
四种定义:
| 描述 | |
|---|---|
| Log(日志) | 节点之间同步的信息,以只追加写的方式进行同步,解决了数据被覆盖的问题 |
| Term(任期号) | 单调递增,每个 Term 内最多只有一个 Leader |
| Committed | 日志被复制到多数派节点,即可认为已经被提交 |
| Applied | 日志被应用到本地状态机:执行了 log 中命令,修改了内存状态 |
状态转移:
Leader选举过程:
- 初始全部为 Follower
- Current Term + 1
- 选举自己
- 向其它参与者发起 RequestVote 请求,retry 直到
- 收到多数派请求,成为 Leader,并发送心跳
- 收到其它 Leader 的请求,转为 Follower,更新自己的 Term
- 收到部分,但未达到多数派,选举超时,随机 timeout 开始下一轮
两个规则
- 在一个任期内每个参与者最多投一票(持久化)
- 要成为 Leader,必须拿到多数投票
Log Replication 过程:
新Leader产生,Leader 和 Follower 不同步,Leader 强制覆盖 Followers 的不同步的日志
- Leader收到写请求w
- 将w写入本地log
- 向其它Follower发起AppendEntries RPC
- 等待多数派回复
- 更新本地状态机,返回给客户端
- 下一个心跳通知 Follower 上一个 Log 已经被 Committed 了
- Follower 也根据命令应用本地状态机
- Follower 有问题,Leader 一直 retry
切主:
当Leader出现问题时,就需要进行重新选举
- Leader 发现失去 Follower 的响应,失去 Leader 身份
- 两个 Follower 之间一段时间未收到心跳,重新进行选举,选出新的 Leader,此时发生了切主
- Leader 自杀重启,以 Follower 的身份加入进来
问题:老 leader 未失去身份,新 leader 已经选出,产生了“双主”,该如何解决呢?
Stale读:
- 发生Leader切换,old leader收到了读请求。如果直接响应,可能会有Stale Read
如何解决?
解决方案,保证读的强一致
读操作在 lease timeout 内,默认自己是 leader;不是则发起一次 heartbeat 。等待 Commit Index 应用到状态机。
Election timeout > lease timeout:新 leader 上任,自从上次心跳之后一定超过了 Election timeout,旧 leader 大概率能够发现自己的 Lease 过期
5.3 Paxos协议
Paxos 算法与 RAFT 算法区别:
- Multi-Paxos 可以并发修改日志,而 Raft 写日志操作必须是连续的
- Multi-Paxos 可以随机选主,不必最新最全的节点当选 Leader
Paxos 优劣势
- 优势:写入并发性能高,所有节点都能写
- 劣势:没有一个节点有完整的最新的数据,恢复流程复杂,需要同步历史记录
6.分布式实践
6.1 MapReduce
设计一个简易的MapReduce系统,思考如何应对故障?
Mapper:将输入分解为多个 Job 来并行处理。彼此间几乎没有依赖关系 Shuffler:将 maper 结果打乱,防止数据倾斜 Reducer:对 map 阶段的结果进行全局汇总
容错:
- Mapper 故障:由中心化节点重新发起调度,新起 Mapper 重跑 job
- Reducer 故障:重跑 Mapper,代价大
6.2 分布式KV
- 设计一个简易的分布式键值系统,要求具备弹性的能力和达成线性一致
架构:将海量结构化数据根据 Key 分成不同的 Region,每个 Region 构建一个单机 KV 数据库,Region 之间形成 Raft Groups,做到强一致
容错:当 Node 故障时,通过 Raft Learner 模式进行数据修复
弹性:当出现局部 Key 热点或数据膨胀时, Region 可以进行 Split 操作,分成两个子 Region,反之收缩时进行 Merge 操作
总结
课后
- 分布式系统有哪些优势和挑战?
- 两将军问题为什么理论上永远达不成共识?
- 为什么 TCP 采用三次握手?而不是两次和四次?
- 为什么在 4 将军问题中,增加 1 轮协商就可以对抗拜占庭故障?
- 什么是最终一致性?什么是线性一致性?
- CAP 理论中,请举例说明可用性和一致性的矛盾?
- 数据库里的一致性和分布式系统中的一致性有什么区别?
- 两阶段提交中,什么场景需要数据库管理员介入?
- 三阶段提交缓和两阶段提交的哪两个问题?
- 什么场景适合乐观锁?什么场景适合悲观锁?
- 在共识协议中,为什么说允许数据被覆盖会带来数据一致性问题?
- RAFT 协议中,Leader 写成功日志 Log20 但未同步给 Followers 后宕机,Follower 重新选举后产生一条新日志 Log20,这时 Leader 重启,整个系统发现两种不一样的 Log20 的记录,请问如何区分并拒掉前面的 Log20?
- RAFT 协议中,Stale 读是如何产生的?该如何解决 Stale 读的问题?
个人总结
- 分布式在现在数据爆炸的时代显得尤为重要
- 系统模型和理论基础要加强理解记忆,能够理解和运用分布式事务和共识协议
- 在熟悉掌握上述基础后,将之运用到现实场景中