分布式事务 TCC 两阶段补偿

原文git

TCC方案是多是目前最火的一种柔性事务方案了。关于TCC(Try-Confirm-Cancel)的概念,最先是由Pat Helland于2007年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。在该论文中,TCC仍是以Tentative-Confirmation-Cancellation命名。正式以Try-Confirm-Cancel做为名称的是Atomikos公司,其注册了TCC商标。github

国内最先关于TCC的报道,应该是InfoQ上对阿里程立博士的一篇采访。通过程博士的这一次传道以后,TCC在国内逐渐被你们广为了解并接受。spring

Atomikos公司在商业版本事务管理器ExtremeTransactions中提供了TCC方案的实现,可是因为其是收费的,所以相应的不少的开源实现方案也就涌现出来,如:tcc-transaction、ByteTCC、spring-cloud-rest-tcc。sql

TCC的做用主要是解决跨服务调用场景下的分布式事务问题,在本文中,笔者将先介绍一个跨服务的场景案例,并分析其中存在的分布式事务问题;而后介绍TCC的基本概念以及其是如何解决这个问题的。数据库

场景案例

Atomikos官网上<>一文中,以航班预约的案例,来介绍TCC要解决的事务场景。在这里笔者虚构一个彻底相同的场景,把本身当作航班预约的主人公,来介绍这个案例。事实上,你能够把本案例当作官方文档案例的一个翻译,只不过把地点从Brussels-->Toronto-->Washington,改为从合肥-->昆明-->大理。框架

有一次,笔者买彩票中奖了(纯属虚构),准备从合肥出发,到云南大理去游玩,而后使用美团App(机票代理商)来订机票。发现没有从合肥直达大理的航班,须要到昆明进行中转。以下图:异步

从图中咱们能够看出来,从合肥到昆明乘坐的是四川航空,从昆明到大理乘坐的是东方航空。分布式

因为使用的是美团App预约,当我选择了这种航班预约方案后,美团App要去四川航空和东方航空各帮我购买一张票。以下图:ide

考虑最简单的状况:美团先去川航帮我买票,若是买不到,那么东航也不必买了。若是川航购买成功,再去东航购买另外一张票。post

如今问题来了:假设美团先从川航成功买到了票,而后去东航买票的时候,由于天气问题,东航航班被取消了。那么此时,美团必须取消川航的票,由于只有一张票是没用的,不取消就是浪费个人钱。那么若是取消会怎样呢?若是读者有取消机票经历的话,非正常退票,确定要扣手续费的。在这里,川航原本已经购买成功,如今由于东航的缘由要退川航的票,川航应该是要扣代理商的钱的。

那么美团就要保证,若是任一航班购买失败,都不能扣钱,怎么作呢?

两个航空公司都为美团提供如下3个接口:机票预留接口、确认接口、取消接口。美团App分2个阶段进行调用,以下所示:

在第1阶段:

美团分别请求两个航空公司预留机票,两个航空公司分别告诉美图预留成功仍是失败。航空公司须要保证,机票预留成功的话,以后必定能购买到。

在第2阶段:

若是两个航空公司都预留成功,则分别向两个公司发送确认购买请求。

若是两个航空公司任意一个预留失败,则对于预留成功的航空公司也要取消预留。这种状况下,对于以前预留成功机票的航班取消,也不会扣用户的钱,由于购买并没实际发生,以前只是请求预留机票而已。

经过这种方案,能够保证两个航空公司购买机票的一致性,要不都成功,要不都失败,即便失败也不会扣用户的钱。若是在两个航班都已经已经确认购买后,再退票,那确定仍是要扣钱的。

固然,实际状况确定这里提到的确定要复杂,一般航空公司在第一阶段,对于预留的机票,会要求在指定的时间必须确认购买(支付成功),若是没有及时确认购买,会自动取消。假设川航要求10分钟内支付成功,东航要求30分钟内支付成功。以较短的时间算,若是用户在10分钟内支付成功的话,那么美团会向两个航空公司都发送确认购买的请求,若是超过10分钟(以较短的时间为准),那么就不能进行支付。

