1. TXC是什么
TXC(Taobao Transaction Constructor)是阿里巴巴的一个分布式事务中间件,它可以通过极少的代码侵入,实现分布式事务。

在大部分情况下,应用只需要引入TXC Client的jar包,进行几项简单配置,以及以行计的代码改造,即可轻松保证分布式数据一致性。

TXC同时提供了丰富的编程和配置策略,以适应各种长尾的应用需求。

2. 背景
2.1. 什么是事务?
以下内容来自 维基百科相关词条。

一个数据库事务通常包含了一个序列的对数据库的读/写操作。它的存在包含有以下两个目的:

为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
并非任意的对数据库的操作序列都是数据库事务。数据库事务拥有以下四个特性,习惯上被称之为 ACID特性。

原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。
2.2 什么是分布式事务?
以下内容来自 百度百科。

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

在应用程序只部署在一台计算机,数据库只部署在一台计算机的情况下,事务的ACID四个特性很容易全部满足。

但是单机的处理能力很容易达到上限,此时必须使用分布式系统。在分布式环境下,应用程序可能部署在多台计算机,并且可能有多个不同的应用程序参与到同一个事务中;数据库也可能部署在多台计算机,并且多个不同的数据库可能会参与到同一个事务中。在这样的分布式环境下,高吞吐量和ACID很难被同时满足。

目前较为流行的分布式事务解决方案可以分为两类:

两阶段提交
两阶段提交,是实现分布式事务的成熟方案。第一阶段是表决阶段,是所有参与者都将本事务能否成功的反馈发给协调者;第二阶段是执行阶段,协调者根据所有参与者的反馈,通知所有参与者,步调一致地在所有分支上提交,或者在所有分支上回滚。

具体可参见 维基百科相关词条。

两阶段提交可以满足ACID,但代价是吞吐量。例如,数据库需要频繁地对资源上锁等等。而且更致命的是,资源被锁住的时间相对较长—-在第一阶段即需要上锁,第二阶段才能解锁,依赖于所有分支的最慢者—-这期间没有任何人可以对该资源进行修改。

两阶段提交理论的一个广泛工业应用是XA协议。目前几乎所有收费的商业数据库都支持XA协议。XA协议已在业界成熟运行数十年,但目前它在互联网海量流量的应用场景中,吞吐量这个瓶颈变得十分致命,因此很少被用到。

基于XA协议的分布式事务实现详见《分布式事务解决方案》

TCC
TCC(Try、Confirm、Cancel)是两阶段提交的一个变种。TCC提供了一个框架,需要应用程序按照该框架编程,将业务逻辑的每个分支都分为Try、Confirm、Cancel三个操作集。TCC让应用程序自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。

以一个典型的淘宝订单为例,按照TCC框架,应用需要在Try阶段将商品的库存减去,将买家支付宝账户中的相应金额扣掉,在临时表中记录下商品的数量,订单的金额等信息;另外再编写Confirm的逻辑,即在临时表中删除相关记录,生成订单,告知CRM、物流等系统,等等;以及Cancel逻辑,即恢复库存和买家账户金额,删除临时表相关记录。

我们能找到的最早出处是atomikos的ExtremeTransactions产品中提出了TCC的概念。在撰写本文时,atomikos网站不能访问,只能在百度百科里面找到一篇 相关的文章。目前,TCC概念最大的应用,一般认为是支付宝XTS系统。

为了更好地介绍TCC,这里先引入这么一个概念:最终一致性

最终一致性目前没有公认的定义。一般来说,它是指事务进行中,某些分支的中间状态可以被事务外观察到,即”读未提交”,从而导致多个分支的状态可能不一致,但所有分支 最终 会达到要么全部提交,要么全部回滚的一致状态。

很明显,最终一致性部分牺牲了ACID中的C和I,但它带来了可观的收益:资源不再需要长时间上锁,极大地提高了吞吐量。

最终一致性在互联网应用场景中被广泛用做吞吐量和ACID的妥协点。

让我们回到前面TCC的例子。在这个流程中,商品库存和买家余额都没有被锁住,因此可以得到很高的吞吐量。但在交易进行中,商品库存和买家余额的变化就已经被外界感知到,而物流系统却可能还没有相应的记录,此时数据是不一致的,但最终(无论是Confirm阶段结束后,还是Cancel阶段结束后)它们会一致。

