vlambda博客
学习文章列表

分布式事务(三)、柔性事务之 TCC、Saga、本地消息表、事务消息、最大努力通知

分布式事务:


【前言】

在前面的文章中我们提到了柔性事务遵循BASE理论,满足最终一致性,柔性事务主要分为补偿型通知型。补偿型事务又分TCC、Saga,通知型事务分事务消息、最大努力通知型。补偿型事务都是同步的,通知型事务都是异步的。本篇介绍柔性事务的这几种实现方案:TCC、Saga、事务消息、本地消息表、最大努力通知。

TCC

TCC(Try-Confirm-Cancel)采用补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC模型通过对业务逻辑的分解来实现分布式事务:

  1. Try:尝试执行业务,完成所有业务检查(一致性),预留必要的业务资源(准隔离性)。

  2. Confirm:确认执行业务,不再做业务检查。只使用Try阶段预留的业务资源,Confirm操作满足幂等性。

  3. Cancel: 若业务执行失败,则取消执行业务并释放Try阶段预留的业务资源。

TCC分布式事务模型包括如下三部分:

  • 主业务服务:负责发起并完成整个业务活动。

  • 从业务服务:是整个业务活动的参与方,实现 Try、Confirm、Cancel 操作,供主业务服务调用。

  • 事务管理器:管理整个业务活动,包括记录事务状态,调用从业务服务的 Confirm/Cancel 操作等。

TCC对比2PC

可以看出,TCC也是把事务分成了两个阶段,Try是阶段一,Confirm 和 Cancel 是阶段二的两个分支。这有点像2PC(二阶段提交),但其实他们是不一样的,下面是他们的区别:

  1. 2PC和3PC都是数据库层面的操作,对于开发人员无感知;而TCC是业务层的操作,对开发人员来说具有较高的开发成本。

  2. 2PC是一个整体的长事务,是刚性事务;而TCC是一组本地短事务,是柔性事务;

  3. 2PC是全局锁定资源,所有参与者阻塞等待事务管理器的通知;而TCC的资源锁定在于Try操作,业务方可以灵活选择业务资源的锁定粒度。

TCC优缺点

优点:

  • 应用可以自定义数据操作的粒度,降低了锁冲突,提升吞吐量。

缺点: 

  • 应用侵入性强, Try、Confirm、Cancel 三个阶段都需要业务逻辑实现。

  • 需要根据网络、系统故障等不同失败原因实现不同的回滚策略, 实现难度大,一般借助 TCC 开源框架,ByteTCC,TCC-transaction,Himly。

Saga

Saga模型是把一个分布式事务拆分为多个本地事务,每个本地事务都有相应的执行模块和补偿模块(对应TCC中的Confirm和Cancel),当Saga事务中任意一个本地事务出错时,可以通过调用相关的补偿方法恢复之前的事务,达到事务最终一致性。

Saga 模型由三部分组成

  • LLT(Long Live Transaction):由一个个本地事务组成的事务链

  • 本地事务:事务链由一个个子事务(本地事务)组成,LLT = T1+T2+T3+...+Ti。

  • 补偿:每个本地事务 Ti 有对应的补偿 Ci。

Saga 的执行顺序

  • 正常情况:T1,T2,T3,...,Ti

  • 异常情况:T1,T2,T3,..Ti,Ci,...C3,C2,C1

Saga 两种恢复策略

  • 向后恢复(Backward Recovery):撤销掉之前所有成功子事务。如果任意本地子事务失败,则补偿已完成的事务。如异常情况的执行顺序T1,T2,T3,..Ti,Ci,...C3,C2,C1。

  • 向前恢复(Forward Recovery):即重试失败的事务,适用于必须要成功的场景,该情况下不需要Ci。执行顺序:T1,T2,...,Tj(失败),Tj(重试),...,Ti。

Saga 模型可以满足事务的三个特性ACD

  • 原子性:Saga 协调器协调事务链中的本地事务要么全部提交,要么全部回滚。

  • 一致性:Saga 事务可以实现最终一致性。

  • 持久性:基于本地事务,所以这个特性可以很好实现。

Saga缺乏隔离性会带来的问题。由于Saga 事务和 TCC 事务一样,都是强依靠业务改造,因此需要在业务设计上去解决这个问题:

  • 在应⽤层⾯加⼊逻辑锁的逻辑。

  • Session 层⾯隔离来保证串⾏化操作。

  • 业务层⾯采⽤预先冻结数据的方式隔离此部分数据。

  • 业务操作过程中通过及时读取当前状态的⽅式获取更新。

 实现Saga的注意事项

  1. Ti和Ci必须是幂等的。如向后恢复和向前恢复时候如果不是幂等操作会导致数据不一致。

  2. Ci必须是能够成功的,如果无法成功则需要人工介入。

  3. Ti->Ci和Ci->Ti的执行结果必须是一样的。

Saga对比TCC

Saga和TCC都是补偿型事务,他们的区别为:

劣势:

  • 无法保证隔离性;

优势:

  • 一阶段提交本地事务,无锁,高性能;

  • 事件驱动模式,参与者可异步执行,高吞吐;

  • Saga 对业务侵入较小,只需要提供一个逆向操作的Cancel即可;而TCC需要对业务进行全局性的流程改造; 


 【通知型事务】

上面提到柔性事务主要分为补偿型通知型,我们已经介绍了补偿型的TCC,Saga模型,下面继续介绍通知型事务。