再次强调,这个案例,能够算是<>中航班预约案例的汉化版。而实际美团App是如何实现这种须要中转的航班预约需求,笔者并不知情。

另外,注意这只是一个案例场景,实际状况中,你是很难去驱动航空公司进行接口改造的。

Whatever,这个方案提供给咱们一种跨服务条用保证事务一致性的一种解决思路,能够把这种方案当作TCC的雏形。

TCC 的基本概念

TCC是Try-Confirm-Cancel的简称:

Try阶段:

完成全部业务检查(一致性),预留业务资源(准隔离性)

回顾上面航班预约案例的阶段1,机票就是业务资源,全部的资源提供者(航空公司)预留都成功,try阶段才算陈宫

Confirm阶段:

确认执行业务操做,不作任何业务检查, 只使用Try阶段预留的业务资源。回顾上面航班预约案例的阶段2,美团APP确认两个航空公司机票都预留成功,所以向两个航空公司分别发送确认购买的请求。

Cancel阶段:

取消Try阶段预留的业务资源。回顾上面航班预约案例的阶段2,若是某个业务方的业务资源没有预留成功,则取消全部业务资源预留请求。

敏锐的读者立马会想到,TCC与XA两阶段提交有着殊途同归之妙,下图列出了两者之间的对比:

  1. 在阶段1:

在XA中,各个RM准备提交各自的事务分支,事实上就是准备提交资源的更新操做(insert、delete、update等);而在TCC中,是主业务活动请求(try)各个从业务服务预留资源。

  1. 在阶段2:

XA根据第一阶段每一个RM是否都prepare成功,判断是要提交仍是回滚。若是都prepare成功,那么就commit每一个事务分支,反之则rollback每一个事务分支。

TCC中,若是在第一阶段全部业务资源都预留成功,那么confirm各个从业务服务,不然取消(cancel)全部从业务服务的资源预留请求。

TCC两阶段提交与XA两阶段提交的区别是:

XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程当中,一直会持有资源的锁。

XA事务中的两阶段提交内部过程是对开发者屏蔽的,回顾咱们以前讲解JTA规范时,经过UserTransaction的commit方法来提交全局事务,这只是一次方法调用,其内部会委派给TransactionManager进行真正的两阶段提交,所以开发者从代码层面是感知不到这个过程的。而事务管理器在两阶段提交过程当中,从prepare到commit/rollback过程当中,资源实际上一直都是被加锁的。若是有其余人须要更新这两条记录,那么就必须等待锁释放。

TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。

TCC中的两阶段提交并无对开发者彻底屏蔽,也就是说从代码层面,开发者是能够感觉到两阶段提交的存在。如上述航班预约案例:在第一阶段,航空公司须要提供try接口(机票资源预留)。在第二阶段,航空公司提须要提供confirm/cancel接口(确认购买机票/取消预留)。开发者明显的感知到了两阶段提交过程的存在。try、confirm/cancel在执行过程当中,通常都会开启各自的本地事务,来保证方法内部业务逻辑的ACID特性。其中:

一、try过程的本地事务,是保证资源预留的业务逻辑的正确性。

二、confirm/cancel执行的本地事务逻辑确认/取消预留资源,以保证最终一致性,也就是所谓的补偿型事务(Compensation-Based Transactions)。

因为是多个独立的本地事务,所以不会对资源一直加锁。

另外,这里提到confirm/cancel执行的本地事务是补偿性事务,关于什么事补偿性事务,atomikos 官网上有如下描述:

红色框中的内容,是对补偿性事务的解释。大体含义是,"补偿是一个独立的支持ACID特性的本地事务,用于在逻辑上取消服务提供者上一个ACID事务形成的影响,对于一个长事务(long-running transaction),与其实现一个巨大的分布式ACID事务,不如使用基于补偿性的方案,把每一次服务调用当作一个较短的本地ACID事务来处理,执行完就当即提交”。

