目录

分布式事务

数据库事务-本地事务

传统的单服务器,单关系型数据库下的事务,就是本地事务。本地事务由资源管理器管理,JDBC事务就是一个非常典型的本地事务。

事务ACID特性的实现思想

  • 原子性:是使用 undo log来实现的,如果事务执行过程中出错或者用户执行了rollback,系统通过undo log日志返回事务开始的状态。
  • 持久性:使用 redo log来实现,只要redo log日志持久化了,当系统崩溃,即可通过redo log把数据恢复。
  • 隔离性:通过锁以及MVCC,使事务相互隔离开。
  • 一致性:通过回滚、恢复,以及并发情况下的隔离性,从而实现一致性。

分布式事务

分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。

微服务架构下的分布式事务

用户下单购买礼物,礼物数据库、金币数据库、订单数据库在不同节点上,用本地事务是不可以的,那么如何保证不同数据库(节点)上的数据一致性呢?这就需要分布式事务啦

分库分表下的分布式事务

随着业务的发展,数据库的数据日益庞大,超过千万级别的数据,我们就需要对它分库分表(以前公司是用mycat分库分表,后来用sharding-jdbc)。一分库,数据又分布在不同节点上啦,比如有的在深圳机房,有的在北京机房~你再想用本地事务去保证,已经无动于衷啦~还是需要分布式事务啦。 比如A转10块给B,A的账户数据是在北京机房,B的账户数据是在深圳机房。流程如下:

分布式事务理论(CAP和BASE)

如果说到事务,ACID是传统数据库常用的设计理念,追求强一致性模型,关系数据库的ACID模型拥有高一致性+可用性,所以很难进行分区,所以在微服务中ACID已经是无法支持,我们还是回到CAP去寻求解决方案,不过根据上面的讨论,CAP定理中,要么只能CP,要么只能AP,如果我们追求数据的一致性而忽略可用性这个在微服务中肯定是行不通的,如果我们追求可用性而忽略一致性,那么在一些重要的数据(例如支付,金额)肯定出现漏洞百出,这个也是无法接受。所以我们既要一致性,也要可用性。

都要是无法实现的,但我们能不能在一致性上作出一些妥协,不追求强一致性,转而追求最终一致性,所以引入BASE理论,在分布式事务中,BASE最重要是为CAP提出了最终一致性的解决方案,BASE强调牺牲高一致性,从而获取肯用性,数据允许在一段时间内不一致,只要保证最终一致性就可以了。

这就是分布式事务等理论基础,即实现最终一致性。

分布式事务的几种解决方案

2PC(二阶段提交)方案/XA

事务的提交分为两个阶段:准备阶段和提交执行方案。

二阶段提交成功的情况

  • 准备阶段,事务管理器向每个资源管理器发送准备消息,如果资源管理器的本地事务操作执行成功,则返回成功。
  • 提交执行阶段,如果事务管理器收到了所有资源管理器回复的成功消息,则向每个资源管理器发送提交消息,RM 根据 TM 的指令执行提交。

二阶段提交失败的情况

  • 准备阶段,事务管理器向每个资源管理器发送准备消息,如果资源管理器的本地事务操作执行成功,则返回成功,如果执行失败,则返回失败。
  • 提交执行阶段,如果事务管理器收到了任何一个资源管理器失败的消息,则向每个资源管理器发送回滚消息。资源管理器根据事务管理器的指令回滚本地事务操作,释放所有事务处理过程中使用的锁资源。

二阶段提交优缺点

  • 单点问题:如果事务管理器出现故障,资源管理器将一直处于锁定状态。
  • 性能问题:所有资源管理器在事务提交阶段处于同步阻塞状态,占用系统资源,一直到提交完成,才释放资源,容易导致性能瓶颈。
  • 数据一致性问题:如果有的资源管理器收到提交的消息,有的没收到,那么会导致数据不一致问题。

TCC(Try、Confirm、Cancel)

TCC 采用了补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。

TCC(Try-Confirm-Cancel)模型

TCC(Try-Confirm-Cancel)是通过对业务逻辑的分解来实现分布式事务。针对一个具体的业务服务,TCC 分布式事务模型需要业务系统都实现一下三段逻辑:

