UP | HOME

分布式事务

Table of Contents

1 分布式事务

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

  1. 事务参与者 (APP)
    • 前端处理请求的应用
  2. 事务管理器 (TM)
    • 后台协调事务的应用
  3. 资源服务器 (RM)
    • 一般指的是数据库中的数据

2 XA 协议

XA 协议是一个基于数据库的分布式事务协议,其分为两部分

  • 事务管理器和本地资源管理器
  • 事务管理器作为一个全局的调度者, 负责对各个本地资源管理器统一号令提交或者回滚
  • 如今 Oracle, Mysql 等数据库均已实现了 XA 接口

3 2PC & 3PC

3.1 提交模型

2PC 和 3PC 参与事务提交的处理流程如下,事务包含如下三类角色

  1. 单业务发起方 APP
  2. 单事务管理器 TM
  3. 多资源管理器 RM
[APP] -> [  TM  ] -> [RM_1]
           |  |
           |  +----> [RM_2]
           |
           +-------> [RM_3]

3.2 2PC (two-phase-commit) 两阶段事务

2PC 的具体的实现流程如下

  1. Prepare 阶段
    • APP 向 TM 发送请求,请求执行一个操作
    • TM 向所有参与的 RM 发送 预请求, RM 需要报告能否执行操作
      • TM 可以向 MQ 消息队列发送个半消息 (Half Message)
      • RM 打开本地事务,执行到待提交的阶段
      • 然后反馈能否成功执行事务
  2. Commit 阶段
    • TM 接收所有 RM 的报告
    • TM 接收到所有的报告,如果所有 RM 表示可以成功执行
      • TM 发送全局提交 (Global Commit) 信号
      • 所有 RM 提交事务
    • TM 接收到所有的报告,如果有一个 RM 表示不能成功执行
      • TM 发送全局回滚 (Global Commit) 信号
      • 所有 RM 回滚事务

2PC 存在问题

  1. RM 的事务一直挂起可能存在 性能问题
  2. TM 是单点的,可能存在 单点故障
  3. Commit 阶段出现消息丢失会导致数据不一致

3.3 3PC (three-phase-commit) 三阶段事务

3PC 在 2PC 的基础上增加了 CanCommit 阶段

  1. 增加了 超时机制, 可以预先判断任何 RM 是否宕机,减少超时交互的成本
  2. 有效地解决了 RM 的 单点故障 问题

3PC 的具体的实现流程如下

  1. CanCommit 阶段
    • APP 向 TM 发送请求,请求执行一个操作
    • TM 向所有参与的 RM 发送 自检操作, 所有 RM 完成自检回复 Yes/No
      • 注意,这里 RM 并没有开启本地事务
    • 如果 TM 收到任何一个 RM 回复 No,事务操作终止
    • 如果任何一个 TM 超时,事务操作终止
  2. PreCommit 阶段
    • 在 CanCommit 阶段中 TM 都回复 Yes
    • 接着进行 PreCommit 阶段,该阶段类似 2PC 的 Prepare 阶段
    • TM 向所有参与的 RM 发送 预请求, RM 需要报告能否执行操作
      • 所有 RM 执行本地事务,记录 Undo/Redo 日志
      • 如果 RM 确认可以执行,向 TM 返回 ACK 确认
  3. doCommit 阶段
    • 该阶段类似于 2PC 的 Commit 阶段
    • TM 接收所有 RM 的报告
    • TM 接收到所有的报告,如果所有 RM 表示可以成功执行
      • TM 发送全局提交 (Global Commit) 信号
      • 所有 RM 提交事务
    • TM 接收到所有的报告,如果有一个 RM 表示不能成功执行
      • TM 发送全局回滚 (Global Commit) 信号
      • 所有 RM 回滚事务

4 TCC (Try-Confirm-Cancel) 补偿事务

4.1 提交模型

TCC 参与事务提交的处理流程如下,事务包含如下三类角色

  1. 单业务发起方 APP
  2. 单事务管理器 TM
  3. 多资源管理器 RM
    • 每个 RM 需要实现 Try, Confirm, Cancel 接口
[APP] ->[  TM  ]-> [RM_1]
          |  |
          |  +---> [RM_2]
          |
          +------> [RM_3]

4.2 TCC 三个操作说明

  1. Try 操作
    • 完成业务检查,预留业务所需的资源
  2. Confirm 操作
    • 执行业务逻辑
  3. Cancel 操作
    • 释放 Try 操作预留的业务资源

