在分布式系统领域,有一个理论,对于分布式系统的设计影响很是大,那就是 CAP 理论,即对于一个分布式系统而言,它是没法同时知足 Consistency(强一致性)、Availability(可用性) 和 Partition tolerance(分区容忍性) 这三个条件的,最多只能知足其中两个。但在实际中,因为网络环境是不可信的,因此分区容忍性几乎是必不可选的,设计者基本就是在一致性和可用性之间作选择,固然大部分状况下,你们都会选择牺牲一部分的一致性来保证可用性(可用性较差的系统很是影响用户体验的,可是对另外一些场景,好比支付场景,强一致性是必需要知足)。可是分布式系统又没法完全放弃一致性(Consistency),若是真的放弃一致性,那么就说明这个系统中的数据根本不可信,数据也就没有意义,那么这个系统也就没有任何价值可言。html

CAP 理论

CAP 理论三个特性的详细含义以下:算法

  1. 一致性(Consistency):每次读取要么是最新的数据,要么是一个错误;
  2. 可用性(Availability):client 在任什么时候刻的读写操做都能在限定的延迟内完成的,即每次请求都能得到一个响应(非错误),但不保证是最新的数据;
  3. 分区容忍性(Partition tolerance):在大规模分布式系统中,网络分区现象,即分区间的机器没法进行网络通讯的状况是必然会发生的,系统应该能保证在这种状况下能够正常工做。

分区容忍性

不少人可能对分区容忍性不太理解,知乎有一个回答对这个解释的比较清楚(CAP理论中的P究竟是个什么意思?),这里引用一下:shell

  • 一个分布式系统里面,节点组成的网络原本应该是连通的。然而可能由于一些故障,使得有些节点之间不连通了,整个网络就分红了几块区域。数据就散布在了这些不连通的区域中。这就叫分区。
  • 当你一个数据项只在一个节点中保存,那么分区出现后,和这个节点不连通的部分就访问不到这个数据了。这时分区就是没法容忍的。
  • 提升分区容忍性的办法就是一个数据项复制到多个节点上,那么出现分区以后,这一数据项就可能分布到各个区里,容忍性就提升了。
  • 然而,要把数据复制到多个节点,就会带来一致性的问题,就是多个节点上面的数据多是不一致的。
  • 要保证一致,每次写操做就都要等待所有节点写成功,而这等待又会带来可用性的问题。
  • 总的来讲就是,数据存在的节点越多,分区容忍性越高,但要复制更新的数据就越多,一致性就越难保证。为了保证一致性,更新全部节点数据所须要的时间就越长,可用性就会下降。

CAP 如何选择

CAP 理论一个经典原理以下所示:segmentfault

CAP 理论原理CAP 理论原理安全

CAP 定理代表,在存在网络分区的状况下,一致性和可用性必须二选一。而在没有发生网络故障时,即分布式系统正常运行时,一致性和可用性是能够同时被知足的。可是,对于大多数互联网应用来讲,由于规模比较大,部署节点分散,网络故障是常态,可用性是必需要保证的,因此只有舍弃一致性来保证服务的 AP。可是对于一些金融相关行业,它有不少场景须要确保一致性,这种状况一般会权衡 CA 和 CP 模型,CA 模型网络故障时彻底不可用,CP 模型具有部分可用性。网络

在一个分布式系统中,对于这三个特性,咱们只能三选二,没法同时知足这三个特性,三选二的组合以及这样系统的特色总结以下(来自左耳朵耗子推荐:分布式系统架构经典资料):架构

  • CA (Consistency + Availability):关注一致性和可用性,它须要很是严格的全体一致的协议,好比“两阶段提交”(2PC)。CA 系统不能容忍网络错误或节点错误,一旦出现这样的问题,整个系统就会拒绝写请求,由于它并不知道对面的那个结点是否挂掉了,仍是只是网络问题。惟一安全的作法就是把本身变成只读的。
  • CP (consistency + partition tolerance):关注一致性和分区容忍性。它关注的是系统里大多数人的一致性协议,好比:Paxos 算法 (Quorum 类的算法)。这样的系统只须要保证大多数结点数据一致,而少数的结点会在没有同步到最新版本的数据时变成不可用的状态。这样可以提供一部分的可用性。
  • AP (availability + partition tolerance):这样的系统关心可用性和分区容忍性。所以,这样的系统不能达成一致性,须要给出数据冲突,给出数据冲突就须要维护数据版本。Dynamo 就是这样的系统。

