分布式事务实战汇总

分布式方案

2PC 两阶段提交协议

  • 两阶段提交协议:事务管理器分两个阶段来协调资源管理器,第一阶段准备资源,也就是预留事务所需的资源,如果每个资源管理器都资源预留成功,则进行第二阶段资源提交,否则协调资源管理器回滚资源。
  • 我的一个实践是 seata的AT模式demo如下
    基于springcloud nacos juejin.im/post/5f18ce…
    基于dubbo nacos juejin.im/post/5f18e1…
  • 基本流程图如下。
  • seata的AT模式下的2pc方案对代码侵入性较小,回滚、确认的操作均由框架自动生成,但是会有行锁的存在。

最终一致性方案

方案一基于RocketMQ 事务消息

rocketmq有一个事务消息 具体来说,就是把消息的发送分成了2个阶段:Prepare阶段和确认阶段。

  • (1) A系统发送Prepared消息
  • (2) A系统执行本地事务
  • (3) A系统根据update DB结果成功或失败,Confirm或者取消Prepared消息。
  • (4) rocketmq轮询本地所有的未确认的prepare消息检查本地事务执行状态。
  • (5) B系统收到confirm的prepare消息进行执行本地事务,如果失败返回稍后重试,直至成功。
  • 流程图如下
  • 这种最终一致性方案,比如a给b打钱,当a的钱扣除成功之后(扣除失败则本次失败),b则必须要加钱成功,如果失败,则要直到补偿成功。消费端消费失败怎么办? 消费失败了,重试,还一直失败怎么办?是不是要自动回滚整个流程? 答案是人工介入。从工程实践角度讲,这种整个流程自动回滚的代价是非常巨大的,不但实现复杂,还会引入新的问题。比如自动回滚失败,又怎么处理? 对应这种极低概率的case,采取人工处理,会比实现一个高复杂的自动化回滚系统,更加可靠,也更加简单。
  • 我的一个实践是 demo如下
    基于springcloud nacos juejin.im/post/5f1c0a…

方案二 本地做状态记录+单笔查询(对账)+调用方补偿重试

同样以上面的下单为例:调用方调订单系统下单,系统B扣库存,如何保证2个同时成功? 用户下单的时候,订单状态未 init,如果B系统接口是不幂等的(B系统扣库存时候需要记录相应订单流水)。 当用户调用系统B进行扣库存时候成功时候,状态修改为success 如果发现系统中某个订单状态一直为init状态,说明这条流水有问题,通过定时器定时load出来这些数据。

  • 分俩情况:

如果B系统接口是不幂等的需要调用B系统单笔查询接口查询是否已经扣过库存,根据查询结果修改订单状态。如果没有则进行补偿调用B系统根据调用结果,修改状态为successs或者fail 直至成功为止。

如果B系统接口幂等的 则进行补偿调用B系统根据调用结果,修改状态为successs或者fail 直至成功为止。

订单id 订单状态
O_78923a2 init/success/fail

方案三 本地做事务流水记录+调用方补偿重试

调用方维护1张事务状态表(或者说事务日志,日志流水),每次调用之前,落盘1条事务流水,生成1个全局的事务ID。表结构大致如下: 为止。

事务id 入参 事务状态
O_78923a2 a init/success/fail

,发现某条流水,在过了某个时间之后(假设1次事务执行成功通常最多花费30s),状态仍然是Init,那就说明这条流水有问题。就重新调用系统A,系统B,保证这条流水的最终状态是Success。当然,系统A, 系统B根据这个全局的事务ID,做幂等,所以重复调用也没关系。 这就是通过同步调用 + 后台任务异步补偿,最终保证系统一致性。

TCC 两阶段提交协议

TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。

TCC的不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、cancel三个操作。此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。

猜你喜欢

转载自juejin.im/post/5f1c17bae51d4534ba1a03a8