CAP
Consistency(一致性)
- 在分布式系统中的所有数据备份,在同一时刻是否同样的值
- 所有节点访问同一份最新的数据副本
- 强调数据的正确性,对于客户端而言,每次都能读取到最新写入的数据
Availability(可用性)
- 在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
- 强调服务可用,但是不保证数据正确
Partition tolerance(分区容错性)
- 以实际效果而言,分区相当于对通信的时限要求
- 系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 C 和 A 之间做出选择。
- 强调的是集群对分区故障的容错能力
可用性和分区容错性的区别
- 可用性:节点出现故障时,系统可用
- 分区容错性:网络出现问题时,系统可用
CAP的关系

- CA 模型,不存在;舍弃P则舍弃分布式系统,如:在单机版MySQL中,如果要考虑主备或集群部署时,它必须考虑 P
- CP 模型,舍弃可用性,一定会读取最新的数据。一旦因为消息丢失、延迟过高发生了网络分区,就影响用户的体验和业务的可用性
- AP 模型,舍弃一致性,实现了服务的高可用。用户访问系统的时候,都能得到响应数据,不会出现响应错误,但会读到旧数据
选择
如果网络故障经常出现一般选择AP原则,如果对一致性要求比较高则选择CP;
- AP:redis、rocketmq、分布式事务-最大努力尝试、Eureka、Cassandra、DynamoDB。
- CP:分布式事务-2pc、基于Raft的强一致性系统(Etcd,Consul 和 Hbase)
ACID(追求一致性)
两阶段提交协议(2PC)
定义(分布式事务一致性协议:Protocol)
分布式系统下,一种通过两阶段协商来保障事务一致性的算法
算法思路
为了保持事务的 ACID 特性,需要引入协调者来统一掌控所有节点(参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)

准备阶段
在一个参与者投票要求提交事务之前,它必须保证能够执行提交协议中它自己那一部分,即使参与者出现故障或者中途被替换掉
- 协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待答复(发送Prepare消息)
- 各参与者执行事务操作,要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的redo和undo日志,但不提交
- 参与者执行成功返回yes,否则no
提交阶段
事务的每个参与者执行最终统一的决定,提交事务或者放弃事务
提交阶段(提交)
- 协调者向所有参与者发出正式提交事务的请求(即 commit 请求)
- 参与者执行 commit 请求,并释放整个事务期间占用的资源
- 各参与者向协调者反馈 ack(应答)完成的消息
- 协调者收到所有参与者反馈的 ack 消息后,即完成事务提交
提交阶段(回滚)
- 协调者向所有参与者发出回滚请求(即 rollback 请求)。
- 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。
- 各参与者向协调者反馈 ack 完成的消息。
- 协调者收到所有参与者反馈的 ack 消息后,即完成事务。
缺点
- 性能问题:所有参与者在事务提交阶段处于同步阻塞状态,占用系统资源,容易导致性能瓶颈。
- 可靠性问题:协调者单点故障,参与者将一直处于锁定状态
- 数据一致性问题:在阶段 2 中,如果出现协调者和参与者都挂了的情况,有可能导致数据不一致。
- 脑裂问题:在二阶段提交的阶段二中,当协调者向参与者发送 commit 请求之后,发生了局部网络异常或者在发送 commit 请求过程中协调者发生了故障,导致只有一部分参与者接受到了 commit 请求。于是整个分布式系统便出现了数据部一致性的现象(脑裂现象)。
- 二阶段无法解决的问题(数据状态不确定):协调者再发出 commit 消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
- 实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景。
优点
尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证强一致)。
三阶段提交协议
二阶段提交(2PC)的改进版本,3PC最关键要解决的就是协调者和参与者同时挂掉的问题,所以3PC把2PC的准备阶段再次一分为二,这样三阶段提交

改进点(对比2PC)
- 同时在协调者和参与者中都引入超时机制
- 在第一阶段和第二阶段中插入一个准备阶段;保证了在最后提交阶段之前各参与节点的状态是一致的。3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有 CanCommit、PreCommit、DoCommit 三个阶段。
阶段一CanCommit 阶段
- 协调者向所有参与者发出包含事务内容的 canCommit 请求,询问是否可以提交事务,并等待所有参与者答复。
- 参与者收到 canCommit 请求后,如果认为可以执行事务操作,则反馈 yes 并进入预备状态,否则反馈 no。
阶段二PreCommit 阶段
yes,协调者预执行事务
- 协调者向所有参与者发出 preCommit 请求,进入准备阶段。
- 参与者收到 preCommit 请求后,执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)。
- 各参与者向协调者反馈 ack 响应或 no 响应,并等待最终指令。
存在no或超时
- 协调者向所有参与者发出 abort 请求。
- 无论收到协调者发出的 abort 请求,或者在等待协调者请求过程中出现超时,参与者均会中断事务。
阶段三doCommit 阶段
ack 响应,执行真正的事务提交
- 如果协调者处于工作状态,则向所有参与者发出 do Commit 请求。
- 参与者收到 do Commit 请求后,会正式执行事务提交,并释放整个事务期间占用的资源。
- 各参与者向协调者反馈 ack 完成的消息。
- 协调者收到所有参与者反馈的 ack 消息后,即完成事务提交
存在no或超时
- 如果协调者处于工作状态,向所有参与者发出 rollback 请求。
- 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。
- 各参与者向协调组反馈 ack 完成的消息。
- 协调组收到所有参与者反馈的 ack 消息后,即完成事务回滚。
优点
相比二阶段提交,三阶段提交降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题。阶段3中协调者出现问题时,参与者会继续提交事务。
缺点
数据不一致问题依然存在,当在参与者收到 preCommit 请求后等待 do commite 指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
TCC 型事务(Try/Confirm/Cancel)
- 针对每个操作都要注册一个与其对应的确认操作和补偿操作(也就是撤销操作)
- 业务层面的协议(TCC操作在业务层实现),一种编程模型
- 为了实现一致性,确认操作和补偿操作必须是等幂的

