写作本文的目的在于项目内技术的总结,侧重分享自己的理解,代码部分参考ChatGPT。
分布式事务:处理跨多个服务或数据库的业务操作,确保这些操作在分布式环境下具备原子性和一致性。
1、常见解决方案
1.1 重试 + 幂等性 + 兜底方案(数据库唯一索引)
-
重试:在操作失败时重新执行。通过重试机制,可以解决由于网络超时、服务短暂不可用等暂时性错误问题。
-
幂等性:幂等性指的是一个操作执行多次,结果仍然保持一致。对于幂等操作,即使多次重试,只有一次真正生效,后续的重试不会改变结果。
幂等性常见实现在后续文章补充?
1.2 本地消息表 + 定时任务
本地消息表:指在业务系统数据库中专门创建一张用于记录消息状态的表。
- 业务系统先将要发送的消息(包括消息内容和状态)记录到本地消息表中,
- 业务操作放在同一个数据库事务中提交。
定时任务:更新本地消息表中的消息状态。
- 通过定时任务或消息投递服务,将消息投递到目标消息队列,
- 当消息投递成功后,更新状态。
1.3 二阶段提交(对比Innodb引擎数据库)
二阶段提交分为“准备阶段”和“提交阶段”两个步骤。
引入了一个事务协调者TC角色,来管理各个参与者(就是各数据库资源)
1、准备阶段(除了事务的提交啥都做了) :
- 协调者(Coordinator)向所有参与者(Participants)发送
Prepare请求,询问是否可以提交事务。 - 然后每个参与者会返回响应告知协调者自己是否准备成功。
2、提交阶段(Commit Phase) :
- 根据收集的响应,如果有一个参与者响应准备失败那么就向所有参与者发送回滚命令,反之发送提交命令
优点:
- 能利用数据库自身的功能进行本地事务的提交和回滚,不需要自己实现业务回滚
缺点:
- 同步阻塞:在第一阶段执行了准备命令后,我们每个本地资源都处于锁定状态
- 单点故障:单点就是协调者,如果协调者挂了整个事务就执行不下去了
1.4 三阶段提交
- 相对于2PC,多了一个询问阶段(询问阶段单纯就是协调者去访问参与者,类似于你还好吗?能接请求不)
- 参与者处也引入了超时机制:这样在协调者挂了的情况下,如果已经到了提交阶段了,参与者等半天没收到协调者的情况的话就会自动提交事务
-
优点:
- 相较于2PC,减少了阻塞情况和单点故障问题。
-
缺点:
- 性能开销较大,并且引入了更多的通信步骤。
1.5 TCC(Try-Confirm-Cancel)
TCC(Try-Confirm-Cancel) 是一种补偿性事务解决方案,特别适用于跨多个微服务的事务管理。
- 第一阶段是资源检查预留阶段即Try
- 第二阶段是提交或回滚
举例说明:比如有一个扣款服务,我需要写 Try 方法,用来冻结扣款资金,还需要一个 Confirm 方法来执行真正的扣款,最后还需要提供 Cancel 来进行冻结操作的回滚,对应的一个事务的所有服务都需要提供这三个方法。可以看到本来就一个方法,现在需要膨胀成三个方法
-
优点:
- TCC 没有资源的阻塞,每一个方法都是直接提交事务的(如果出错是通过业务层面的 Cancel 来进行补偿,所以也称补偿性事务方法)
-
缺点:
- 对业务有很大的侵入
- 实现复杂度较高,需要针对每个操作编写
Try、Confirm和Cancel逻辑。
(Try、Cancel)因为有三个方法,所以在网络波动等问题时,出现三个方法执行顺序不一致的问题:
空回滚问题:Try方法由于网络问题没收到超时了,此时事务管理器就会发出 Cancel 命令
悬挂问题:Try方法由于网络阻塞超时触发了事务管理器发出了 Cancel 命令,但是执行了 Cancel 命令之后 Try 请求到了
1.6 Seata框架(todo)
Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的一种分布式事务解决方案。它支持多种分布式事务模式,包括 AT 模式(Automatic Transaction 模式) 、TCC 模式、Saga 模式和 XA 模式。
后续补充AT模式。
1.7 RocketMQ事务消息
MQ事务消息的核心思想是将业务操作和消息发送进行绑定,通过消息队列的事务机制来确保业务操作和消息投递的一致性。它的工作流程分为三个阶段:
-
消息发送前的半消息(Half Message)阶段:
- 生产者向消息队列发送半消息,此时该消息是处于 “预发送” 状态,消费者无法接收到这条消息。
- 半消息仅表示事务消息的一部分被发送到 MQ,但还没有正式提交。
-
本地事务执行阶段:
- 生产者执行本地事务操作,如数据库更新、订单创建等业务逻辑。
- 根据本地事务的执行结果,生产者向 MQ 提交“提交消息”或“回滚消息”。
- 如果本地事务成功,则提交消息到 MQ,消费者可以接收并处理该消息。
- 如果本地事务失败,则回滚消息,MQ 会丢弃该消息,消费者不会接收到消息。
-
事务消息确认阶段:
- MQ 根据生产者的提交或回滚操作,决定是否将消息投递给消费者。
- 如果生产者长时间没有提交或回滚消息,MQ 会定时对该消息发起事务状态检查(事务回查),以判断事务状态,从而决定消息的最终处理。
2、应用场景及选型
介绍笔者项目中实际场景选型,后续遇到具体场景再补充。
2.1、异步任务导出
介绍:在执行大数据量导出任务时,由于任务通常耗时较长,如果用户始终停留在等待页面,将导致较差的用户体验。为此,我们引入了异步任务列表,不仅可以减少用户的等待时间,还能方便用户查看和管理其所有的导出任务状态。
步骤:
-
用户提交数据导出请求,并将任务信息记录到本地消息表中(数据库)。
-
定时任务扫描本地消息表中的任务信息,处理任务并生成导出文件。
-
用户可以通过任务列表查看任务状态和导出结果。
2.2、审批后落库
介绍:在项目审批完成后,需要对审批后的数据从一个数据库到另一个数据库,此时就涉及到分布式事务,由于本项目已经使用RocketMQ来实现异步和解耦,此处选择RocketMQ事务消息 步骤:
-
用户完成审批后,消息提交到RocektMQ。
-
使用RocketMQ异步消息。