对于分布式系统分区容忍性是自然具有的要求,不然一旦出现网络分区,系统就拒绝全部写入只容许可读,这对大部分的场景是不可接收的,所以,在设计分布式系统时,更多的状况下是选举 CP 仍是 AP,要么选择强一致性弱可用性,要么选择高可用性容忍弱一致性。分布式

一致性模型

关于分布式系统的一致性模型有如下几种:post

强一致性

当更新操做完成以后,任何多个后续进程或者线程的访问都会返回最新的更新过的值,直到这个数据被其余数据更新为止。性能

可是这种实现对性能影响较大,由于这意味着,只要上次的操做没有处理完,就不能让用户读取数据。

弱一致性

系统并不保证进程或者线程的访问都会返回最新更新过的值。系统在数据写入成功以后,不承诺当即能够读到最新写入的值,也不会具体的承诺多久以后能够读到。但会尽量保证在某个时间级别(好比秒级别)以后,可让数据达到一致性状态。

最终一致性

最终一致性也是弱一致性的一种,它没法保证数据更新后,全部后续的访问都能看到最新数值,而是须要一个时间,在这个时间以后能够保证这一点,而在这个时间内,数据也许是不一致的,这个系统没法保证强一致性的时间片断被称为「不一致窗口」。不一致窗口的时间长短取决于不少因素,好比备份数据的个数、网络传输延迟速度、系统负载等。

最终一致性在实际应用中又有多种变种:

类型 说明
因果一致性 若是 A 进程在更新以后向 B 进程通知更新的完成,那么 B 的访问操做将会返回更新的值。而没有因果关系的 C 进程将会遵循最终一致性的规则(C 在不一致窗口内仍是看到是旧值)。
读你所写一致性 因果一致性的特定形式。一个进程进行数据更新后,会给本身发送一条通知,该进程后续的操做都会以最新值做为基础,而其余的进程仍是只能在不一致窗口以后才能看到最新值。
会话一致性 读你所写一致性的特定形式。进程在访问存储系统同一个会话内,系统保证该进程能够读取到最新之,但若是会话终止,从新链接后,若是此时还在不一致窗口内,仍是可嫩读取到旧值。
单调读一致性 若是一个进程已经读取到一个特定值,那么该进程不会读取到该值之前的任何值。
单调写一致性 系统保证对同一个进程的写操做串行化。

它们的关系又以下图所示(图来自 《大数据日知录:架构与算法》):

一致性模型之间关系一致性模型之间关系

分布式一致性协议

为了解决分布式系统的一致性问题,在长期的研究探索过程当中,业内涌现出了一大批经典的一致性协议和算法,其中比较著名的有二阶段提交协议(2PC),三阶段提交协议(3PC)和 Paxos 算法(本文暂时先不介绍)。

Google 2009年 在Transaction Across DataCenter 的分享中,对一致性协议在业内的实践作了一简单的总结,以下图所示,这是 CAP 理论在工业界应用的实践经验。

CAP 理论在工业界的实践CAP 理论在工业界的实践

其中,第一行表头表明了分布式系统中通用的一致性方案,包括冷备、Master/Slave、Master/Master、两阶段提交以及基于 Paxos 算法的解决方案,第一列表头表明了分布式系统你们所关心的各项指标,包括一致性、事务支持程度、数据延迟、系统吞吐量、数据丢失可能性、故障自动恢复方式。

两阶段提交协议(2PC)

二阶段提交协议(Two-phase Commit,即2PC)是经常使用的分布式事务解决方案,它能够保证在分布式事务中,要么全部参与进程都提交事务,要么都取消事务,即实现 ACID 的原子性(A)。在数据一致性中,它的含义是:要么全部副本(备份数据)同时修改某个数值,要么都不更改,以此来保证数据的强一致性。

2PC 要解决的问题能够简单总结为:在分布式系统中,每一个节点虽然能够知道本身的操做是成功仍是失败,倒是没法知道其余节点的操做状态。当一个事务须要跨越多个节点时,为了保持事务的 ACID 特性,须要引入一个做为协调者的组件来统一掌控全部节点(参与者)的操做结果并最终指示这些节点是否要把操做结果进行真正的提交(好比将更新后的数据写入磁盘等等)。所以,二阶段提交的算法思路能够归纳为: 参与者将操做结果通知协调者,再由协调者根据全部参与者的反馈情报决定各参与者是否要提交操做仍是停止操做。