通知型事务的主流实现是通过MQ(消息队列)来通知其他事务参与者自己事务的执行状态,MQ组件的引入有效的将事务参与者进行解耦,各参与者都可以异步执行,所以通知型事务又被称为异步事务。通知型事务主要适用于那些需要异步更新数据,并且对数据的实时性要求较低的场景,主要包含事务消息最大努力通知事务两种。

事务消息:主要适用于内部系统的数据最终一致性保障,因为内部相对比较可控,如订单和购物车、收货与清算、支付与结算等等场景;

最大努力通知:主要用于外部系统,因为外部的网络环境更加复杂和不可信,所以只能尽最大努力去通知实现数据最终一致性,比如充值平台与运营商、支付对接等等跨网络系统级别对接;

普通消息是无法解决本地事务执行和消息发送的一致性问题的。因为消息发送是一个网络通信的过程,发送消息的过程就有可能出现发送失败、或者超时的情况。超时有可能发送成功了,有可能发送失败了,消息的发送方是无法确定的,所以此时消息发送方无论是提交事务还是回滚事务,都有可能不一致性出现。因此通知型事务的难点在于投递消息和参与者自身本地事务的一致性保障。目前业界解决这个一致性的方案有两个分支:

  • 基于MQ自身的事务消息方案

  • 基于DB的本地消息表方案 

本地消息表

本地消息表最初由eBay 提出来解决分布式事务的问题。是目前业界使用的比较多的方案之一,它的核心思想就是将分布式事务拆分成本地事务进行处理。

流程

分布式事务(三)、柔性事务之 TCC、Saga、本地消息表、事务消息、最大努力通知

发送消息方:

  • 需要有一个消息表,记录着消息状态相关信息。

  • 业务数据和消息表在同一个数据库,要保证它俩在同一个本地事务。

  • 在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。

  • 消息会发到消息消费方,如果发送失败,即进行重试。

消息消费方:

  • 处理消息队列中的消息,完成自己的业务逻辑。

  • 如果本地事务处理成功,则表明已经处理成功了。

  • 如果本地事务处理失败,那么就会重试执行。

  • 如果是业务层面的失败,给消息生产方发送一个业务补偿消息,通知进行回滚等操作。

生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。

本地消息表优缺点:

优点:

  • 本地消息表建设成本比较低,实现了可靠消息的传递确保了分布式事务的最终一致性。

缺点:

  • 本地消息表与业务耦合在一起,难于做成通用性,不可独立伸缩。

  • 本地消息表是基于数据库来做的,而数据库是要读写磁盘IO的,因此在高并发下是有性能瓶颈的

MQ事务消息

基于MQ的事务消息方案主要依靠MQ的半消息机制来实现投递消息和参与者自身本地事务的一致性保障。半消息机制实现原理其实借鉴的2PC的思路,是二阶段提交的广义拓展。

半消息:在原有队列消息执行后的逻辑,如果后面的本地逻辑出错,则不发送该消息,如果通过则告知MQ发送;

流程

  1. 事务发起方首先发送半消息到MQ;

  2. MQ通知发送方消息发送成功;

  3. 在发送半消息成功后执行本地事务;

  4. 根据本地事务执行结果返回commit或者是rollback;

  5. 如果消息是rollback, MQ将丢弃该消息不投递;如果是commit,MQ将会消息发送给消息订阅方;

  6. 订阅方根据消息执行本地事务;

  7. 订阅方执行本地事务成功后再从MQ中将该消息标记为已消费;

  8. 如果执行本地事务过程中,执行端挂掉,或者超时,MQ服务器端将不停的询问producer来获取事务状态;

  9. Consumer端的消费成功机制有MQ保证; 

MQ事务消息对比本地消息表

MQ事务消息:

  • 需要MQ支持半消息机制或者类似特性,在重复投递上具有比较好的去重处理;

  • 具有比较大的业务侵入性,需要业务方进行改造,提供对应的本地操作成功的回查功能;

DB本地消息表:

  • 使用了数据库来存储事务消息,降低了对MQ的要求,但是增加了存储成本;

  • 事务消息使用了异步投递,增大了消息重复投递的可能性;

最大努力通知

最大努力通知方案的目标,就是发起通知方通过一定的机制,最大努力将业务处理结果通知到接收方。本质是通过引入定期校验机制实现最终一致性,对业务的侵入性较低,适合于对最终一致性敏感度比较低、业务链路较短的场景。

最大努力通知解决方案:要实现最大努力通知,可以采用 MQ 的 ACK 机制。

  1. 业务活动的主动方,在完成业务处理之后,向业务活动的被动方发送消息,允许消息丢失。

  2. 主动方可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,直到通知N次后不再通知。

  3. 主动方提供校对查询接口给被动方按需校对查询,用于恢复丢失的业务消息。

  4. 业务活动的被动方如果正常接收了数据,就正常返回响应,并结束事务。

  5. 如果被动方没有正常接收,根据定时策略,向业务活动主动方查询,恢复丢失的业务消息。

特点

  1. 用到的服务模式:可查询操作、幂等操作;

  2. 被动方的处理结果不影响主动方的处理结果;

  3. 适用于对业务最终一致性的时间敏感度低的系统;

  4. 适合跨企业的系统间的操作,或者企业内部比较独立的系统间的操作,比如银行通知、商户通知等;

总结

方案对比:

属性 2PC TCC Saga 本地消息表 事务消息 尽最大努力通知
事务一致性
复杂性
业务侵入性
使用局限性
性能
维护成本

 希望本文对你有帮助,请点个赞鼓励一下作者吧~ 谢谢!