4.3 TCC 实践案例

  1. 汇款服务
    • Try: 处理预扣款逻辑
      • 检查 A 账户的有效性
      • 检查 A 账户的余额是否充足
      • 从 A 账户中扣减 100 元,并将状态置为 "转账中"
      • 预留扣减资源,将 "从 A 账户向 B 账户转账 100 元" 这个事件存入消息或日志
    • Confirm: 不做任何操作
    • Cancel: A 账户增加 100 元;从日志或消息中释放扣减资源
  2. 收款服务
    • Try: 检查 B 账户的有效性
    • Confirm: 读取日志或者消息, B 账户增加 100 元;从日志或消息中释放扣减资源
    • Cancel: 不做任何操作

由此可以看出,TCC 模型对业务的侵入性较强,改造的难度较大

4.4 TCC 缺点

  1. 应用侵入性强
    • TCC 由于基于在业务层面, 开发时需要在业务层面保证事务的有效性
    • 至使每个操作都需要有 Try, Confirm, Cancel 三个接口
  2. 开发难度大
    • 代码开发量很大
    • Confirm 和 Cancel 接口还必须实现幂等性才能保证数据一致性

5 本地消息表方法

PK Status
1001 0
1002 1
1005 0
1007 1
  1. 就是上游服务先执行操作, 将操作记录到数据库中的本地消息表, 并且此时这条操作 记录的 Status 设置为 0, 也就是未通知成功
  2. 然后将操作记录封装成 Kafka 消息发送到消息队列, 下游系统接受到, 进行消费, 然 后消费成功后调用上游服务的接口, 通知他消费成功了, 上游系统将本地消息表中这 条记录的 Status 设置为 1, 代表通知成功
  3. 并且上游系统会定时扫描本地消息表, 将 Status 为 0 的操作记录, 封装成 Kafka 消息, 发送到消息队列
  4. 并且下游系统是通过消息中操作记录的主键 ID 来防止不重复消费, 保证幂等性的。 就是消费消息时, 发送操作记录的 ID 已经在数据库中存在了, 就代表之前已经处理 过了, 不处理这条消息了

6 可靠消息最终一致

6.1 传统事务消息实现

传统事务消息实现,一种思路是依赖于 AMQP 协议用来确保消息发送成功,AMQP 模式下 需要在发送在发送事务消息时进行两阶段提交,首先进行 txselect 开启事务,然后再 进行消息发送,最后进行消息的 commit 或者是 rollback。这个过程可以保证在消息发 送成功的同时本地事务也一定成功执行,但事务粒度不好控制,而且会导致性能急剧下 降,同时依然无法解决本地事务执行与消息发送的原子性问题。

还有另外一种思路,就是通过保证多条消息的同时可见性来保证事务一致性。但是此类 消息事务实现机制更多的是用到 consume-transform-produce 场景中,其本质还是用来 保证消息自身事务,并没有把外部事务包含进来。

6.2 RocketMQ 事务消息

RocketMQ 事务消息设计则主要是为了解决 Producer 端的消息发送与本地事务执行的原 子性问题,RocketMQ 的设计中 broker 与 producer 端的双向通信能力,使得 broker 天生可以作为一个事务协调者存在;而 RocketMQ 本身提供的存储机制,则为事务消息 提供了持久化能力;RocketMQ 的高可用机制以及可靠消息设计,则为事务消息在系统在 发生异常时,依然能够保证事务的最终一致性达成。

6.3 可靠消息最终一致的实现

RocketMQ 4.3 以后,增加了对分布式事务的支持,就是将事务的执行状态保存在 RocketMQ 中,由 RocketMQ 去负责将 commit 状态的消息推送给下游系统

事务消息作为一种异步确保型事务, 将两个事务分支通过 MQ 进行异步解耦,RocketMQ 事务消息的设计流程同样借鉴了两阶段提交理论,整体交互流程如下图所示

mq-distrubute-transaction.png

  1. 事务发起方首先发送 prepare 消息到 MQ
  2. 在发送 prepare 消息成功后执行本地事务
  3. 根据本地事务执行结果返回 commit 或者是 rollback
  4. 如果消息是 rollback,MQ 将删除该 prepare 消息不进行下发,如果是 commit 消 息,MQ 将会把这个消息发送给 consumer 端
  5. 如果执行本地事务过程中,执行端挂掉,或者超时,MQ 将会不停的询问其同组的其 它 producer 来获取状态
  6. Consumer 端的消费成功机制有 MQ 保证

Last Updated 2021-08-05 Thu 23:09. Created by Jinghui Hu at 2021-07-28 Wed 08:23.