2PC 过程

关于两阶段提交的过程以下图所示:

两阶段提交过程两阶段提交过程

顾名思义,2PC 分为两个过程:

  1. 表决阶段:此时 Coordinator (协调者)向全部的参与者发送一个 vote request,参与者在收到这请求后,若是准备好了就会向 Coordinator 发送一个 VOTE_COMMIT 消息做为回应,告知 Coordinator 本身已经作好了准备,不然会返回一个 VOTE_ABORT 消息;
  2. 提交阶段:Coordinator 收到全部参与者的表决信息,若是全部参与者一致认为能够提交事务,那么 Coordinator 就会发送 GLOBAL_COMMIT 消息,不然发送 GLOBAL_ABORT 消息;对于参与者而言,若是收到 GLOBAL_COMMIT 消息,就会提交本地事务,不然就会取消本地事务。

2PC 一致性问题

这里先讨论一下,2PC 是否能够在任何状况下均可以解决一致性问题,在实际的网络生产中,各类状况都有可能发生,这里,咱们先从理论上分析各类意外状况。

2PC 在执行过程当中可能发生 Coordinator 或者参与者忽然宕机的状况,在不一样时期宕机可能有不一样的现象。

状况 分析及解决方案
Coordinator 挂了,参与者没挂 这种状况其实比较好解决,只要找一个 Coordinator 的替代者。当他成为新的 Coordinator 的时候,询问全部参与者的最后那条事务的执行状况,他就能够知道是应该作什么样的操做了。因此,这种状况不会致使数据不一致。
参与者挂了(没法恢复),Coordinator 没挂 若是挂了以后没有恢复,那么是不会致使数据一致性问题。
参与者挂了(后来恢复),Coordinator 没挂 恢复后参与者若是发现有未执行完的事务操做,直接取消,而后再询问 Coordinator 目前我应该怎么作,协调者就会比对本身的事务执行记录和该参与者的事务执行记录,告诉他应该怎么作来保持数据的一致性。

还有一种状况是:参与者挂了,Coordinator 也挂了,须要再细分为几种类型来讨论:

状况 分析及解决方案
Coordinator 和参与者在第一阶段挂了 因为这时尚未执行 commit 操做,新选出来的 Coordinator 能够询问各个参与者的状况,再决定是进行 commit 仍是 roolback。由于尚未 commit,因此不会致使数据一致性问题。
Coordinator 和参与者在第二阶段挂了,可是挂的这个参与者在挂以前尚未作相关操做 这种状况下,当新的 Coordinator 被选出来以后,他一样是询问全部参与者的状况。只要有机器执行了 abort(roolback)操做或者第一阶段返回的信息是 No 的话,那就直接执行 roolback 操做。若是没有人执行 abort 操做,可是有机器执行了 commit 操做,那么就直接执行 commit 操做。这样,当挂掉的参与者恢复以后,只要按照 Coordinator 的指示进行事务的 commit 仍是 roolback 操做就能够了。由于挂掉的机器并无作 commit 或者 roolback 操做,而没有挂掉的机器们和新的 Coordinator 又执行了一样的操做,那么这种状况不会致使数据不一致现象。
Coordinator 和参与者在第二阶段挂了,挂的这个参与者在挂以前已经执行了操做。可是因为他挂了,没有人知道他执行了什么操做。 这种状况下,新的 Coordinator 被选出来以后,若是他想负起 Coordinator 的责任的话他就只能按照以前那种状况来执行 commit 或者 roolback 操做。这样新的 Coordinator 和全部没挂掉的参与者就保持了数据的一致性,咱们假定他们执行了 commit。可是,这个时候,那个挂掉的参与者恢复了怎么办,由于他已经执行完了以前的事务,若是他执行的是 commit 那还好,和其余的机器保持一致了,万一他执行的是 roolback 操做呢?这不就致使数据的不一致性了么?虽然这个时候能够再经过手段让他和 Coordinator 通讯,再想办法把数据搞成一致的,可是,这段时间内他的数据状态已是不一致的了!

因此,2PC协议中,若是出现协调者和参与者都挂了的状况,有可能致使数据不一致。为了解决这个问题,衍生出了3PC。

2PC 优缺点

简单总结一下 2PC 的优缺点:

  • 优势:原理简洁清晰、实现方便;
  • 缺点:同步阻塞、单点问题、某些状况可能致使数据不一致。

