分布式事务介绍

事务是指要满足acid条件的一系列操作。事务内的操作要么同时成功要么同时失败。单体数据库的事务性由存储引擎保证,在分布式应用中,服务在不同的机器,由于网络、应用不可靠,保证操作的全局事务性十分困难,需要一定的妥协。保证分布式事务实质是保证不同数据库的数据一致性。
分布式场景下,满足绝对ACID原则的刚性事务性能会有较大损耗,因此从强一致性变为基于BASE理论的最终一致性,即数据可以不一致,但最终会达到一致的状态,满足柔性事务。

典型场景:

  • 转账:A账户增加10元,B账户减少10元
  • 商品购买:下单-扣账户金额-扣库存-发货
  • 异常:如果是调用第三方服务,下游接口超时或失败则重试或回滚事务

首先要明确哪些业务需要保证事务性,哪些需要同步保证,哪些需要异步保证。能不用分布式事务尽量不用。

XA协议/二阶段提交
XA 规范是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。描述了全局的事务管理器与局部的资源管理器之间的接口。目的是允许的多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使 ACID 属性跨越应用程序而保持有效。使用两阶段提交(2PC,Two-Phase Commit)来保证所有资源同时提交或回滚任何特定的事务。在上世纪 90 年代初就被提出。目前,几乎所有主流的数据库都对 XA 规范 提供了支持。

流程:

  • 第一阶段(prepare):事务管理器向所有本地资源管理器发起请求,询问是否是 ready 状态,所有参与者都将本事务能否成功的信息反馈发给协调者;
  • 第二阶段 (commit/rollback):事务管理器根据所有本地资源管理器的反馈,通知所有本地资源管理器,步调一致地在所有分支上提交或者回滚。

问题:

  • 数据源可能不支持XA协议,如es,redis
  • 长事务影响性能,同步阻塞资源
  • 事务管理器容易出现单点故障,导致整个系统不可用
  • 如果只有部分资源管理器commit,会使数据不一致

除了基于xa协议的分布式事务,还有基于最终一致性的分布式事务方案,即允许数据有不一致的状态,但最终会达到一致
理想的方案:业务入侵小,需要手写的代码少,性能高

解决方案:
业务是否有入侵:

  • 业务无入侵

    • XA,seata
  • 业务有入侵

    • TCC 基于消息队列/本地消息表 saga

一致性保证:

  • 强一致性

    • XA,需要锁定资源
  • 最终一致性,可能会读到脏数据,遵循base理论,实现柔性事务

    • 事务补偿:TCC,saga
    • 事务回滚:Seata
    • 消息日志:基于消息队列/本地消息表
    • 本质是感知子事务的执行状态,子事务要有回退/重试的逻辑(手写或代理实现)

参考:
https://xiaomi-info.github.io...

最终一致性大致思路:

  • 服务A调用下游服务B,执行业务sql的同时插入调用日志表,调用状态为已创建。这两个sql保证是一个事务,保证有迹可查。然后调用B。调用可为rpc或mq异步调用
  • 下游服务B如果执行成功,调用服务A接口,修改调用记录表,调用状态改为处理成功
  • 通过定时任务扫描调用记录表,状态不为处理成功的进行重发。调用记录表的存在确保消息一定被下游处理,保证数据最终一致。下游可能会处理多次重复的消息,因此要保证接口幂等。

异常情况:

  • 调用下游服务失败,调用状态为已创建
  • 调用成功,但由于网络原因下游未收到,调用状态为已创建
  • 下游处理成功,但未返回,调用状态为已创建

这些异常情况都会被定时任务扫描并进行重试
对于seata,调用日志表为回滚日志表,如果全局事务的任一子事务失败,则全局回滚。

基于本地消息表
分布式事务介绍_第1张图片

步骤:

  • 当系统 A 被其他系统调用发生数据库表更操作,首先会更新数据库的业务表,其次会往相同数据库的消息表中插入一条数据,两个操作发生在同一个事务中
  • 系统 A 的脚本定期轮询本地消息往 mq 中写入一条消息,如果消息发送失败会进行重试
  • 系统 B 消费 mq 中的消息,并处理业务逻辑。如果本地事务处理失败,会在继续消费 mq 中的消息进行重试,如果业务上的失败,可以通知系统 A 进行回滚操作

本地消息表实现的条件:

  • 消费者与生成者的接口都要支持幂等
  • 生产者需要额外的创建消息表
  • 需要提供补偿逻辑,如果消费者业务失败,需要生产者支持回滚操作

容错机制:

  • 步骤 1 失败时,事务直接回滚
  • 步骤 2、3 写 mq 与消费 mq 失败会进行重试
  • 步骤 3 业务失败系统 B 向系统 A 发起事务回滚操作

此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。
本地消息表保证消息一定会被下游服务消费,至少消费一次,因此下游服务的接口要保证幂等。

参考:
https://juejin.im/post/684490...
https://jeremyxu2010.github.io/2020/03/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B8%AD%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E6%96%B9%E6%A1%88/
https://houbb.github.io/2018/...
https://www.pianshen.com/article/36841500709/
https://segmentfault.com/a/11...

基于可靠的消息服务
分布式事务介绍_第2张图片

  • 第一阶段Prepared消息,会拿到消息的地址。
  • 第二阶段执行本地事务。
  • 第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。消息接受者就能使用这个消息。
  • 如果确认消息失败,在RocketMq Broker中提供了定时扫描没有更新状态的消息,如果有消息没有得到确认,会向消息发送者发送消息,来判断是否提交,在rocketmq中是以listener的形式给发送者,用来处理。

通过rocketmq本身的事务性保证执行业务逻辑和消息发送同时成功或失败,下游数据至少被消费一次

参考:
https://mp.weixin.qq.com/s/Uc...

TCC
TCC是一种两阶段型模式,只有在所有的服务的第一阶段(try)都成功的时候才进行第二阶段确认(Confirm)操作,否则进行补偿(Cancel)操作,而在try阶段是不会进行真正的业务处理的。
TCC模式的具体流程为两个阶段:

  1. Try,业务服务完成所有的业务检查,预留必需的业务资源
  2. 如果Try在所有服务中都成功,那么执行Confirm操作,Confirm操作不做任何的业务检查(因为try中已经做过),只是用Try阶段预留的业务资源进行业务处理;否则进行Cancel操作,Cancel操作释放Try阶段预留的业务资源。

TCC模式跟纯业务补偿模式相比,需要每个服务都需要实现Confirm和Cancel两个接口,因此落地实施上比纯业务补偿模式复杂一些,但好处是数据一致的实时性高,因此其在很多金融、电商场景中大量采用。
在事务补偿方案中,由于上游服务依赖于下游服务的结果,考虑到上下游服务间网络有可能是不稳定的,因此业务接口、补偿接口(Saga模式中)和Try接口、Confirm接口、Cancel接口(TCC模式中)均有可能会被多次调用,因此这些接口在实现时需要考虑幂等性。

参考:
https://www.cnblogs.com/jajia...
https://www.bytesoft.org/

比较三种最终一致性的分布式事务方案,在执行业务sql时都要在同一事务中插入操作/回滚记录,保证相关事务异常时回滚或重试,当全局事务成功后删除记录。

你可能感兴趣的