这是我参与「第五届青训营 」伴学笔记创作活动的第 5 天
分布式概述
什么是分布式
分布式系统是计算机程序的集合, 这些程序利用跨多个独立计算节点的计算资源来实现共同的目标. 包括分布式计算, 分布式存储, 分布式数据库等
优势:
- 去中心化
- 低成本
- 弹性
- 资源共享
- 可靠性高
挑战:
- 普遍的节点故障
- 不可靠的网络
- 异构的机器与硬件环境
- 安全
常见的分布式系统
- 分布式存储
- google file system
- ceph
- hadoop hdfs
- zookeeper
- 分布式数据库
- google spanner
- TiDB
- hbase
- mongoDB
- 分布式计算
- hadoop
- spark
- yarn
系统模型
故障模型
- Byzantine failure: 节点可以任意篡改发送给其它节点的数据
- Authentication detectable byzantine failure: 上述故障的特例, 节点可以篡改数据但不能伪造其它节点的数据
上述两个故障涉及到数据的篡改, 较难处理
- Performance failure: 节点未在特定时间段收到数据, 即时间太早或太晚
- Omission failure: 节点收到数据的时间无限晚, 收不到数据
- Crash failure: 在上一个故障的基础上, 增加了节点停止响应的假设, 即节点宕机, 持续性发生omission failure
- Fail-stop failure: 在上一个故障的基础上, 可检测到具体的错误
从上到下, 错误更清晰, 更好处理
实际导致上述故障的原因包括磁盘故障, 磁盘坏道, 服务器主板故障, 网络故障, 网络分区, 内存故障, 线缆故障, 内核崩溃, cpu故障, 电源故障, 软件故障等
拜占庭将军问题
两将军问题(two generals' problem)
两支军队的将军只能派信使穿越敌方领土相互通信, 以此约定进攻时间. 求在信使可能被俘虏的情况下, 两将军达成共识
结论: 该问题无解, 无论两个将军进行多少轮信息交换, 都有可能因为信使被俘虏(丢包)而无法达成共识
- 方案一: 同时发送n个信使 缺点: 仍然不能保证一定达成共识
- 方案二: 设置超时时间, 若信使未返回, 加派信使
共识和传递消息不同, 传递消息只需要单边的信使成功抵达, 消息也能传递成功, 而达成共识需要回应确认的消息也成功抵达才能确立
tcp三次握手是在两个方向确认包的序列号, 增加超时重试, 是该问题的一个工程解
三个将军传递消息
三个将军互相传递消息, 消息可能丢失, 也可能被叛徒将军篡改, 当出现一个叛徒, 则系统无法达成一致
例如三个将军需要统一进攻或者撤退的战术安排, 将自己的决策传递给其他两个将军, 每个将军从三个决策中选取占大多数的决策作为最终策略:
- 如果不存在叛徒: 每个将军的决策都能正确地互相传达, 都能做出一致的决策
- 存在一个叛徒: 叛徒可以向其他两个将军发送不同的决策, 从而导致其他两个将军的最终决策不一致
解决方法: 增加一个将军作为消息中枢, 并增加一轮决策
- 第一轮三个将军将自己的决策发送给消息中枢, 消息中枢根据三个决策选出一个最终决策, 发送给三个将军
- 第二轮三个将军之间互相发送第一轮收到的决策, 根据这一轮收到的决策确定最终决策
两种可能:
-
当消息中枢是叛徒
他会在第一轮之后向其他三个将军发送不一致的最终决策, 但是由于这三个将军都是忠将, 他们在第二轮决策中还是能够根据消息中枢发送的三个决策达成一致的最终决策
-
当三个将军中有叛徒
第一轮消息中枢向三个将军发送一致的最终决策, 第二轮两个忠将会相互发送这个决策, 因此他们最终收到的决策中正确的决策一定占大多数, 从而达成消息中枢和两个忠将的一致
可以证明, 对于3m+1个将军, 其中有m个叛徒时, 可以增加m轮协商, 最终达成一致
共识和一致性
假设有三个客户端A, B, C, C客户端在某一时间进行写入操作, A和B进行读取操作, 在这个过程中, 客户端A和B可能读取到写入前的数据, 也可能读取到写入后的数据
- 当C写入完成后, A和B最终能读到一致的数据, 这样的一致性为Eventually consistent
- 当A读取到新版本的数据后, 将数据同步给B, 那么B就能够立刻获取到新的数据, 这样的一致性称为Linearizability
时间和事件顺序
定义happened before关系, 用表示, 满足条件:
- 如果a和b是发生在同一节点上的两个事件, a在b之前发生, 则定义
- 如果事件a表示某个节点发送某条消息, b是另一个节点接受这条消息, 则有
- 如果有且, 则有
当且仅当且时, 两个事件是并发的.
图中, 可以推导出, 从而找出两个事件之间的关系
lamport逻辑时钟
对每个节点定义一个时钟, 它是一个函数, 输入为节点上的事件, 输出为一个编号, 这些函数满足条件:
- 对于相同节点上的两个事件, 根据先后顺序有绝对的大小关系
- 对于发送和接收消息的两个节点上的两个事件, 根据收发关系也有绝对的大小关系
这些编号用上图的虚线表示, 即tick line, 在同一节点的连续两个事件之间, 至少要有一条tick line, 从而对整个系统中的事件进行全序排序
理论基础
CAP理论
- Consistence: 一致性, 在多个副本之间能够保持一致(严格的一致性)
- Availability: 可用性, 系统提供的服务必须一直处于可用的状态, 每次请求都能得到非错误的响应, 但不保证为最新的数据
- Network Partitioning: 分区容错性, 在任何网络分区故障的时候, 仍然能够对外提供满足一致性和可用性的服务
同时满足三种性质的分布式系统不存在, 在实际业务中往往会一定程度抛弃其中一个特性:
- CA: 放弃分区容错性, 即传统的单机数据库
- AP: 放弃一致性, 追求容错和可用性, 适用于注重用户体验的系统
- CP: 放弃可用性, 即系统可能一段时间不可用, 但是一定能保证数据的一致及容错性, 适用于钱财安全相关的系统
ACID理论
事务是数据库系统中非常重要的概念, 它是数据库管理系统执行过程中的一个逻辑单元, 一个单元内的操作要么全部执行, 要么全部不执行. 拥有4个特性
- 原子性 Atomicity: 所有操作要么全部成功, 要么回滚
- 一致性 Consistency: 事务必须从一个一致性状态变为另一个一致性状态, 比如银行转账, 必须保证操作前后状态的存款总额不变
前两个性质必须保证
- 隔离性 Isolation: 多个用户并发访问数据库时, 数据库为每个用户开启的事务不能被其它事务干扰
- 持久性 Durability: 一个事务一旦被提交, 那么数据库中的数据改变就是永久性的, 不会丢失提交事务的操作
后两个性质某些数据库系统可能不能保证
BASE理论
对CAP理论一致性和可用性权衡的结果, 核心思想是:
- basically available: 基本可用, 假设系统出现了不可预知的故障, 但是还是能用, 会有响应时间或者功能上的损失
- soft state: 软状态, 允许系统中的数据存在中间状态, 并且该状态不影响系统的整体可用性, 多个数据副本之间存在数据延时
- eventually consistent: 最终一致性, 系统的数据最终一定能够达到一致的状态
分布式事务
二阶段提交(two-phase commit)
为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的演算法
- 引入协调者和参与者, 互相进行通信
- 所有节点都采用预写式日志, 且日志被写入后被保持在可靠的存储设备上
- 所有节点不会永久性损坏
可能出现的故障情况:
- 参与者宕机, 需要进行回滚操作
- 协调者宕机, 需要起一个新的协调者, 查询状态后重复二阶段的提交
- 都宕机, 则无法确认状态, 需要管理员的介入, 防止数据库不一致的状态
问题:
- 性能问题: 多次节点通信带来的开销
- 协调者单点故障问题: 需要起新的协调者
- 网络分区带来的数据不一致
三阶段提交
将prepare阶段拆分为cancommit和precommit
- 解决了单点故障问题, 先确定是否可以commit, 再进行实际的commit, 减少了单点故障带来的开销
- 解决了阻塞问题
- 引入超时机制, 超时后会继续进行事务的提交
MVCC
锁
- 悲观锁: 操作数据时直接锁住, 直到操作完成才会释放锁
- 乐观锁: 不上锁, 只有在执行更新的时候判断别人是否修改数据, 如果两个人都修改数据产生冲突, 则放弃操作
使用MVCC方法
并发控制的方法, 维持一个数据的多个版本, 使读写操作没有冲突, 不阻塞. MVCC为每个修改保存一个版本, 和事务的时间戳相关联
如何保持时间的一致性:
- 通过在分布式系统中布置物理时钟, 使服务器时钟的偏差在可控制的范围内. 根据这个时间范围划分
tick line, 保证了每个事件都在不同的时间点 - 对于无法布置物理时钟的情况, 可以使用时间戳预言机(TSO), 采用中心化的授时方式, 协调者向中心化节点获取时钟
共识协议
Quorum NWR模型
- N: 在分布式存储系统中, 有n份备份数据
- W: 代表一次更新操作需要有w份数据写入成功, 满足后没写入的数据可以异步完成
- R: 一次成功的读操作要有R份数据成功读取, 从R份数据中选取版本最新的数据作为最终结果
为了保证强一致性, 需要保证
可以将CAP理论的选择权交给用户, 当用户选择配置, 则可以较好地保证一致性; 当用户选择配置, 则可以较好地保证可用性(降低延迟)
问题: 当更新数据的操作是对某一数据的覆盖时, 不能保证一致性
- 假设有三个副本, 初始状态某个数据都一致
- 进行第一次写, 前两个副本写入成功, 则认为这次写入成功, 第三个副本的写入进入异步执行状态
- 进行第二次写, 第一个和第三个副本写入成功, 认为这次写入成功, 但是上一个过程中的异步执行过程完成, 覆盖了本次副本3的写入结果, 则产生数据不一致问题, 对副本1和2的读取与副本2和3的读取结果不一致
因此问题的根源来源于允许数据被覆盖
RAFT协议
是一种分布式一致性算法, 即使出现部分节点故障, 网络延时, 也不影响各节点
每个节点可以拥有三种状态:
- leader:
- 一个系统只有一个leader
- 负责处理所有客户端请求, 向follower同步请求日志
- 当日志同步到大多数节点上后, 通知follower提交日志(利用了quorum的思想, 当大多数成功, 则认为操作成功, 提交结果)
- follower:
- 不会发送任何请求, 接受并持久化leader同步的日志, 当leader通知可以提交后, 提交日志.
- 当leader出现故障, 主动推荐自己为candidate
- candidate:
- 备选者, 选举过程中的临时角色, 向其他节点发送请求投票信息
- 如果获得大多数选票, 晋升为leader
每个节点都是一个状态机, 根据指定的情况进行状态转移
log日志: 是节点之间同步的信息, 以只追加写的方式进行同步, 避免了允许数据被覆盖的情况
term: 规定了一个单调递增的任期号, 每个term内只有一个leader
读取操作只需要读取leader的数据, 因为leader的数据一定是最新的
leader选举过程:
规则:
一个term内每个参与者只能投一票
成为leader必须拿到多数投票
- 初始全部为follower
- current term + 1
- 选举自己
- 向其他参与者发送requestvote, 不断重复直到:
- 收到多数派请求, 成为leader, 发送heartbeat
- 收到其它leader请求, 转为follower
- 收到部分, 但未达到大多数, 选举超时, 随机timeout开始下一轮
日志同步过程:
- leader收到写请求
- 将写请求写入本地log
- 向其它follower发起appendentries rpc
- 等待多数派回复
- 更新本地状态机, 返回客户端结果
- 通知follower上一个log已经提交了
- follower也将命令应用到本地
- follower有问题, 不断重试
一般通知会随着heartbeat一起发送给follower, 但是当请求较多时也可以直接发送通知, 因为heartbeat只是一个周期性的确认leader状态的通知机制
当新的leader产生, 且和followers不同步, 则根据新leader的log强制覆盖其它followers的log, 保证了即使旧leader已经应用了一些新的更新, 也能通过这个机制进行回滚
切主, 当leader出现问题, 重新进行选举:
- leader发现失去follower的响应, 失去leader身份
- 两个follower一段时间没有收到heartbeat, 重新选举, 选出新的leader, 发生切主
- leader自杀重启, 以follower的身份加入进来
问题: 老leader未失去身份, 新leader已经选出, 产生双主, 可能导致stale读(双主状态下两个leader返回的数据不一致, 导致读取结果不确定)
解决方案: 确立lease timeout, 在一定时间范围内假设旧leader仍然存活, 经过一段时间, 才能确立新leader
可能存在无主时间, 降低系统一定的可用性, 但是已经相对能满足CAP理论的需求了
Paxos协议
区别:
- 可以并发修改日志, 而raft写日志必须连续
- 可以随机选主, 不必最新最全的节点当选leader
优势: 写入并发性能高, 所有节点都能写入
劣势: 没有节点拥有完整的最新数据, 恢复流程复杂, 需要同步历史记录