try阶段:

尝试去执行,完成所有业务的一致性检查,预留必须的业务资源。

Confirm阶段:

该阶段对业务进行确认提交,不做任何检查,因为try阶段已经检查过了,默认Confirm阶段是不会出错的。

Cancel 阶段:

若业务执行失败,则进入该阶段,它会释放try阶段占用的所有业务资源,并回滚Confirm阶段执行的所有操作。

TCC优缺点

TCC方案让应用可以自定义数据库操作的粒度,降低了锁冲突,可以提升性能,但是也有以下缺点:

  • 应用侵入性强,try、confirm、cancel三个阶段都需要业务逻辑实现。
  • 需要根据网络、系统故障等不同失败原因实现不同的回滚策略,实现难度大,一般借助TCC开源框架,ByteTCC,TCC-transaction,Himly。

在 Try 阶段,是对业务系统进行检查及资源预览,比如订单和存储操作,需要检查库存剩余数量是否够用,并进行预留,预留操作的话就是新建一个可用库存数量字段,Try 阶段操作是对这个可用库存数量进行操作。 基于 TCC 实现分布式事务,会将原来只需要一个接口就可以实现的逻辑拆分为 Try、Confirm、Cancel 三个接口,所以代码实现复杂度相对较高。

TCC 需要事务接口提供 try, confirm, cancel 三个接口,提高了编程的复杂性。依赖于业务方来配合提供这样的接口,推行难度大,所以一般不推荐使用这种方式。

本地消息表

核心思想就是将分布式事务拆分成本地事务进行处理

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

本地消息表实现的条件:

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

容错机制:

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

此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。

实际方案

跨行转账可通过该方案实现。

用户 A 向用户 B 发起转账,首先系统会扣掉用户 A 账户中的金额,将该转账消息写入消息表中,如果事务执行失败则转账失败,如果转账成功,系统中会有定时轮询消息表,往 mq 中写入转账消息,失败重试。mq 消息会被实时消费并往用户 B 中账户增加转账金额,执行失败会不断重试。

最大努力通知

最大努力通知是最简单的一种柔性事务,适用于一些最终一致性时间敏感度低的业务,且被动方处理结果 不影响主动方的处理结果。

方案流程

  1. 系统 A 本地事务执行完之后,发送个消息到 MQ;
  2. 这里会有个专门消费 MQ 的服务,这个服务会消费 MQ 并调用系统 B 的接口;
  3. 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B, 反复 N 次,最后还是不行就放弃。

实际方案

最大努力通知最常见的场景就是支付回调,支付服务收到第三方服务支付成功通知后,先更新自己库中订单支付状态,然后同步通知订单服务支付成功。如果此次同步通知失败,会通过异步脚步不断重试地调用订单服务的接口。

可靠消息最终一致性

大致流程如下:

/images/distributed/mq-message.jpg
可靠消息最终一致性

  1. A 系统先向 mq 发送一条 prepare 消息,如果 prepare 消息发送失败,则直接取消操作
  2. 如果消息发送成功,则执行本地事务
  3. 如果本地事务执行成功,则想 mq 发送一条 confirm 消息,如果发送失败,则发送回滚消息
  4. B 系统定期消费 mq 中的 confirm 消息,执行本地事务,并发送 ack 消息。如果 B 系统中的本地事务失败,会一直不断重试,如果是业务失败,会向 A 系统发起回滚请求
  5. mq 会定期轮询所有 prepared 消息调用系统 A 提供的接口查询消息的处理情况,如果该 prepare 消息本地事务处理成功,则重新发送 confirm 消息,否则直接回滚该消息

该方案与本地消息最大的不同是去掉了本地消息表,其次本地消息表依赖消息表重试写入 mq 这一步由本方案中的轮询 prepare 消息状态来重试或者回滚该消息替代。其实现条件与余容错方案基本一致。目前市面上实现该方案的只有阿里的 RocketMq。

实际方案

目前市面上支持该方案的 mq 只有阿里的 rocketmq, 该方案应用场景也比较多,比如用户注册成功后发送邮件、电商系统给用户发送优惠券等需要保证最终一致性的场景

Saga事务

核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。

参考文章