分布式事务理论
# 分布式事务理论
分布式场景下,多个服务同时对服务一个流程,比如电商下单场景,涉及支付、库存、订单生成、物流信息等。如果一个服务执行失败,或者网络不通引起的请求丢失,那么整个系统可能出现数据不一致的问题。
根部原因在于分布式操作,引起的本地事务无法保证数据的原子性。
# 1 什么是分布式事务?有哪些实现方案?
在分布式系统中,一次业务处理可能需要多个应用来实现,比如用户发一次下单请求,就设涉及到订单系统创建订单、库存系统减库存,而对于一次下单,订单创建与库存应该要同时成功或同时失败的,但分布式系统中,如果不做处理,可能出现订单创建成功,减库存失败,解决这类问题需要分布式事务。
- 本地消息表:创建订单时,将减库存消息加入在本地事务中,一起提交到数据库存入本地消息表,然后调用库存系统,如果调用成功则修改本地消息状态为成功,如果调用库存系统失败,则由后台定时任务从本地消息表中取出未成功的消息,重试调用库存系统。
- 消息队列:目前RocketMQ中支持事务消息,工作原理是:
- 生产者调用系统发送一条half消息到broker,half消息对消费者而言是不可见的。
- 在创建订单,根据订单成功与否,向broker发送commit或rollback
- 并且生产者订单系统还可以提供Broker回调接口,当Broker发现一段时间half消息没有收到任何操作命令,则会主动掉此接口来查询订单是否创建成功。
- 一旦half消息commit了,消费者库存系统就会来消费,如果消费成功,则消息销毁,分布式事务成功结束。
- 如果消费失败,则根据重试策略进行重试,最后还失败则进入死信队列,等待进一步处理。
- Seata:阿里开源分布式事务架构,支持AT、TCC等多种模式,底层都是基于两阶段提交理论来实现的。
# 1 分布式事务分类
刚性事务满足 CAP 的 CP 理论
柔性事务满足 BASE 理论
# 2 刚性事务
刚性事务通常无业务改造,强一致性,原生支持回顾/隔离性,低并发,适合短事务。
XA协议(2PC、JTA、JTS)、3PC,但由于同步阻塞,处理效率低,不适合大型网站分布式场景。
# 3 柔性事务
不要求强一致性,要求最终一致性,允许有中间状态,也就是Base理论。特点:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务。
分为:补偿型、通知型(异步确保型、最大努力通知型)
# 4 两阶段提交 2PC(标准XA模型)
2PC,Two-Phase Commit,两阶段提交。
阶段一:准备阶段(投票阶段)
1 事务询问,协调者向事务参与者发送事务内容,询问是否可以执行提交操作,并开始等待各参与者进行响应。
2 执行操作,个参与者节点,执行事务操作,并将Undo和Redo操作计入本机事务日志。
3 各参与者向协调者反馈事务问询的响应,成功执行返回Yes,否则返回No。
阶段二:提交阶段(执行阶段)
协调者在第二阶段决定是否最终执行事务提交操作,包含两种情形:
执行事务提交
所有参与者reply Yes,那么执行事务提交。
- 协调者所有参与者发送Commit请求;
- 参与者收到Commit请求后,会正式执行事务提交操作,并在完成提交操作之后,释放在整个事务执行期间占用的资源。
- 参与者在完成事务提交后,向协调者发送Ack消息确认。
- 协调者在收到所有参与者的Ack后,完成事务。
中断事务
事务总会出现意外,当存在某一参与者向协调者发送No响应,或者等待超时。协调者只要无法收到所有参与的Yes响应,就会中断事务。
- 协调者向所有参与者发送Rollback请求。
- 参与者收到请求后,利用本机Undo信息,执行Rollback操作。并在回滚结束后释放该事务所占用的系统资源。
- 参与者在完成回滚操作后,向协调者发送Ack消息。
- 协调者收到所有参与者的回滚Ack消息后,完成事务中断。
特点:适合单体应用,不适合高并发。缺点很多,使用场景少。
# 5 两阶段提交的问题
- 性能问题:执行过程中间,节点处于阻塞状态。各个操作数据库的节点此时都占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知进行全局提交,参与者进行本地事务提交后才会释放资源。这样的过程会比较漫长,对性能影响较大。
- 协调者单点故障:协调者是XA模型的核心,一旦事务协调者节点挂掉,会导致参与者收不到提交或回滚的通知,从而导致参与者节点始终处于事务无法完成的中间状态。
- 丢失消息导致的数据不一致问题:在第二阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分参与者没收到提交消息,那么就会导致节点间数据的不一致问题。
# 6 三阶段提交 3PC
分为:CanCommit、PreCommit、doCommit阶段
阶段一:事务询问 CanCommit
- 协调者向所有参与者发送canCommit的请求,询问是否可以执行事务提交;
- 正常情况下,如果参与者认为可以顺利执行事务,返回Yes,进入预备状态,否则返回No。
不像2pc第一阶段就开始锁表,3pc的阶段一是为了先排查个别不具备提交事务能力的参与者。
阶段二:事务执行 PreCommit
在本阶段,协调者根据上一阶段的反馈情况来决定是否可以执行事务的PreCommit操作,两种可能:
执行事务与提交:所有参与者响应Yes
- 协调者向所有节点发出PreCommit请求,并进入prepared阶段。
- 参与者收到PreCommit请求后,会执行事务操作,并将Undo和Redo日志写入本机事务日志。
- 将事物操作的反馈以Ack响应形式发送给协调者,等待最终指令。
中断事务:任意参与者向协调者发送No响应,或者等待超时时,即可以中断事务,协调者向所有参与者发送Abort请求。
阶段三:事务提交 doCommit
提交事务,存在两种可能:
提交阶段
- 假如协调者收到了所有参与者的Ack响应,那么将从预提交转换到提交状态,并向所有参与者发送doCommit请求。
- 参与者收到doCommit请求后,会正式执行事务提交操作,并在完成提交操作后释放占用资源。
- 参与者将完成事务提交后,向协调者发送Ack消息
- 协调者接收到所有参与者的Ack消息后,完成事务。
中断事务
假设协调者接收到任一参与者发送的No响应,或超时,则会中断事务:
- 协调者向所有参与者发送Abort请求
- 参与者收到abort请求后,会利用阶段二中的Undo消息执行事务回滚后释放占用资源。
- 参与者在完成回滚后返回协调者发送Ack消息
- 协调者接收到所有参与者反馈的Ack消息后,完成事务中断。
由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大。
# 7 通知型事物
通知型事物的主流实现是通过MQ来通知其他事物参与者自己事物的执行状态,将事物参与者进行解耦,各参与者都可以异步执行,所以通知型事务又被称为异步事务。
通知型事务主要适用于那些需要异步更新数据,并且对数据的实时性要求较低的场景,主要包含:
异步确保型事务:适用于内部系统的数据最终一致性保障,因为内部相对比较可控,如订单和购物车、收获与清算、支付与结算等等场景。
最大努力通知:主要用于外部系统,因为外部的网络环境更加复杂和不可信,所以只能最大努力去通知实现数据最终一致性,比如充值平台与运营商、支付对接等等跨网络系统级别对接。
# 8 异步确保事务
指将一系列同步的事务操作修改为基于消息队列异步执行的操作,来避免分布式事务中同步阻塞带来的数据操作性能的下降。
# 9 异步确保事务—MQ事务消息方案
- 事务发起方首先发送半消息到MQ
- MQ通知发送方消息发送成功
- 在发送版消息成功后执行本地事务
- 根据本地事务的执行结果返回commit或者rollback
- 如果消息是rollback,MQ将丢弃该消息不投递;如果是commit,MQ将消息发送给消息订阅方。
- 订阅方根据消息执行本地事务
- 订阅方执行本地事务成功后,将MQ中该消息标记为以消费。
- 如果执行本地事务过程中,执行段挂掉或超时,MQ服务器将不停询问producer来获取事务状态。
- Consumer端的消费成功机制有MQ保证。
# 9 最大努力通知
最大努力通知方案的目标,就是发起通知方通过一定的机制,最大努力将业务处理结果通知到接收方。
通过引入定期校验机制实现最终一致性,对业务的侵入性较低,适合于对最终一致性敏感度比较低、业务链路比较短的场景。
主要用于外部系统,因为外部的网络环境更加复杂和不可信,所以只能尽最大努力去通知实现数据最终一致性,比如充值平台与运营商、支付对接、商户通知等等跨平台、跨企业的系统间业务交互场景。
特点:
- 业务主动发再完成业务处理后,向业务被动方发送通知消息,允许存在消息丢失。
- 业务主动发提供多挡位时间间隔,用于失败重试调用业务被动方的接口;在通知N次后就不再通知,报警+记日志+人工介入。
- 业务被动方提供幂等的服务接口,防止通知重复消息。
- 业务主动方需要定期校验机制,对业务数据进行兜底;防止业务被动方无法履行责任时进行业务回滚,确保数据最终一致性。
# 10 最大努力通知—MQ事务消息方案
- 业务的主动方,在完成业务处理后,向业务活动的被动方发送消息,允许消息丢失。
- 主动方可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,知道通知N次后不再通知。
- 主动方提供校对查询接口给被动方按需校对查询,用于恢复丢失的业务消息。
- 业务活动的被动方如果正常接收了数据,就正常返回相应,并结束事务。
- 如果被动方没有正常接收,根据定时策略,向业务主动方查询,恢复丢失的业务消息。
# 11 最大努力通知事务与异步确保型事务的区别?
- 从参与者来说:最大努力通知事务适用于跨平台、跨企业的系统间业务交互;异步确保型事务更适用于同网络体系的内部服务交付。
- 从消息层面说:最大努力通知事务需要主动推送并提供多档次时间的重试机制来保证数据的通知;而异步确保型事务只需要消息消费者主动去消费。
- 从数据层面说:最大努力通知事务还需额外的定期校验机制对数据进行兜底,保证数据的最终一致性;而异步确保型事务只需保证消息的可靠投递即可,自身无需对数据进行兜底处理。
# 12 什么是补偿模式?
补偿模式使用一个额外的协调服务来协调各个需要保证一致性的业务服务,协调服务按顺序调用各个业务微服务,如果某个业务服务调用异常(包括业务异常和技术异常)就取消之前所有已经调用成功的业务服务。
# 13 TCC事务模型组成
TCC(Try-Confrim-Cancel)分布式事务模型包括三部分:
- 主业务服务:业务活动的发起方,服务的编排者,负责发起并完成整个业务活动。
- 从业务服务:业务活动的参与方,负责TCC业务操作,实现初步操作Try、确认操作Confirm、取消操作Cancel三个接口,供主业务服务调用。
- 业务活动管理者:管理控制整个业务活动,包括记录维护TCC全局事务的事务状态和每个业务服务的子事务状态,并在业务活动提交时调用所有业务服务的Confirm操作,在业务活动取消时调用所有从业务操作的Cancel操作。
# 14 TCC工作流程
初步操作Try:调用Try接口,尝试执行业务,完成所有业务检查,预留必须的业务资源。
确认操作Confirm:真正执行的业务逻辑,不作任何业务检测,只使用Try阶段预留的业务资源。因此, 只要Try操作成功,Confirm必须能成功。另外,Confirm操作需满足幂等性,保证一笔分布式事务有且只能成功一次。
取消操作Cancel:释放Try二段预留的业务资源。同样的,Cancel操作也需要满足幂等性。
Confirm和Cancel阶段是互斥的,只能进入其中一个,并且都要满足幂等性,允许失败重试。
# 15 TCC事务案例
场景:某笔订单完成时,同时扣掉用户的信息,但交易未完成,也未被取消时,不能让客户看到钱变少了。
引入TCC,流程如下:
- 订单服务创建订单
- 订单服务发送远程调用到现金服务,冻结客户的现金
- 提交订单服务数据
- 订单服务发送远程调用到现金服务,扣除客户冻结的现金。
以上为正常的流程,若为异常流程,则需要发送远程调用请求到现金服务,撤销冻结的金额。