分布式事务(二)
一、CAP定理
2007年7月,Eric Brewer教授提出CAP猜想。2年后,Seth Glibert和Nancy Lynch从理论上证明了CAP,CAP理论正式成为分布式计算领域的公认定理。可以说,分布式系统离不开CAP理论。
1. CAP基础知识
C(一致性)
any read operation that begins after a write operation completes must return that value,or the result of a later write operation.
在写操作完成后,任何读操作都必须返回该值。(强一致性),强调的数据
A(可用性)
every request received by a non-failing node in the system must result in a response.
一个正常的服务器接收一个请求,必须响应结果给客户端。强调是的及时响应
P(分区容错性)
the network will be allowed to lose arbitrarily many messages sent from one node another.
网络是不可靠的,允许丢失任意多的消息从一个节点发送到另一个节点。允许分布式环境中,节点之间的通信可能发生问题,整个系统就产生所谓的分区,一定要保证P。
2. CP pk AP
(1) 证明分布式系统不能同时满足CAP三点
分布式系统主要问题还是网络的问题,理想条件下,我们认为网络是可靠的。AP是可以同时存在的。
第一步:Client向ServerA更改数据v0->v1
第二步:由于网络可靠,ServerA会同步数据到ServerB,ServerB的v0->v1
第三步:Client从ServerA或ServerB读取v值时,都是v1(一致性和可用性)
真实情况下,网络是不可靠的,一旦网络断开就产生了分区(强制保证分区容错性)。此时,有以下两种方案:
第一种:牺牲数据一致性,保证高可用。结果:响应旧的数据v0给客户端。
第二种:牺牲可用性,保证数据一致性。结果:阻塞等待,直到网络恢复连接,完成数据同步,再响应最新的数据v1给客户端。
(2)选择CP还是AP
适合的才是最好的
二、BASE理论
eBay的架构师Dan Pritchett源于对大规模分布式系统的实践总结,在ACM上发表文章提出BASE理论,BASE理论是对CAP理论的延伸,即使无法保证强一致性(CAP的C),但也可以通过某种方式达到最终一致性。
基本可用
指分布式系统出现故障时,允许损失部分可用,即保证核心可用。电商大促时,为了应对访问激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。
软状态
指允许系统存在中间状态,而该中间状态不会影响系统整体可用性,分布式存储中一般一份数据至少有三个副本,允许不同节点间副本同步的延时就是软状态的体现。
最终一致性
指系统中的所有数据副本经过一段时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。
最终一致性模型根据其提供的不同保证可以划分为更多的模型,包括因果一致性和读自写一致性等。
三、分阶段提交
一个分布式事务(全局事务)可以被拆分为多个本地事务,运行在不同的AP和RM上。每个本地事务都具有ACID原则,只要保证全局事务中的每一个本地事务,要么全部成功,要么全部回滚。所以,需要通过CRM来通知各个本地事务,同步事务执行的状态。
1. 如何通知?
为了实现各个数据库之间的通信,必须有一个统一的规范,XA
就是X/Open DTP中通信中间件与TM联系的接口规范,定义了用于通知事务开始、提交、终止、回滚等接口,各个数据库厂商都必须实现这搁接口。
2. 角色划分
- 应用程序(AP):每一个微服务
- 事务管理器(TM):全局事务管理者
- 资源管理器(RM):一般指数据库
- 通信资源管理器(CRM):TM与RM间的通信中间件
二阶段提交协议(2pc)
将全局事务拆成两个阶段执行:
第一阶段:准备阶段,协调者向参与者发送请求,询问是否能提交本地事务。
第二阶段:执行阶段,根据参与者的回应,协调者发送commit/rollback请求,结束全局事务。
缺点:
- 单点故障:协调者挂掉,整个2PC逻辑就彻底不能运行。
- 阻塞问题:执行过程是同步的,各参与者在等待其他参与者响应的过程中都处于阻塞状态,大并发下有性能问题。
- 数据不一致:如果由于网络异常等意外导致只有部分参与者收到了commit请求,就会造成部分参与者提交了事务而其他参与者未提交的情况。
四、TCC
解决了2PC中的资源锁定和阻塞问题,减少资源锁定时间
TTC执行分为两个阶段:
第一阶段:准备阶段(try),资源的检测和预留
第二阶段:执行阶段(confirm/cancel),根据上一步的结果,判断下面的执行方法。如果所有事物参与者都成功,则执行confirm,否则执行cancel。
- try、confirm、cancel都是独立的事务,不受其他参与者影响,不会阻塞等待。
- try、confirm、cancel由程序员在业务层编码,锁粒度由代码控制。
支付系统,需支付RMB30元。库存系统,需减少库存1份。
第一阶段(try):此阶段执行完毕,提交事务。
- 检查余额,若余额充足则冻结用户30元(try成功),若余额不足,try失败。同理,库存也是一样。注意:此时只是冻结,并没有改变数据库数据。
第二阶段:此阶段执行完毕,提交事务。 - 提交(confirm):若余额try成功,库存也try成功。将冻结余额和库存都清空,并修改相应的剩余数(余额、库存数量)。
- 补偿(cancel):释放之前冻结的余额可库存数,并非回滚。余额和库存数量不变,修改冻结数据为0。
优点:
TCC执行的每一个阶段都会提交本地事务并释放锁,并不需要等待其它事务的执行结果。而如果其它事务执行失败,最后不是回滚,而是执行补偿操作。这样避免了资源的长期锁定和阻塞等待,执行效率比较高。
缺点:
- 代码侵入:需要人为编写代码实现try、confirm、cancel,代码侵入较多。
- 开发成本高:一个业务需要拆成3个步骤,分别编写业务实现。
- 安全性:cancel动作如果执行失败,资源无法释放,需要引入重试机制,而重试可能重复执行,还要考虑幂等问题。
使用场景
- 对事务有一定的一致性要求(最终一致)
- 对性能要求较高
- 开发人员具备较高的编码能力和幂等处理经验
五、可靠消息服务
设计思想是将远程分布式事务拆成一系列本地事务。
5.1 基本原理
- 事务发起者A执行本地事务
- 事务发起者A通过MQ将需要执行的事务信息发送给事务参与者B
- 事务参与者B接收到消息后执行本地事务
注意:
- 事务发起者A必须确保本地事务成功后,消息一定能发送成功。
- MQ必须保证消息正确投递和持久化保存
- 事务参与者B必须确保消息最终一定能消费,如果失败需要多次重试
- 事务B执行失败,会重试,但不会导致事务A回滚。
5.2 本地消息表
1)简化版本
事务发起者:
1. 开启本地事务 2. 执行事务相关业务 3. 发送消息到MQ 4. 把消息持久化到数据库,标记为已发送 5. 提交本地事务
事务接收者:
1. 接收消息 2. 开启本地事务 3. 处理事务相关业务 4. 修改数据库消息状态为已消费 5. 提交本地事务
额外定时任务:
定时扫描表中超时未消费消息,重新发送。
优点:
- 与tcc相比,实现方式较简单,开发成本低。
缺点:
- 数据一致性完全依赖于消息服务,因此消息服务必须可靠。
- 需要处理被动业务方的幂等问题
- 被动业务失败不会导致主动业务回滚,而是重试被动的业务
- 事务业务与消息发送业务耦合,业务数据与**消息表**要在一起
2)独立消息服务
引入一个独立的消息服务,来完成对消息的持久化、发送、确认、失败重试等一系列行为。
5.3 RocketMQ事务消息
RocketMQ本身自带了事务消息,可以保证消息的可靠性,原理是自带了本地消息表,与上面类型。
5.4 RabbitMQ的消息确认
并没有采用传统的本地消息表来确保消息不丢失,而是利用了消息的确认机制。
生产者确认机制:确保消息从生产者到MQ不会出现问题
- 消息生产者发送消息到RabbitMQ时,可以设置一个异步监听器,监听来自MQ的ACK
- MQ接收消息后,会返回一个回执给生产者
- 消息到达交换机后路由失败,会返回失败ACK
- 消息路由成功,持久化失败,会返回失败ACK
- 消息路由成功,持久化成功,会返回成功ACK
- 生产者提前编写好不同回执的处理方式
- 失败回执:等待一段时间后重新发送
- 成功回执:记录日志等行为
消费者确认机制:确保消息能够被消费者正确消费
- 消费者需要在监听队列的时候指定手动ACK模式
- RabbitMQ把消息投递给消费者后,会等待消费者ACK,接收ACK后才删除消息,如果没有接收ACK消息会一直保留在服务端,如果消费者断开连接或异常后,消息会投递给其它消费者。
- 消费者处理完消息,提交事务后,手动ACK。如果执行过程中抛出异常,则不会ACK,业务处理失败,等待下一条消息。
经过上面的两种确认机制,可以确保从消息生产者到消息消费者的安全,再结合生产者和消费者两端的本地事务,即可保证一个分布式事务的最终一致性。
优点:
- 业务相对简单,不需要编写3个阶段业务
- 是多个本地事务的结合,因此资源锁定周期短,性能好
缺点:
- 代码入侵
- 依赖MQ的可靠性
- 消息发起者可以回滚,但是消息参与者无法引起回滚
- 事务时效性差,取决于MQ消息发送是否及时,还有消息参与者的执行情况。
六、AT模式
AT模式是一种无侵入的分布式事务解决方案,是对TCC和二阶段提交模型的一种优化。AT模式下,用户只需要关心自己的“业务SQL”,用户的“业务SQL”作为第一阶段,Seata框架会自动生成事务的二阶段提交或回滚操作。
第一阶段:执行本地事务,并返回执行结果
第二阶段:根据执行结果,判断二阶段做法(commit或rollback)
==>>>>流程
name='li'
阶段一
update table set name='zhang' where id = 1;
Seata会解析SQL,根据条件查询该条记录作为镜像 select * form table where id = 1; (before image)
执行update SQL, 结果 name='zhang'
Seata再次查询 select * form table where id = 1; (after image)
阶段二
如果阶段一都是成功的,清空image
如果阶段一有一个失败,根据before image进行回滚。