背景
分布式系统对于数据复制的需求:
- 可用性:防止单点故障引起的系统不可用
- 性能:通过负载均衡技术,能够让分布在不同地方的数据副本都能够为用户提供服务
分布式一致性问题:在分布式环境中引入数据复制机制后,不同数据节点间可能出现的,并无法依靠计算机程序自身解决的数据不一致情况。即指在对一个副本数据进行更新时,必须确保也能够更新其他的副本,否则不同副本之间的数据将不再一致
副本就是指分布式系统对数据和服务提供的一种冗余方式,它分为:
- 数据副本:不同结点上持久化同一份数据,当某个节点上存储的数据丢失时可以从副本上读取
- 服务副本:多个节点提供同样的服务,每个节点都有能力接收来自外部的请求并进行相应的处理
一致性协议历史
1 Sequential Consistency
顺序一致性的要求有两方面:
- 事件历史在各个进程上看全局一致
- 单个线程的事件历史在全局历史上符合(单线程内的)程序顺序
可见顺序一致性对全序关系只要求一致即可,并不关心(多个线程中)事件发生的真实时间顺序
早期一致性问题都是在多处理器级别研究的(另外可参考 C++11 多线程感知-顺序一致性)。但是从之后发展起来的分布式系统来看, 多个服务器节点的写操作也经常会有本地缓存以及网络延迟与异常等情况,也会产生类似CPU缓存的副作用,这些问题在不同规模上看都是类似的
2 Linearizability
线性一致性,同时可也称作strong consistency、atomic consistency,线性一致性相当于在顺序一致性的基础上再去关心时间顺序,区分并发操作的先后。当多个事件之间没有重叠,中间有个”小间隔”的时候,线性一致性必须给出明确的(时间)先后顺序;当多个事件之间有重叠(并行),线性一致性要求所有线程都必须给出一致的事件顺序,这样所有线程都会对事件历史有完全相同的线性观点,这就像是一个消除并行的过程,所叫称之为 Linearization。(由于并行的存在,事件历史可以有多种Linearziation, 保证所有线程有相同的线性观点即可)
那么后来还有许多的性能更好、一致性更弱的协议 ... ...
CAP 定理
- Consistency
- 强一致性
- Availability
- 可用性,系统对用户的每一个操作请求总是能够在有限时间内返回结果
- Partition tolerance
- 分区容忍性,允许节点和其它节点无法通信
分区容忍性是分布式系统必然需要面对和解决的问题,因此通常需要根据业务特点在一致性和可用性之间寻求平衡
分布式环境相关概念
一致性级别
-
强一致性
- 系统写入值后,以后的读取一定能读到最新写入的值
-
弱一致性
- 系统在写入成功后,不承诺立即可以读到写入的值,尽可能的保证到某个时间级别(如秒级别)后能达到一致状态
-
最终一致性
- 它是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态
分布式环境下常见问题
- 节点之间通信异常
- 网络分区
- 通信异常的一种特殊情况,指不同的节点分布在不同的子网络中,这些子网络之间出现网络不连通的状况,但各子网络内部通信正常
- 节点故障
初识一致性协议
在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败
一致性协议的保证
- 安全性
- 存活性
2PC
二阶段提交协议(Two-Phase Commit)
-
假设
- 分布式系统中,存在一个节点作为协调者(Coordinator),其他节点作为参与者(Participants),且节点之间可以进行网络通信
- 所有节点都采用预写式日志,且日志被写入后即被保持在可靠的存储设备上,即使节点损坏不会导致日志数据的消失
- 所有节点不会永久性损坏,即使损坏后仍然可以恢复
-
概括
- 参与者 将操作成败 通知 协调者,再由 协调者 根据 所有参与者 的反馈情报决定 各参与者 是否要 提交操作 还是 中止操作
-
阶段一(投票阶段)
- 事务(是否可以提交事务)询问
- 协调者 向所有的 参与者 发送事务内容,询问是否可以执行 事务提交 操作,并开始等待各 参与者 的响应
- 执行事务
- 各 参与者节点 执行事务操作,并将Undo和Redo信息记入事务日志中
- 各 参与者 向 协调者 反馈事务询问的响应
- 如果参与者成功执行了事务操作,则反馈YES响应,否则返回NO响应
- 事务(是否可以提交事务)询问
-
阶段二(完成阶段)
- 协调者 从所有 参与者 获得的反馈都是YES响应
- 发送提交请求
- 协调者 向所有 参与者节点 发出COMMIT请求
- 事务提交
- 参与者接收到COMMIT请求后,会正式执行事务提交操作,并在完成提交之后 释放 在整个事务执行期间占用的事务资源
- 反馈事务提交结果
- 参与者 在完成事务提交之后,向 协调者 发送ACK消息
- 完成事务
- 协调者 接收到所有 参与者 反馈的ACK消息后,完成事务
- 发送提交请求
- 任何一个 参与者 向 协调者 反馈了NO响应
- 发送回滚请求
- 协调者 向所有 参与者节点 发送 ROLLBACK 请求
- 事务回滚
- 参与者 接收到ROLLBACK请求后,会利用其在阶段一中记录的UNDO信息来执行事务回滚操作,并在完成回滚之后 释放 在整个事务执行期间占用的资源
- 反馈事务回滚结果
- 参与者 在完成事务回滚之后,向 协调者 发送ACK消息
- 中断事务
- 协调者 接收到 所有参与者 反馈的ACK消息后,完成事务中断
- 发送回滚请求
- 协调者 从所有 参与者 获得的反馈都是YES响应
-
缺点
- 同步阻塞
- 所有(参与者)参与该事务操作的逻辑都处于阻塞状态
- 其它访问该事务操作锁定的资源的操作也会被阻塞
- 单点问题
- 协调者出现问题则整个二阶段提交流程将无法运行
- 如果协调者出现问题,可能会导致某些参与者一直处于锁定事务资源的状态,而无法继续完成事务操作
- 解决方案如:其他参与者重新选择协调者并继续执行协议
- 协调者和某些参与者同时出问题,而另外一些参与者处于阻塞等待第二阶段中的指令的状态
- 这会导致无法恢复,因为另外一些参与者 1)不知道出问题的参与者第一阶段返回的是 YES 还是 NO,2)不知道协调者发送的是什么指令,从而无法确定应该提交还是中断
- 数据不一致
- 可能只有部分参与者收到了COMMIT请求,并执行了提交;其他结点没有收到COMMIT请求从而无法提交
- 太过保守
- 当参与者出现故障,导致协调者始终无法获取所有参与者的响应信息时,协调者只能依靠自身的超时机制判断是否需要中断事务
- 同步阻塞
-
总结
- 二阶段协议对每个事务都是先尝试执行而后提交的两阶段处理方式,当协调者向用户返回结果前如果已经收到所有参与者的 ACK 消息,那么可以将二阶段提交看作强一致性算法
3PC
-
阶段一:CanCommit
- 协调者向参与者发送CanCommit请求
- 参与者向协调者返回询问的响应
-
阶段二:PreCommit
- 都响应 YES
- 协调者向参与者发送PreCommit请求
- 参与者接收到请求后,执行事务操作,并将Undo和Redo信息记录到事务日志中
- 参与者向协调者返回执行的响应
- 有响应 NO,或者协调者等待超时
- 协调者发送abort请求
- 参与者接收到请求后中断事务
- 都响应 YES
-
阶段三:DoCommit
- 第二阶段的返回响应都是 YES
- 协调者向参与者发送提交请求
- 参与者提交事务
- 参与者向协调者返回提交响应 ACK
- 协调者接收到所有 ACK 消息后完成事务
- 有响应 NO,或者协调者等待超时
- 协调者向参与者发送abort请求
- 参与者回滚事务
- 参与者向协调者返回响应 ACK
- 协调者接收到所有 ACK 消息后完成事务
- 第二阶段的返回响应都是 YES
在阶段三中,参与者如果等待来自协调者的commit或者abort请求超时后,都会执行提交操作,因此3PC协议降低了参与者阻塞的范围。而添加了一个PreCommit阶段,同时也降低了参与者阻塞的范围(可能有其他参与者在CanCommit阶段就已经返回 NO),
引入超时提交的依据:由于参与者在第二阶段已经收到了PreCommit请求,而协调者发送PreCommit请求的前提条件是它在第一阶段收到所有参与者的CanCommit响应都是Yes(一旦参与者收到了PreCommit,意味所有参与者都同意修改了;但在2PC中,参与者收到commit询问请求,并不能确定其它参与者是返回 YES 还是 NO)。但这仅仅是说参与者收到了PreCommit之后进行超时提交也是 OK 的概率比较大,但它也可能会提交失败
3PC没有什么实际应用,它的缺点主要有:高延迟、网络分区的情况下的不一致问题等
Paxos
假设没有失败和消息丢失
1 问题描述:有一组可以提出提案的进程集合,最终需要选择出一个一致同意的提案,并且每个进程都能获取到被选定的提案
2 推导过程
术语:提出、接受、选定
当只有一个 Acceptor 存在时(可以有多个 Proposer),只要 Acceptor 接受它收到的第一个提案,则该提案被选定,该提案里的值就是被选定的value。这样就保证只有一个value会被选定
而通常情况下是有多个 Acceptor(可以有多个 Proposer)的,如果我们希望即使只有一个 Proposer 提出了一个value,该value也能最终被选定,那么需要:
P1: 一个 Acceptor 必须接受它收到的第一个提案
如果只有一个提案,那 Acceptor 接受它时就可以选定它了。但考虑多个不同 Proposer 同时提出多个不同提案时只依赖 P1 显然是有问题的,那么还需要添加一个要求:一个提案被选定需要被半数以上的 Acceptor 接受才行。这个要求暗示了:一个Acceptor必须能够接受不止一个提案,以及所有被选定的提案具有相同的值,它的定义为:
P2: 如果编号为 M0,值为 V0 的提案[M0, V0]被选定了,那么所有比编号 M0 更高的,且被选定的提案,其值必须也是 V0
可以理解为当有一个提案被选定后,后续的被选定的提案值必须也是 V0。因为一个提案只有被 Acceptor 接受才可能被选定,因此把P2约束改写成对 Acceptor 接受提案的约束:
P2a:如果某个值为V0的提案被选定了,那么每个编号更高的被 Acceptor 接受的提案的值必须也是V0
一个提案必须在被 Acceptor 接受后才能被批准,所以只要满足了P2a,就能满足P2。但考虑一种情况:因为通信是异步的,一个提案可能会在某个 Acceptor 还未收到任何提案时就被选定了,而此时另一个 Proposer 产生了一个具有其他值的编号更大的提案,由于网络原因只提给了这一个 Acceptor,那么根据 P1,这个 Acceptor 会批准该提案。这种情况并不满足 P2a,那么需要对 P2a 做出强化:
P2b:如果某个值为V0的提案被选定了,那么之后任何 Proposer 提出的编号更高的提案的值必须也是V0
一个提案必须在被 Proposer 提出后才能被 Acceptor 接受,所以由 P2b 可以推出 P2a,进而推出 P2。那么如何确保在某个值为V0的提案被选定后,Proposer 提出的编号更高的提案的值都是 V0 呢?只要 Proposer 根据 P2c 产生一个提案:
P2c:对于任意的N和V,如果提案[N, V]被提出,需要存在一个半数以上的 Acceptor 组成的集合 S,满足以下两个条件中的任意一个:
- S 中每个 Acceptor 都没有接受过编号小于N的提案
- S 中 Acceptor 接受过的最大编号的提案的值为V
使用数学归纳法可以证明 P2c,因此 P2c=>P2b=>P2a=>P2,然后通过 P2和 P1来保证一致性
3 算法描述
阶段一:
(a) Proposer选择一个提案编号N,然后向半数以上的Acceptor发送编号为N的Prepare请求
(b) 如果一个Acceptor收到一个编号为N的Prepare请求,且N大于该Acceptor已经响应过的所有Prepare请求的编号,那么它就会将它已经接受过的编号最大的提案(如果有的话)作为响应反馈给Proposer,同时该Acceptor承诺不再接受任何编号小于N的提案
阶段二:
(a) 如果Proposer收到半数以上Acceptor对其发出的编号为N的Prepare请求的响应,那么它就会发送一个针对[N,V]提案的Accept请求给半数以上的Acceptor。注意:V就是收到的响应中编号最大的提案的value,如果响应中不包含任何提案,那么V就由Proposer自己决定。
(b) 如果Acceptor收到一个针对编号为N的提案的Accept请求,只要该Acceptor没有对编号大于N的Prepare请求做出过响应,它就接受该提案
每个 Acceptor 只需要记住它已经批准的提案的最大编号以及它已经做出 Prepare 请求响应的提高的最大编号 ,以便在出现故障或者节点重启的情况下,也能保证 P2c 的不变性。此外,如果一个 Acceptor 因为已经收到过更大的编号的 Prepare 请求而忽略某个编号更小的 Prepare 请求或者 Accept 请求,那它也应该通知其对应的 Proposer,让它能够将提案进行丢弃
4 获取提案
即当 Acceptor 接受提案后与 Learner 进行通信,当提案已经被半数以上的 Acceptor 接受时,Leaner即可获取被选定的提案;方案有多种,可参考相应的论文
5 保证活性
一种极端情况下存在无限循环,可以采用设置一个主 Proposer 以及规定只有主 Proposer 才能提出提案来解决
TODO:Paxos与2PC、3PC 关系,以及它会有什么问题
参考
《从Paxos到Zookeeper-分布式一致性原理与实践》
分布式系统一致性的发展历史 (一)
分布式系统一致性的发展历史 (二)
分布式一致性中,3PC是否或如何解决了2PC无法保障的协调者和参与者都挂掉后,节点恢复后数据一致的问题?
2pc和3pc的优缺点比较(同步阻塞、单点故障、数据不一致)
分布式CAP定理,为什么不能同时满足三个特性?
Paxos Made Simple
分布式系统共识协议: Paxos
Paxos算法原理与推导
浅谈 CAP 和 Paxos 共识算法
微信PaxosStore:深入浅出Paxos算法协议
Paxos-->Fast Paxos-->Zookeeper分析