一、CAP 定理
1.1 分布式系统的三个指标
1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标。
- Consistency
- Availability
- Partition tolerance
它们的第一个字母分别是 C、A、P。
Eric Brewer 说,这三个指标不可能同时做到。这个结论就叫做 CAP 定理。
1.2 Partition tolerance
先看 Partition tolerance,中文叫做"分区容错"。
大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信。
上图中,G1 和 G2 是两台跨区的服务器。G1 向 G2 发送一条消息,G2 可能无法收到。系统设计的时候,必须考虑到这种情况。
一般来说,分区容错无法避免,因此可以认为 CAP 的 P 总是成立。CAP 定理告诉我们,剩下的 C 和 A 无法同时做到。
1.3 Consistency
Consistency 中文叫做"一致性"。意思是,写操作之后的读操作,必须返回该值。举例来说,某条记录是 v0,用户向 G1 发起一个写操作,将其改为 v1。
接下来,用户的读操作就会得到 v1。这就叫一致性。
问题是,用户有可能向 G2 发起读操作,由于 G2 的值没有发生变化,因此返回的是 v0。G1 和 G2 读操作的结果不一致,这就不满足一致性了。
为了让 G2 也能变为 v1,就要在 G1 写操作的时候,让 G1 向 G2 发送一条消息,要求 G2 也改成 v1。
这样的话,用户向 G2 发起读操作,也能得到 v1。
1.4 Availability
Availability 中文叫做"可用性",意思是只要收到用户的请求,服务器就必须给出回应。
用户可以选择向 G1 或 G2 发起读操作。不管是哪台服务器,只要收到请求,就必须告诉用户,到底是 v0 还是 v1,否则就不满足可用性。
1.5 Consistency 和 Availability 的矛盾
一致性和可用性,为什么不可能同时成立?答案很简单,因为可能通信失败(即出现分区容错)。
如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,G2 不能读写,没有可用性不。
如果保证 G2 的可用性,那么势必不能锁定 G2,所以一致性不成立。
综上所述,G2 无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,那就没法做到一致性。
二、BASE理论
BASE是Basically Available(基本可用)
、Soft state(软状态)
和Eventually consistent(最终一致性)
三个短语的简写,BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,接受数据存在中间状态(软状态如同步延时,但不会影响系统的整体可用性),采用适当的方式来使系统达到最终一致性(Eventual consistency)。
2.1 基本可用
也就是说分布式系统在出现不可预知故障的时候,允许损失部分可用性。
这绝不等价于系统不可用。比如:
(1)响应时间上的损失。正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒
(2)系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面
2.2.软状态
软状态指的是允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性 。也就说允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
2.3 最终一致性
最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
总的来说,BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。
但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID特性和BASE理论往往又会结合在一起。
三、两阶段提交(2PC)
Two-Phase Commit:准备阶段和提交阶段
2PC的概念如图所示,引入一个资源协调者的概念,由这个资源协调者进行事务协调。
第一阶段,由这个资源协调者对每个mysql实例调用prepare命令,让所有的mysql实例准备好,如果其中由mysql实例没有准备好,协调者就让所有实例调用rollback命令进行回滚。如果所有mysql都prepare完成,那么就进入第二阶段。
第二阶段,资源协调者让每个mysql实例都调用commit方法,进行提交。
所有相关节点都有Undo和Redo信息及其他binlog记入事务日志中。
优点
原理简单,实现方便
缺点
每一阶段都存在同步阻塞、协调者单点问题、保守的容错机制(有一个结点宕机就失败)、脑裂。
同步阻塞:二阶段提交协议最明显也是最大问题,极大限制了分布式系统性能。各个参与者在等待其余参与者响应过程中没法进行其他任何操作。
单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
数据不一致:在阶段二,如果协调者发出了一部分commit信号之后宕机,导致最终只有部分参与者收到了commit信号。于是收到了commit信号的参与者执行提交操作,没有收到commit信号的就无法提交,这两部分节点就会出现数据不一致
太过保守的容错机制:参与者中任何一台机器宕机都会导致整个事务的失败。
四、三阶段提交(3PC)
三阶段提交协议在coordinator(协调者)和participants(参与者)中都引入了超时机制。并且将两阶段提交协议的第一个阶段拆分成了两步。询问,然后再锁资源,最后真正提交。
三个阶段的执行
4.1 CanCommit阶段
3PC的CanCommit阶段很像2PC中的准备阶段。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应。否则就返回No。
4.2 PreCommit阶段
在此阶段,coordinator根据participants的响应情况来决定是否可以继续事务的PreCommit操作。
可能有以下几种情况:
A. all participants都是yes,那么就进行事务的预执行:
发送预提交请求。Coordinator向Cohort发送PreCommit请求,并进入Prepared阶段;
事务预提交。Cohort接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
响应反馈。若Cohort成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
B. 其中一个Cohort向coordinator发送了No响应,或者等待超时之后,Coordinator都没有接收到Cohort的响应,那么就中断事务。
发送中断请求。Coordinator向所有的cohort发送abort请求;
中断事务。cohort收到来自coordinator的abort请求之后(或者超时之后仍未收到Cohort的请求),执行事务的中断。
4.3 DoCommit阶段
此阶段进行真正的事务提交。分为以下两种情况。
4.3.1 执行提交
发送提交请求。Coordinator接收到cohort发送的ACK响应,他将从预提交状态进入提交状态。并向所有的Cohort发送doCommit请求。
事务提交。cohort接收到doCommit请求之后,执行正式的事务提交,并在完成事务提交之后释放所有事务资源。
响应反馈。事务提交完之后,向coordinator发送ACK响应。
完成事务。coordinator接收到所有的cohort的ack响应之后,完成事务。
4.3.2 中断事务
coordinator没有接收到cohort发送的ack响应(可能cohort发送的不是ack响应,也可能是响应超时 )那么就会执行中断事务。
优点
降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段3中协调者出现问题时,参与者会继续提交事务
缺陷
脑裂问题依然存在,即在参与者收到PreCommit请求后等待最终指令,如果此时协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
无论2PC或3PC,均无法彻底解决分布式一致性问题。解决一致性问题,唯有Paxos / Raft
三、补偿事务 TCC
TCC的核心思想是针对每一个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
Try 阶段是对业务系统做检测以及资源预留;
Confirm阶段主要是对业务系统做出确认提交;try阶段成功,默认confirm一定成功。
Cancel阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源得到释放。
举个例子,假入 Bob 要向 Smith 转账,思路大概是:
我们有一个本地方法,里面依次调用
1、首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。
2、在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
3、如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。