分布式事务解决方案与适用场景分析
一. 基于 XA 协议的应用场景
XA 协议在架构上与 TCC 模型相比,最大的不同是 XA 直接作用于资源层,而后者作用于服务层。
资源层更普适,并且对业务几乎没有侵入,但是为了适应各种业务场景使用,需要严格遵循事务ACID 特性;服务层更接近业务,可以针对不同业务做特定的优化处理,追求更高的极限性能。
当然,并不是说 XA 协议只能作用于单个服务内部的多资源场景,跨服务的多资源场景也是可以的,只不过同样需要额外的事务传递机制。
在《分布式事务综述》一中介绍过,XA 协议通过每个 RM(Resource Manager,资源管理器)的本地事务隔离性来保证全局隔离,并且需要通过串行化隔离级别来保证分布式事务一致性。但是,串行化隔离级别存在一定的性能问题,如下所示:
在串行化隔离级别下,会为本来不加锁的 Select 快照读操作都加上读锁,导致锁持有时间增加,并发性能进一步降低。当实现了无锁的全局一致性读取以后,比如分布式 MVCC,可以大幅减少锁持有时间,并发性能会获得较大提升。
但是不管怎么优化实现,分布式事务的热点数据并发性能最高就是趋近于单机本地事务。所以,无论是基于 XA 协议实现的分布式事务,还是单机本地事务,都是存在热点数据并发性能极限的。
那么 XA 协议最大的作用是什么呢?其最大的作用在于数据库资源横向扩展时,能保证多资源访问的事务属性。
当单台 RM 机器达到资源性能瓶颈,无法满足业务增长需求时,就需要横向扩展 RM 资源,形成 RM 集群。通过横向扩展资源,提升非热点数据的并发性能,这对于大体量的互联网产品来说,是至关重要的。
以上图为例,假设单台 RM 的非热点数据并发性能为 100 TPS,那么 5台 RM 就是 500 TPS,就算一个分布式事务平均涉及 2 台 RM,也有 250 TPS,提升了 2.5 倍的非热点并发能力。
综上所述,基于 XA 协议实现的分布式事务并不能提升热点并发性能,其意义在于横向扩展资源提升非热点数据并发性能时,能严格保证对多资源访问的事务 ACID 特性。
至于热点数据并发性能问题,对于一般的应用来说,经过 SQL 层面一定的性能优化之后,其并发性能基本就能够满足业务的需求。如果经过优化,达到性能极限之后,还不能满足,就需要上升到业务层面,根据业务特点,通过专门的业务逻辑或业务架构优化来实现。
直接在资源层实现分布式事务的另外一点好处是其普适性,可以对上层业务屏蔽底层实现细节。这一点在云服务时代特别有用,云服务面对的是大量的中小企业,甚至是个人开发者,业务诉求不尽相同,普适、标准的分布式事务产品是非常有必要的,可以让开发者从底层技术细节中脱离出来,更专注于业务逻辑的实现,从而获得更高效、快速的业务发展。
二. 基于 TCC 模型的应用场景
TCC 分布式事务模型直接作用于服务层。不与具体的服务框架耦合,与底层 RPC 协议无关,与底层存储介质无关,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。
2.1 TCC 模型优势
对于 TCC 分布式事务模型,笔者认为其在业务场景应用上,有两方面的意义。
2.1.1 跨服务的分布式事务
这一部分的作用与 XA 类似,服务的拆分,也可以认为是资源的横向扩展,只不过方向不同而已。
横向扩展可能沿着两个方向发展:
-
功能扩展。根据功能对数据进行分组,并将不同的功能组分布在多个不同的数据库上,这实际上就是 SOA 架构下的服务化。
-
数据分片,在功能组内部将数据拆分到多个数据库上,为横向扩展增加一个新的维度。
下图简要阐释了横向数据扩展策略:
横向扩展的两种方法可以同时进行运用:用户信息(Users)、产品信息(Products)与交易信息(Trans)三个不同功能组可以存储在不同的数据库中。另外,每个功能组内根据其业务量可以再拆分到多个数据库中,各功能组可以相互独立地进行扩展。
因此,TCC 的其中一个作用就是在按照功能横向扩展资源时,保证多资源访问的事务属性。
2.1.2 两阶段拆分
TCC 另一个作用就是把两阶段拆分成了两个独立的阶段,通过资源业务锁定的方式进行关联。资源业务锁定方式的好处在于,既不会阻塞其他事务在第一阶段对于相同资源的继续使用,也不会影响本事务第二阶段的正确执行。
XA模型的并发事务:
TCC 模型的并发事务:
![](https://cdn.yuque.com/lark/2018/png/33058/ 1523590867479-3b9e86ec-c774-454e-a9c1-f0182170f5a0.png#width=470)
可以发现 TCC 模型进一步减少了资源锁的持有时间。同时,从理论上来说,只要业务允许,事务的第二阶段什么时候执行都可以,反正资源已经业务锁定,不会有其他事务动用该事务锁定的资源。
这对业务有什么好处呢?拿支付宝的担保交易场景来说,简化情况下,只需要涉及两个服务,交易服务和账务服务。交易作为主业务服务,账务作为从业务服务,提供 Try、Commit、Cancel 接口:
-
Try 接口扣除用户可用资金,转移到预冻结资金。预冻结资金就是业务锁定方案,每个事务第二阶段只能使用本事务的预冻结资金,在第一阶段执行结束后,其他并发事务也可以继续处理用户的可用资金。
-
Commit 接口扣除预冻结资金,增加中间账户可用资金(担保交易不能立即把钱打给商户,需要有一个中间账户来暂存)。
假设只有一个中间账户的情况下,每次调用支付服务的 Commit 接口,都会锁定中间账户,中间账户存在热点性能问题。
但是,在担保交易场景中,七天以后才需要将资金从中间账户划拨给商户,中间账户并不需要对外展示。因此,在执行完支付服务的第一阶段后,就可以认为本次交易的支付环节已经完成,并向用户和商户返回支付成功的结果,并不需要马上执行支付服务二阶段的 Commit 接口,等到低锋期时,再慢慢消化,异步地执行。
可能部分读者认为担保交易比较特殊,其实直付交易(直接把钱打到商户账户的交易模式,Commit 接口扣除预冻结资金以后,不是转移到中间账务,而是直接转移到商户账户)也可以这样使用,只要提前告知商户,高峰期交易资金不是实时到账,但保证在一定时间之内结算完成,商户应该也是可以理解的。
这就是 TCC 分布式事务模型的二阶段异步化功能,从业务服务的第一阶段执行成功,主业务服务就可以提交完成,然后再由框架异步的执行各从业务服务的第二阶段。
2.2 通用型 TCC 解决方案
通用型 TCC 解决方案就是最典型的 TCC 分布式事务模型实现,所有从业务服务都需要参与到主业务服务的决策当中。
适用场景
由于从业务服务是同步调用,其结果会影响到主业务服务的决策,因此通用型 TCC 分布式事务解决方案适用于执行时间确定且较短的业务,比如互联网金融企业最核心的三个服务:交易、支付、账务:
当用户发起一笔交易时,首先访问交易服务,创建交易订单;然后交易服务调用支付服务为该交易创建支付订单,执行收款动作,最后支付服务调用账务服务记录账户流水和记账。
为了保证三个服务一起完成一笔交易,要么同时成功,要么同时失败,可以使用通用型 TCC 解决方案,将这三个服务放在一个分布式事务中,交易作为主业务服务,支付作为从业务服务,账务作为支付服务的嵌套从业务服务,由 TCC 模型保证事务的原子性。
支付服务的 Try 接口创建支付订单,开启嵌套分布式事务,并调用账务服务的 Try 接口;账务服务在 Try 接口中冻结买家资金。一阶段调用完成后,交易完成,提交本地事务,由 TCC 框架完成分布式事务各从业务服务二阶段的调用。
支付服务二阶段先调用账务服务的 Confirm 接口,扣除买家冻结资金;增加卖家可用资金。调用成功后,支付服务修改支付订单为完成状态,完成支付。
当支付和账务服务二阶段都调用完成后,整个分布式事务结束。
2.3 异步确保型 TCC 解决方案
异步确保型 TCC 解决方案的直接从业务服务是可靠消息服务,而真正的从业务服务则通过消息服务解耦,作为消息服务的消费端,异步地执行。
可靠消息服务需要提供 Try,Confirm,Cancel 三个接口。Try 接口预发送,只负责持久化存储消息数据;Confirm 接口确认发送,这时才开始真正的投递消息;Cancel 接口取消发送,删除消息数据。
消息服务的消息数据独立存储,独立伸缩,降低从业务服务与消息系统间的耦合,在消息服务可靠的前提下,实现分布式事务的最终一致性。
此解决方案虽然增加了消息服务的维护成本,但由于消息服务代替从业务服务实现了 TCC 接口,从业务服务不需要任何改造,接入成本非常低。
适用场景
由于从业务服务消费消息是一个异步的过程,执行时间不确定,可能会导致不一致时间窗口增加。因此,异步确保性 TCC 分布式事务解决方案只适用于对最终一致性时间敏感度较低的一些被动型业务(从业务服务的处理结果不影响主业务服务的决策,只被动的接收主业务服务的决策结果)。比如会员注册服务和邮件发送服务:
当用户注册会员成功,需要给用户发送一封邮件,告诉用户注册成功,并提示用户激活该会员。但要注意两点:
-
如果用户注册成功,一定要给用户发送一封邮件;
-
如果用户注册失败,一定不能给用户发送邮件。
因此,这同样需要会员服务和邮件服务保证原子性,要么都执行,要么都不执行。不一样的是,邮件服务只是一种被动型的业务,并不影响用户是否能够注册成功,它只需要在用户注册成功以后发送邮件给用户即可,邮件服务不需要参与到会员服务的活动决策中。
对于此种业务场景,可以使用异步确保型TCC分布式事务解决方案,如下:
由可靠消息服务来解耦会员和邮件服务,会员服务与消息服务组成 TCC 事务模型,保证事务原子性。然后通过消息服务的可靠特性,确保消息一定能够被邮件服务消费,从而使得会员与邮件服务在同一个分布式事务中。同时,邮件服务也不会影响会员服务的执行过程,只在会员服务执行成功后被动接收发送邮件的请求。
2.4 补偿型 TCC 解决方案
补偿型 TCC 解决方案与通用型 TCC 解决方案的结构相似,其从业务服务也需要参与到主业务服务的活动决策当中。但不一样的是,前者的从业务服务只需要提供 Do 和 Compensate 两个接口,而后者需要提供三个接口。
Do 接口直接执行真正的完整业务逻辑,完成业务处理,业务执行结果外部可见;Compensate 操作用于业务补偿,抵消或部分抵消正向业务操作的业务结果,Compensate操作需满足幂等性。
与通用型解决方案相比,补偿型解决方案的从业务服务不需要改造原有业务逻辑,只需要额外增加一个补偿回滚逻辑即可,业务改造量较小。但要注意的是,业务在一阶段就执行完整个业务逻辑,无法做到有效的事务隔离,当需要回滚时,可能存在补偿失败的情况,还需要额外的异常处理机制,比如人工介入。
适用场景
由于存在回滚补偿失败的情况,补偿型 TCC 分布式事务解决方案只适用于一些并发冲突较少或者需要与外部交互的业务,这些外部业务不属于被动型业务,其执行结果会影响主业务服务的决策,比如机票代理商的机票预订服务:
该机票服务提供多程机票预订服务,可以同时预订多趟行程航班机票,比如从北京到圣彼得堡,需要第一程从北京到莫斯科,以及第二程从莫斯科到圣彼得堡。
当用户预订机票时,肯定希望能同时预订这两趟航班的机票,只预订一趟航班对用户来说没有意义。因此,对于这样的业务服务同样提出了原子性要求,如果其中一趟航班的机票预订失败,另外一趟需要能够取消预订。
但是,由于航空公司相对于机票代理商来说属于外部业务,只提供订票接口和取消预订接口,想要推动航空公司改造是极其困难的。因此,对于此类业务服务,可以使用补偿型 TCC 分布式事务解决方案,如下:
网关服务在原有逻辑基础上增加Compensate接口,负责调用对应航空公司的取消预订接口。
在用户发起机票预订请求时,机票服务先通过网关Do接口,调用各航空公司的预订接口,如果所有航班都预订成功,则整个分布式事务直接执行成功;一旦某趟航班机票预订失败,则分布式事务回滚,由 TCC 事务框架调用各网关的Compensate补偿接口,其再调用对应航空公司的取消预订接口。通过这种方式,也可以保证多程机票预订服务的原子性。
三. 总结
对于现在的互联网应用来说,资源横向扩展提供了更多的灵活性,是一种比较容易实现的向外扩展方案,但是同时也明显增加了复杂度,引入一些新的挑战,比如资源之间的数据一致性问题。
横向数据扩展既可以按数据分片扩展,也可以按功能扩展。XA 与 TCC 模型在这一点上的作用类似,都能在横向扩展资源的同时,保证多资源访问的事务属性,只不过前者作用于数据分片时,后者作用于功能扩展时。
XA 模型另外一个意义在于其普适性,抛开性能问题的情况下,几乎可以适用于所有业务模式,这对于一些基础性的技术产品来说是非常有用的,比如分布式数据库、云服务的分布式事务框架等。
TCC 模型除了跨服务的分布式事务这一层作用之外,还具有两阶段划分的功能,通过业务资源锁定,允许第二阶段的异步执行,而异步化思想正是解决热点数据并发性能问题的利器之一。
本文结合具体的业务场景和例子,对比分析了各分布式事务解决方案在性能、热点冲突、接入复杂度和适用场景等方面的能力,希望能帮助各位读者对分布式事务有更深一层的理解。
业务各有各的不同,有些业务能容忍短期不一致,有些业务的操作可以幂等,无论什么样的分布式事务解决方案都有其优缺点,没有一个银弹能够适配所有。因此,业务需要什么样的解决方案,还需要结合自身的业务需求、业务特点、技术架构以及各解决方案的特性,综合分析,才能找到最适合的方案。