在这里,笔者理解为confirm和cancel就是补偿事务,用于取消try阶段本地事务形成的影响。由于第一阶段try只是预留资源,以后必需要明确的告诉服务提供者,这个资源你到底要不要,对应第二阶段的confirm/cancel。

提示:读者如今应该明白为何把TCC叫作两阶段补偿性事务了,提交过程分为2个阶段,第二阶段的confirm/cancel执行的事务属于补偿事务。

TCC事务模型 VS DTP事务模型

在介绍完TCC的基本概念以后,咱们再来比较一下TCC事务模型和DTP事务模型,以下所示:

这两张图看起来差异较大,实际上不少地方是相似的!

一、TCC模型中的主业务服务 至关于 DTP模型中的AP,TCC模型中的从业务服务 至关于 DTP模型中的RM

在DTP模型中,应用AP操做多个资源管理器RM上的资源;而在TCC模型中,是主业务服务操做多个从业务服务上的资源。例如航班预约案例中,美团App就是主业务服务,而川航和东航就是从业务服务,主业务服务须要使用从业务服务上的机票资源。不一样的是DTP模型中的资源提供者是相似于Mysql这种关系型数据库,而TCC模型中资源的提供者是其余业务服务。

二、TCC模型中,从业务服务提供的try、confirm、cancel接口 至关于 DTP模型中RM提供的prepare、commit、rollback接口

XA协议中规定了DTP模型中定RM须要提供prepare、commit、rollback接口给TM调用,以实现两阶段提交。

而在TCC模型中,从业务服务至关于RM,提供了相似的try、confirm、cancel接口。

三、事务管理器

DTP模型和TCC模型中都有一个事务管理器。不一样的是:

在DTP模型中,阶段1的(prepare)和阶段2的(commit、rollback),都是由TM进行调用的。

在TCC模型中,阶段1的try接口是主业务服务调用(绿色箭头),阶段2的(confirm、cancel接口)是事务管理器TM调用(红色箭头)。这就是 TCC 分布式事务模型的二阶段异步化功能,从业务服务的第一阶段执行成功,主业务服务就能够提交完成,而后再由事务管理器框架异步的执行各从业务服务的第二阶段。这里牺牲了必定的隔离性和一致性的,可是提升了长事务的可用性。问题来了,既然第二阶段是异步执行的,主业务服务怎么知道异步执行的结果呢?发消息异步通知?返回一个id,后面让业务去查?

TCC事务的优缺点:

优势:XA两阶段提交资源层面的,而TCC实际上把资源层面二阶段提交上提到了业务层面来实现。有效了的避免了XA两阶段提交占用资源锁时间过长致使的性能地下问题。

缺点:主业务服务和从业务服务都须要进行改造,从业务方改形成本更高。仍是航班预约案例,原来只须要提供一个购买接口,如今须要改形成try、confirm、canel3个接口,开发成本高。

提示:国内有一些关于TCC方案介绍的文章中,把TCC分红三种类型:

通用型TCC,若是咱们上面介绍的TCC模型实例,从业务服务须要提供try、confirm、cancel

补偿性TCC,从业务服务只须要提供 Do 和 Compensate 两个接口

异步确保型 TCC,主业务服务的直接从业务服务是可靠消息服务,而真正的从业务服务则经过消息服务解耦,做为消息服务的消费端,异步地执行。

关于这种划分,笔者并不赞同,基于两点:

一、笔者在Atomikos在官网上参考了多份资料,并无看到这种划分,猜想应该是这些公司在内部实践中,自行提出的概念。

二、对于上面所谓的"补偿性TCC”、”异步确保型 TCC”,从业务服务不须要提供try、confirm、cancel三个接口,在这种状况下,好像称之为TCC也不太合适。

推荐阅读资料:

开源TCC实现方案

Atomikos的官方文档

  • Composite Transactions for SOA

  • Transaction management API for REST: TCC

  • Atomikos ExtremeTransactions Guide

相关文章
相关标签/搜索