3. TXC的目标应用场景
TXC的目标应用场景是:解决在分布式应用中,多条数据库记录被修改而可能带来的一致性问题;该分布式应用可以接受最终一致性;该应用的事务改造对工作量有较严格的限制。

最终一致性
如前所述,目前业内的所有分布式事务中间件都很难在保持高吞吐量的前提下,完全满足ACID四个特性。目前几乎所有的分布式事务中间件都是以牺牲这5个指标(性能、ACID)中的一个或多个来实现事务的,它们的区别往往在于牺牲了不同的指标。例如两阶段提交就是牺牲了性能,而ExtremeTransactions和XTS则是牺牲了一致性和隔离性。

在这方面,TXC也采用了最终一致性,因此部分牺牲了一致性和隔离性。在此前提下,TXC在性能、隔离性之间提供了数个平衡点,应用程序管理员可以通过配置不同的TXC策略来选择本应用所需要的平衡点。详情请见后续章节。

代码侵入少
除了数据一致性之外,为了实现分布式事务而花在开发上的开销,也是一个值得考虑的因素。如果没有分布式事务中间件,要达到类似目的,需要为业务上的每一个写SQL都编写回滚操作,这些回滚操作还必须是幂等的,应用程序员还要决定什么时候调用这些回滚操作中的全部或一部分—-毫无疑问这些工作是繁琐而容易出错的。

所有的中间件,其目的都是为了让业务只需要关注于业务。通过TXC的自动接口,应用程序员只需要指定事务的边界,即可实现分布式事务,代码修改可以短至1行。代码侵入少了,犯错误的概率就小,开发周期也会随之缩短。

最后,和支付宝XTS类似,TXC也提供了灵活、强大的类似TCC的手动接口,以较多的代码修改获取较高的性能。

TXC推荐应用程序以自动接口实现大部分逻辑,用手动接口实现对性能非常敏感的一小部分核心逻辑。TXC保证这些逻辑都可以被包含在在同一个分布式事务中。

跨多应用的事务
TXC的事务可以在应用之间随着HSF服务调用被传播,使得跨多应用、跨多库的数据库操作可以被包含在同一个事务内。这些不同的应用可以采用不同的TXC配置,从而满足各种各样的应用需求。

综上所述,TXC的目标场景,是最终一致性、开发简便快捷、跨多应用的事务场景。

4. 概念及术语介绍
事务超时时间 – TXC中,对每个分布式事务均需设定一个超时时间,超出超时时间未被提交的事务,均会被TXC框架回滚。

事务分支 – 一个分布式事务可能包含多个分支,只有当所有的分支全部成功时,分布式事务才能成功,一个分支的失败将导致分布式事务的回滚。在TXC框架下,分支可能是一个分库上执行的sql语句,或是一个手动模式分支。

事务边界 – 分布式事务需要进行开启,在执行结束后需要进行结束(提交或回滚),事务开启和关闭即划定了一个事务边界。

事务模式 – TXC提供的预先定义好的事务模式,不同的事务模式提供了不同的易用性和性能,不同的事务模式组合(详见最佳实践)可解决极度复杂的场景。

TXC Client – 事务发起者,用以界定分布式事务边界

RM – Resource Manager , Rm是事务中的资源管理器抽象,Rm定义了资源参与到事务中的行为,不同事务模式对应不同的资源管理器。

TM – Transaction Manager ,泛指事务协调器,在TXC中,其责任由TXC Server承担。

5. TXC的3种模式和它们各自适用的场景
5.1 标准模式(AT模式)
TXC标准模式(Automatic TXC)是最主要的事务模式,通过基于TDDL的TXC数据源,它对SQL语句提供了分布式事务支持。它帮助应用方以最小的改造代价来实现TDDL下的事务的功能。

在标准模式下,当开启了TXC分布式事务时,TXC框架将自动的根据执行的SQL语句,进行事务分支划分(每个物理库上的一个本地事务作为一个分布式事务分支),把各个分支统一纳入事务。