WS-BusinessActivity 提供了一种基于补偿的 long-running 的事务处理模型。服务器 A 发起事务,服务器 B 参与事务,服务器 A 的事务如果执行顺利,那么事务 A 就先行提交,如果事务 B 也执行顺利,则事务 B 也提交,整个事务就算完成。但是如果事务 B 执行失败,事务 B 本身回滚,这时事务 A 已经被提交,所以需要执行一个补偿操作,将已经提交的事务 A 执行的操作作反操作,恢复到未执行前事务 A 的状态。这样的 SAGA 事务模型,是牺牲了一定的隔离性和一致性的,但是提高了 long-running 事务的可用性。
条件
- 需要实现确认和补偿逻辑
- 需要支持幂等
处理流程
- Try 阶段主要是对业务系统做检测及资源预留:
- 完成所有业务检查( 一致性 )
- 预留必须业务资源( 准隔离性 )
- Try 尝试执行业务。
- Confirm 阶段主要是对业务系统做确认提交,默认Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
- Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
优点
- 性能提升:具体业务来实现控制资源锁的粒度变小,不会锁定整个资源。
- 数据最终一致性:基于 Confirm 和 Cancel 的幂等性,保证事务最终完成确认或者取消,保证数据的一致性。
- 可靠性:解决了 XA 协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群。
缺点
TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。
1.1.2 异步确保型
1、 通过将一系列同步的事务操作变为基于消息执行的异步操作, 避免了分布式事务中的同步阻塞操作的影响。
1.1.1 最大努力通知型(多次尝试)
1、 这是分布式事务中要求最低的一种, 也可以通过消息中间件实现, 与前面异步确保型操作不同的一点是, 在消息由 MQ Server 投递到消费者之后, 允许在达到最大重试次数之后正常结束事务。
1.2 本地消息表(消息队列)
其核心思想是将分布式事务拆分成本地事务进行处理。
方案通过在消费者额外新建事务消息表,消费者处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,提供者基于消息中间件消费事务消息表中的事务。
1.1.1 条件
服务消费者需要创建一张消息表,用来记录消息状态。
服务消费者和提供者需要支持幂等。
需要补偿逻辑。
每个节点上起定时线程,检查未处理完成或发出失败的消息,重新发出消息,即重试机制和幂等性机制。
1.1.2 处理流程
- 服务消费者把业务数据和消息一同提交,发起事务。
- 消息经过MQ发送到服务提供方,服务消费者等待处理结果。
- 服务提供方接收消息,完成业务逻辑并通知消费者已处理的消息。
容错处理情况如下:
当步骤1处理出错,事务回滚,相当于什么都没有发生。
当步骤2、3处理出错,由于消息保存在消费者表中,可以重新发送到MQ进行重试。
如果步骤3处理出错,且是业务上的失败,服务提供者发送消息通知消费者事务失败,且此时变为消费者发起回滚事务进行回滚逻辑。
优点:从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于消息中间件,弱化了对 MQ 中间件特性的依赖。
缺点:与具体的业务场景绑定,耦合性强,不可公用。消息数据与业务数据同库,占用业务系统资源。业务系统在使用关系型数据库的情况下,消息服务性能会受到关系型数据库并发性能的局限。 - 2 MQ事务消息(最终一致性)
支持事务消息的MQ,其支持事务消息的方式采用类似于二阶段提交。
基于 MQ 的分布式事务方案其实是对本地消息表的封装,将本地消息表基于 MQ 内部,其他方面的协议基本与本地消息表一致。
1.1.1 条件
a) 需要补偿逻辑
b) 业务处理逻辑需要幂等
1.1.2 处理流程
c) 消费者向MQ发送half消息。
d) MQ Server将消息持久化后,向发送方ack确认消息发送成功。
e) 消费者开始执行事务逻辑。
f) 消费者根据本地事务执行结果向MQ Server提交二次确认或者回滚。
g) MQ Server收到commit状态则将half消息标记可投递状态。
h) 服务提供者收到该消息,执行本地业务逻辑。返回处理结果。
优点:
消息数据独立存储,降低业务系统与消息系统之间的耦合。
吞吐量优于本地消息表方案。
缺点:
一次消息发送需要两次网络请求(half消息 + commit/rollback)。
需要实现消息回查接口。
1.2 Sagas事务模型(最终一致性)
Saga模式是一种分布式异步事务,一种最终一致性事务,是一种柔性事务,有两种不同的方式来实现saga事务,最流行的两种方式是:
1.2.1 事件/编排Choreography:没有中央协调器(没有单点风险)时,每个服务产生并聆听其他服务的事件,并决定是否应采取行动。
该实现第一个服务执行一个事务,然后发布一个事件。该事件被一个或多个服务进行监听,这些服务再执行本地事务并发布(或不发布)新的事件,当最后一个服务执行本地事务并且不发布任何事件时,意味着分布式事务结束,或者它发布的事件没有被任何Saga参与者听到都意味着事务结束。
1.1.1.1 处理流程
订单服务保存新订单,将状态设置为pengding挂起状态,并发布名为ORDER_CREATED_EVENT的事件。
支付服务监听ORDER_CREATED_EVENT,并公布事件BILLED_ORDER_EVENT。
库存服务监听BILLED_ORDER_EVENT,更新库存,并发布ORDER_PREPARED_EVENT。
货运服务监听ORDER_PREPARED_EVENT,然后交付产品。最后,它发布ORDER_DELIVERED_EVENT。
最后,订单服务侦听ORDER_DELIVERED_EVENT并设置订单的状态为concluded完成。
假设库存服务在事务过程中失败了。进行回滚:
库存服务产生PRODUCT_OUT_OF_STOCK_EVENT
订购服务和支付服务会监听到上面库存服务的这一事件:
①支付服务会退款给客户。
②订单服务将订单状态设置为失败。
优点:事件/编排是实现Saga模式的自然方式; 它很简单,容易理解,不需要太多的努力来构建,所有参与者都是松散耦合的,因为他们彼此之间没有直接的耦合。如果您的事务涉及2至4个步骤,则可能是非常合适的。
1.1.2 命令/协调orchestrator:中央协调器负责集中处理事件的决策和业务逻辑排序。
saga协调器orchestrator以命令/回复的方式与每项服务进行通信,告诉他们应该执行哪些操作。
订单服务保存pending状态,并要求订单Saga协调器(简称OSO)开始启动订单事务。
OSO向收款服务发送执行收款命令,收款服务回复Payment Executed消息。
OSO向库存服务发送准备订单命令,库存服务将回复OrderPrepared消息。
OSO向货运服务发送订单发货命令,货运服务将回复Order Delivered消息。
OSO订单Saga协调器必须事先知道执行“创建订单”事务所需的流程(通过读取BPM业务流程XML配置获得)。如果有任何失败,它还负责通过向每个参与者发送命令来撤销之前的操作来协调分布式的回滚。当你有一个中央协调器协调一切时,回滚要容易得多,因为协调器默认是执行正向流程,回滚时只要执行反向流程即可。
优点:
避免服务之间的循环依赖关系,因为saga协调器会调用saga参与者,但参与者不会调用协调器。
集中分布式事务的编排。
只需要执行命令/回复(其实回复消息也是一种事件消息),降低参与者的复杂性。
在添加新步骤时,事务复杂性保持线性,回滚更容易管理。
如果在第一笔交易还没有执行完,想改变有第二笔事务的目标对象,则可以轻松地将其暂停在协调器上,直到第一笔交易结束。
BASE理论(追求可用性)
BASE 理论是 CAP 理论中的 AP 的延伸,是对互联网大规模分布式系统的实践总结,强调可用性。核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
Basically Available(基本可用)
基本可用四板斧
- 流量削峰:分时间段时间分业务场景处理
- 延迟响应:队列等待
- 体验降级:降低用户体验
- 过载保护:排队执行、超时保护
Soft state(软状态)
Eventually consistent(最终一致性)
系统中所有的数据副本在经过一段时间的同步后,最终能够达到一个一致的状态。
实现依据
- 以最新写入的数据为准,比如 AP 模型的 KV 存储采用的就是这种方式;
- 以第一次写入的数据为准,如果你不希望存储的数据被更改,可以以它为准。
实现方式
- 读时修复:在读取数据时,检测数据的不一致,进行修复。比如 Cassandra 的 Read Repair 实现,具体来说,在向 Cassandra 系统查询数据的时候,如果检测到不同节点的副本数据不一致,系统就自动修复数据。
- 写时修复:在写入数据,检测数据的不一致时,进行修复。比如 Cassandra 的 Hinted Handoff 实现。具体来说,Cassandra 集群的节点之间远程写数据的时候,如果写失败就将数据缓存下来,然后定时重传,修复数据的不一致性。
- 异步修复:这个是最常用的方式,通过定时对账检测副本数据的一致性,并修复