前言


分布式事务框架学习


什么是事务与架构

数据库事务需要具有特性
微服务架构--cap的取舍

分布式事务实现方案

  1. 基于数据库资源层面

  2. 2PC 两阶段提交协议

  3. 3PC 三阶段提价协议

  4. 基于业务层面

  5. TCC

基于数据库资源层面实现方案,由于存在多个事务,我们需要存在一个角色管理各个事务的状态。我们将这个角色称为协调者,事务参与者称为参与者。参与者与协调者一般会基于某种特定协议,目前比较有名的为 XA 接口协议。基于协调者与参与者的思想设定,分别提出了 2PC 与 3PC 实现XA 分布式事务。

2PC两阶段提交协议

过程主要分为两步。

第一阶段,协调者(事务管理器)将涉及到事务的进行预提交,这个时候数据库资源开始被锁定。参与者将 redo 与 undo 写入事务日志。第二阶段,参与者(资源管理器)按redo提交事务,或者利用 undo 日志回滚事务,释放资源。

整个过程如下图。

分布式事务提交成功场景
分布式事务提交成功场景
分布式事务失败回滚场景
分布式事务失败回滚场景
  • 优点:
    实现比较简单,主流数据库都支持,强一致性。MySQL 5.5 以后基于 XA 协议实现.

  • 缺点:

    1. 协调者的单点问题。若协调者在提交阶段宕机,参与者一直在等待,就一直锁定资源,一直阻塞。虽然可以重新选举协调者,但是无法解决该问题。
    2. 同步阻塞时间过长,整个执行过程事务是阻塞的,直到提交完成,释放资源,若在提交过程/回滚过程,因为网络延时,参与者一直未收到指令,则参与者一直被阻塞。
    3. 数据不一致。第二阶段,协调者发出第一个提交信号后后宕机,则第一个参与者提交事务,第二个参与者因为未收到协调者信号,无法进行事务提交。

3PC三阶段提交协议

2阶段的改进型

步骤如下 :

  1. CanCommit,协调者询问参与者是否可以进行事务提交。

  2. PreCommit ,若所有参与者可以进行事务提交,协调者下达 PreCommit 命令,参与者锁定资源,并等待最终命令。

  3. 所有参与者返回确认信息,协调者向各个事务下发事务执行通知,锁定资源,并将执行情况返回。

  4. 部分参与者返回否认信息或协调者等待超时。这种情况,协调者认为事务无法正常执行,下发中断指令,各个参与者退出预备状态

  5. Do Commit,若第二阶段全部回应ack,则下达Do Commit,进行事务最终提交,否则下达中断事务命令,所有参与者进行事务回滚。

  6. 所有参与者正常执行执行事务,协调者下发最终提交指令,释放锁定资源。

  7. 部分参与者执行事务失败,协调者等待超时,协调者下发回滚指令,释放锁定资源。

3PC三阶段提交协议
3PC三阶段提交协议

三阶段提交对比两阶段,

  • 优点
    引入超时机制减少事务阻塞,解决单点故障。在第三阶段,一旦参与者无法接受到协调者信号时,等待超时之后,参与者默认执行 commit,释放资源。
  • 缺点
    三阶段仍然不能解决数据一致性问题。若协调者发出回滚命令,但是由于网络问题,参与者在等待时间内都无法接收到,这时参与者默认提交事务,而其他事务进行了回滚,造成事务不一致。

TCC

为了解决在事务运行过程中大颗粒度资源锁定的问题,业界提出一种新的事务模型,它是基于业务层面的事务定义。锁粒度完全由业务自己控制。它本质是一种补偿的思路。它把事务运行过程分成 Try、Confirm / Cancel 两个阶段。在每个阶段的逻辑由业务代码控制。这样就事务的锁粒度可以完全自由控制。业务可以在牺牲隔离性的情况下,获取更高的性能。

TCC 分别为 Trying,Confirm,Cancel 三个单词缩写。不同于 2PC 与 3PC 基于数据库层面,TCC 基于应用层面。TCC 三个动作分别为:

Trying:

  • 完成所有业务检查(一致性)
  • 预留必须业务资源(准隔离性)

Confirm:

  • 真正执行业务
  • Confirm操作要满足幂等性

Cancel:

  • 释放Try阶段预留的业务资源
  • Cancel操作要满足幂等性

下面模拟一次外卖系统的支付过程

步骤:

  1. 创建订单
  2. 调用红包系统,扣减红包
  3. 调用余额系统,扣减余额
  4. 修改订单状态为已支付
  5. 完成支付。

如下图所示

但是这么一个支付过程调用多个子服务,我们不能保证所有服务都能成功,比如我们在调用余额系统扣减余额失败。这个时候我们就碰到尴尬的场景,由于扣费服务失败,导致方法异常退出,这个时候订单状态为初始状态,但是用户红包已经扣减。这对用户体验非常不友好。所以这次支付过程,我们必须存在机制将这次过程当成一次整体的行为,必须保证这其中服务调用,要么都成功,要么都失败,成为一个整体的事务。

引入TCC事务

我们需要将各个服务改造成 Try Confirm Cancle 三步

TCC TRY:

根据上面的业务,订单系统增加 try 方法将订单状态修改成 PAYING。先从红包系统检查红包余额是否充足,然后扣除红包金额,余额系统和红包系统一样,再从从改造过程可以看出,TCC try 方法需检查各业务资源,且这过程需要引入中间状态。我们根据下图来看整个过程。

TCC Confirm:

TCC 第一步 TRY 如果所有子服务调用都成功,这个时候我们就需要确认各服务。各个服务增加 confirm 方法。如余额系统 confirm 方法用来将冻结金额置为0,红包系统如上。订单系统将订单状态修改为 SUCCESS。confirm 方法需要注意实现幂等。如订单系统更新前,一定要先判断该笔订单状态处于 PAYING,才能更新订单。整个过程如下图。

TCC Cancle:

如若 TCC Try 过程中,冻结红包方法失败,这时我们就需要将之前修改都撤销,修改成其初始状态。cancle 方法也需要实现幂等如 confirm 方法 如下图:

看到这,我们我们可以看出 TCC Try 成功,confirm 必定要成功,try 失败,cancle 必定要成功。因为 confirm 是系统更新为终态的关键。但是现实这么无情,生产系统 confirm 或 cancle 肯定会有几率失败,这个时候就需要 TCC 框架记录调用 confirm 结果。如果 confirm 调用失败,TCC 框架需要记录下来,然后间隔一定时间再次去调用。

总结与思考

看完全文,基本上对分布式事务又一定了解了吧。

我们基于此对此总结下。使用分布式事务,我们需要结合我们实际场景应用。

如果业务还处于开始阶段,我们其实可以选择数据库事务来保证快速上线迭代。

等到业务一定阶段,系统开始拆分,数据库也拆分,这时如果业务需要保证一致性,这时必须使用分布式事务。这时候使用分布式事务,我们需要基于业务考虑使用哪种。

使用 2PC 或 3PC 实现的分布式框架,业务应用层无需改动,接入较简单。但是相对应能较低,数据资源锁定较长。不太适合互联网等高并发业务场景。

而使用基于 TCC 实现分布式框架,相对 2PC 性能较高,可以保证数据最终一致性。但是对于应用层来说,一个方法必须改造成三个方法,且业务中需引入一些中间状态,相对而言应用改造程度较大。