分布式事务的隔离级别可以配置,读未提交(read uncommitted)和读已提交(read committed)。读未提交是缺省设置。

标准模式适合于TDDL分库分表、多TDDL数据源、跨进程的多TDDL数据源等几乎任何TDDL应用场景下的分布式事务。

5.2 自定义模式(MT模式)
MT(Manual TXC)模式,提供用户可以介入两阶段提交过程的一种模式。在这种模式下,用户可以根据自身业务需求自定义在TXC的两阶段中每个阶段的具体行为。MT模式提供了更多的灵活性,可能性,以达到特殊场景下的自定义优化,及特殊功能的实现。

MT模式不依赖于TDDL,这是它相对于标准模式的一个最大的优势。MT模式几乎满足任何我们想到的事务场景。

5.3 重试模式
RT(Retry TXC)模式严格地说,不属于传统的事务范围。在TXC将其定义为一种特殊的事务,它通过在用户指定时间内不停的异步重试失败的SQL语句,来保证SQL语句最终成功。

RT模式也是基于TXC数据源的。

当我们通过TDDL执行一个需要分库的SQL,假设在第一个库执行成功了,但是在第二个库执行失败了。如果采用TXC标准模式,第一个库的SQL会回滚。对用户来说,他的SQL失败了,在两个库上是一致的。

如果采用RT模式,第二个库执行失败的SQL会保存下来,TXC不断重试这个SQL,直到成功。对用户来说, 他的SQL成功了,在两个库上最终是一致的。当然,TXC不会一直重试SQL,用户可以指定一个超时时间,超过这个时间限制,TXC会发送告警信息到用户。用户拿到告警信息后,可以从业务库的RT SQL表中拿到对应的SQL语句,决定下一步怎么处理。

试想一下,当一个SQL涉及到分库,我们执行这个SQL失败了,通常来说,我们需要通过log查出它在哪几个库成功了哪几个库失败了,并且在失败的库上不断重试。这是很繁琐的。RT模式把用户从这种繁琐工作中解脱出来,用户不再需要关注哪些库上SQL失败了,也不需要自己重试SQL。

6. TXC的ACID权衡
6.1 原子性与一致性
在AT方式下,事务范围内所有分支的写SQL操作应该要么都执行,要么都不执行。

在弱隔离性策略下,有微小的可能无法保证一致性,参考”弱隔离性策略”。

6.2 持久性
所有分支的状态都会被持久化,整个全局事务的状态也需要被持久化,以便在TXC Server或者client出故障之后可以回滚事务。

6.3 隔离性
TXC提供了丰富的配置策略,其中与隔离性有关的有3种:弱隔离性策略、强隔离性策略,和重试策略。下面对这3种策略一一进行介绍。

(a) 弱隔离性策略(缺省策略)
基于追求高吞吐量的考虑,TXC的缺省策略是弱隔离性,即读未提交。在应用程序尚未执行txc_commit()之前,各个分支上写操作的结果已经可以被外界观察到,并且可以被并发地修改。

当事务需要被回滚时,TXC会以反向补偿机制来让事务各个参与者保持数据的一致性。但如果事务中被修改的某条记录如果在回滚前就被别的事务并发修改了,回滚就无法进行。此时TXC系统将产生报警,需要管理员手工订正数据。我们称这个问题为” 脏写”。因此,如果应用的业务逻辑决定了不太可能对同一行记录进行并发修改,缺省模式就比较适合这样的应用;否则,如果预期对同一行记录的修改可能会相对频繁,则推荐使用强隔离性策略。

(b)强隔离性策略
TXC的强隔离性策略可以防前面提到的脏写问题,避免手工订正。

在TXC的强隔离性策略下,所有的写操作都会检测目标记录是否已经被某个进行中的事务修改为中间状态。如果是,则写操作会在一段时间内重试以读取一个最终状态,或者在这段时间过去之后返回失败。同样的行为也适用于select for update。

值得突出的是,如果应用A发起一个事务,在它进行中,同一个事务的其他分支(即随着HSF传播出去的事务分支)也可以并发地修改同一条记录,从而实现了紧耦合的事务。目前该功能仅在支持XA的商业解决方案中才被支持。

对于纯读操作,为了吞吐量考虑,TXC一般不去做类似检测。当事务进行中对某个记录进行修改时,该中间状态可能会被其他事务读到。我们称这个问题为” 脏读”。一般来说,这样的脏读不会有严重后果;但如果应用确实需要避免脏读,那么可以在select语句上加上一些特定的hint。对于带这些hint的select语句,TXC也会牺牲性能,先做检测,如果相关记录属于某个事务的中间状态,select会在一段时间内重试以读取一个最终状态,或者在这段时间过去之后返回失败。

强隔离性策略要求所有可能访问业务表的应用都部署TXC并配置强隔离性策略。

对于未部署TXC的系统,它们可以在事务进行中,跳过TXC直接并发修改业务表的同一条记录,从而造成脏写。目前尚无手段避免这样的脏写。

(c)重试策略
重试策略并不属于严格意义上的事务范畴,因为需要重试的操作即使失败,也不会导致事务回滚。

让我们考察这样的例子:当一个用户注销的时候,需要将该用户在多个表中的相关记录删除。这样的操作即使一时会因为网络、数据库主库当机等原因失败,它最终也基本上能保证会成功,因此业务往往不希望这样的操作会引起整个事务的回滚,而是希望简单地重试该操作,直到成功。

在重试策略下,TXC可以”记住”这样的操作,在适当的时候代替应用执行重试。此时,可能会在比较大的时间窗口内,原子性和一致性没有被满足。应用程序需要根据自己的情况,决定是否采用重试策略。

7. TXC事务的传播
通过一些简单的配置,TXC事务即可随HSF服务传播。即,当应用A发起了一个事务,在事务进行中调用应用B的服务BS1时,如果配置得当,BS1中的所有写SQL操作将被自动纳入此事务。应用B只需要安装TXC的客户端包并进行少量必要的配置。BS1不需要为此做任何代码修改。

为了避免服务在不知情的情况下被卷入事务,一个HSF服务在缺省情况下是拒绝事务传播的,即接受事务传播的配置必须显示地被打开。

该事务传播可以是多分支(A调用B、A调用C,以此类推)、多层级(A调用B、B调用C,以此类推)的。对于多层级的传播,要求每层服务都部署了TXC,并且显示地配置了接受事务传播,否则传播会被中断,不能进入后面的所有层级。

该事务传播可以是异步的,但在事务提交前,应用必须保证所有异步操作都必须返回。

8. TXC的非功能性特点
8.1 可用性
当TXC Server有一台宕机时,当前有部分事务会被回滚,但新发起的事务仍能继续进行。

当TXC Server有多台同时宕机时,会有部分事务被回滚,部分新发起的事务不能继续进行。

当TXC Client所在的应用机器宕机时,该机器上发起或参与的所有事务都将被回滚,其中该机器发起的事务可能会被另一台同一应用、同一数据库DBKey的机器回滚(如果有的话)。

8.2 可扩展性
TXC各个Server之间是share nothing的架构,因此可以水平扩展。

各应用会随机地连接到一个TXC Server。当新增TXC Server时,Diamond配置会被推送,负载很快会重新平衡。

8.3 TXC的数据安全性
TXC不会保留、分析业务数据。TXC仅仅在事务进行中会暂存一部分业务数据到业务数据库,以准备可能的回滚。TXC不会试图去理解这些数据背后的业务含义。当事务结束后这些数据会被立刻删除。

8.4 TXC支持的语言和平台
目前仅支持Java、Linux

8.5 兼容性考虑
TXC的不同模式之间可以互相兼容。即,如果应用A是AT模式,通过HSF调用配置为MT(或RT)模式的B服务,A与B的所有写SQL操作可以被包含到同一个TXC分布式事务中。以此类推。

8.6 部署和运维
TXC在部署上分两个部分,TXC Server和TXC Client。

TXC Server是若干个独立的服务器,通常由北京中间件团队维护,但特殊情况下也可以部署在应用方自己的机房内,由应用方自己维护。

TXC Client是一个或多个二分包,应用需要依赖这些二方包,并通过若干配置(至少需要能通过配置连接上一个TXC Server)使用分布式事务功能。

参考:TXC分布式事务简介