关于这几个缺点,在实际应用中,都是对2PC 作了相应的改造:

  1. 同步阻塞:2PC 有几个过程(好比 Coordinator 等待全部参与者表决的过程当中)都是同步阻塞的,在实际的应用中,这可能会致使长阻塞问题,这个问题是经过超时判断机制来解决的,但并不能彻底解决同步阻塞问题;
  2. Coordinator 单点问题:实际生产应用中,Coordinator 都会有相应的备选节点;
  3. 数据不一致:这个在前面已经讲述过了,若是在第二阶段,Coordinator 和参与者都出现挂掉的状况下,是有可能致使数据不一致的。

三阶段提交协议(3PC)

三阶段提交协议(Three-Phase Commit, 3PC)最关键要解决的就是 Coordinator 和参与者同时挂掉致使数据不一致的问题,因此 3PC 把在 2PC 中又添加一个阶段,这样三阶段提交就有:CanCommit、PreCommit 和 DoCommit 三个阶段。

3PC 过程

三阶段提交协议的过程以下图(图来自 维基百科:三阶段提交)所示:

三节点提交过程三节点提交过程

3PC 的详细过程以下(这个过程步骤内容来自 2PC到3PC到Paxos到Raft到ISR):

阶段一 CanCommit

  1. 事务询问:Coordinator 向各参与者发送 CanCommit 的请求,询问是否能够执行事务提交操做,并开始等待各参与者的响应;
  2. 参与者向 Coordinator 反馈询问的响应:参与者收到 CanCommit 请求后,正常状况下,若是自身认为能够顺利执行事务,那么会反馈 Yes 响应,并进入预备状态,不然反馈 No。

阶段二 PreCommit

执行事务预提交:若是 Coordinator 接收到各参与者反馈都是Yes,那么执行事务预提交:

  1. 发送预提交请求:Coordinator 向各参与者发送 preCommit 请求,并进入 prepared 阶段;
  2. 事务预提交:参与者接收到 preCommit 请求后,会执行事务操做,并将 Undo 和 Redo 信息记录到事务日记中;
  3. 各参与者向 Coordinator 反馈事务执行的响应:若是各参与者都成功执行了事务操做,那么反馈给协调者 ACK 响应,同时等待最终指令,提交 commit 或者终止 abort,结束流程;

中断事务:若是任何一个参与者向 Coordinator 反馈了 No 响应,或者在等待超时后,Coordinator 没法接收到全部参与者的反馈,那么就会中断事务。

  1. 发送中断请求:Coordinator 向全部参与者发送 abort 请求;
  2. 中断事务:不管是收到来自 Coordinator 的 abort 请求,仍是等待超时,参与者都中断事务。

阶段三 doCommit

执行提交

  1. 发送提交请求:假设 Coordinator 正常工做,接收到了全部参与者的 ack 响应,那么它将从预提交阶段进入提交状态,并向全部参与者发送 doCommit 请求;
  2. 事务提交:参与者收到 doCommit 请求后,正式提交事务,并在完成事务提交后释放占用的资源;
  3. 反馈事务提交结果:参与者完成事务提交后,向 Coordinator 发送 ACK 信息;
  4. 完成事务:Coordinator 接收到全部参与者 ack 信息,完成事务。

中断事务:假设 Coordinator 正常工做,而且有任一参与者反馈 No,或者在等待超时后没法接收全部参与者的反馈,都会中断事务

  1. 发送中断请求:Coordinator 向全部参与者节点发送 abort 请求;
  2. 事务回滚:参与者接收到 abort 请求后,利用 undo 日志执行事务回滚,并在完成事务回滚后释放占用的资源;
  3. 反馈事务回滚结果:参与者在完成事务回滚以后,向 Coordinator 发送 ack 信息;
  4. 中断事务:Coordinator 接收到全部参与者反馈的 ack 信息后,中断事务。

3PC 分析

3PC 虽然解决了 Coordinator 与参与者都异常状况下致使数据不一致的问题,3PC 依然带来其余问题:好比,网络分区问题,在 preCommit 消息发送后忽然两个机房断开,这时候 Coordinator 所在机房会 abort, 另外剩余参与者的机房则会 commit。

并且因为3PC 的设计过于复杂,在解决2PC 问题的同时也引入了新的问题,因此在实际上应用不是很普遍。

参考: