理论
论文
Pat Helland于2007年发表tcc论文《Life beyond Distributed Transactions: an Apostate’s Opinion》,提出了TCC的概念,在论文中,TCC还是以Tentative-Confirmation-Cancellation命名的,后来Atomikos公司改名为Try-Confirm-Cancel。
TCC事务相对于传统事务(XA, Two-Phase-Commit),其特征在于它不依赖资源管理器(RM)对XA的支持,而是通过对(由业务系统提供的)业务逻辑的接口调用来实现分布式事务。
理论
TCC事务有一系列子事务构成,每个子事务所属的RM需要提供Try-Confirm-Cancel三个接口来给事务协调者调用。
TCC和2PC类似,也是分为两个阶段,Try阶段和Confirm或Cancel阶段。
- 第一阶段Try:进行资源检查与预留。尝试执行,完成所有业务一致性检查,预留业务资源,但不会对资源进行锁定。
第二阶段
- Confirm:确认,真正执行业务,处理Try 阶段预留的业务资源,Confirm 操作要求具备幂等设计,只要进入到Confirm阶段,则事务就已经处于提交状态了,所以Confirm 失败后需要进行重试直到成功为止。
- Cancel:取消,释放 Try 阶段预留的业务资源。Cancel 阶段的异常和 Confirm 阶段异常处理方案基本上一致,要求满足幂等设计。
TCC不会对在数据资源层面对资源进行锁定,但是会进行业务层面的预留,以将资源层的加锁升级到业务层上,这样业务层可以灵活实现隔离性,达到准隔离性的同时提高并发性能。
举例
仍然使用转账的例子:A和B账户余额都是100元,A转账30元给B。
这个TCC事务有两个子事务,
- 子事务A:从A的账户减去30元,提交后A的账户余额为70元
- 子事务B:给B的账户添加30元,提交后B的账户余额为130元
需要在资源层将增加一个资源字段冻结余额
执行事务:
第一阶段,Try:
- 检查A的余额是否大于等于30元,利用资源层事务的原子性,在A的冻结余额中增加-30元,不修改A的余额
,所以A的账户余额还是100元。其他事务看到的余额总数还是100元。 - 将B的冻结余额增加+30元,不修改B的账户余额,其他事务看到的B的账户余额仍然是100元。
- 检查A的余额是否大于等于30元,利用资源层事务的原子性,在A的冻结余额中增加-30元,不修改A的余额
第二阶段,
Confirm:
- 将A的账户的冻结余额的-30元加到账户余额中,那么A的账户余额等于70元。
- 将B的账户的冻结余额的+30元加到账户余额中,B的账户余额等于130元。
Cancel:
- 将A的账户的冻结余额清空
- 将B的账户的冻结余额清空
隔离性:
事务执行期间,A和B的账户余额还是100元,但是如果其他事务需要并发修改A或B的账户余额,则需要考虑冻结余额。
- 如果其他事务需要修改A的账户余额,则需要查看冻结余额,发现已经冻结了-30元,则只有70元是可用的
- 如果其他事务需要修改B的账户余额,则需要查看冻结余额,发现已经冻结了+30元,则只有100元是可用的
或者事务执行期间,不允许其他事务修改A和B的的账户余额。
所以,将锁从资源层上升到业务层,隔离性更加灵活,需要如何实现隔离性,完全由业务决定,也避免了资源层并发带来的回滚。
使用场景
- 适用于业务流程短的事务
- 隔离性较好:对中间状态有约束的业务
优点
- 并发度较高,不需要像XA事务对资源进行锁定
- 隔离性比Saga好,取决于业务实现
缺点
- 对业务有侵入性:需要实现Try,Confirm,Cancel接口
- 参与者需要实现幂等
设计
基础概念
TCC事务由三种角色组成
- TCC事务提交者:事务发起者,在分布式事务中统称为AP
- TCC事务协调者:接受AP的事务请求,管理TCC事务,在分布式事务中统称为事务协调者(TC : Transaction Coordinator)
- TCC子事务参与者:协调者将子事务提交给参与者执行, 在分布式事务中统称为资源管理器(RM:Resource Manager)。参与者需要包证接口的幂等性
事务状态
- committed : 已经提交。最终态
- aborted :已经回滚。最终态
- failed :回滚中或提交失败(可能重试中)。非终态
- prepared:提交中。非终态
TCC事务协调者的实现
TCC Service
- 提供RESTful API,AP通过API进行事务提交或查询
- TCC service将请求封装成TCC事务对象,提交给TCC Executor执行
API结果响应:
- 如果是同步请求,则等待TCC Executor处理完成,再将结果封装成TccResponse
给AP - 如果是异步请求,则马上返回给AP
- 如果是同步请求,则等待TCC Executor处理完成,再将结果封装成TccResponse
- 通知:TC通知 AP TCC事务执行的结果
TCC Executor
- TCC事务真正的执行者,管理TCC事务的状态转换,重试,持久化等等,不涉及与RM和AP的交互
- TCC子事务处理和回滚:TCC Executor并不处理子事务,而是将子事务操作写入Channel,由外层去向RM发送子事务请求
- TCC事务通知:TCC Executor不执行具体的通知操作,而是将操作写入Channel,由外层去执行AP
- 数据持久化:TCC Executor将TCC数据存储到本地数据库中来保证数据的持久化,也同时依赖本地数据库的事务特性。
事务流程
- 开启TCC事务:AP调用 TC的prepare接口,开启一个TCC事务
循环处理所有子事务:如果某个步骤失败,重试策略取决于AP
- 注册子事务(分支事务):AP调用TC register接口,注册一个子事务,注册成功再执行下一步
- Try:AP调用RM的Try接口
Confirm:如果所有子事务都成功注册和执行try,则AP调用TC的Confirm接口,告知事务可以执行提交
- TC循环调用所有参与子事务的RM的Confirm接口,如果返回失败则会一直重试指导成功
Cancel:如果子事务执行失败,AP不进行重试则调用TC的Cancel接口取消TCC事务;或者达到TCC事务过期时间,TC会自行取消TCC事务。
- 循环调用所有子事务的RM,调用RM的Cancel接口
异常情况处理
分布式事务实现的一个难点就是时序问题,主要体现在:
- 服务器的时钟不同步
- 请求乱序
因此会产生一些不可预测的异常。
TCC事务过期
如果TCC事务过期,则TC需要先将事务的状态标记为"需要Cancel"状态,再调用RM的Cancel接口来取消子事务。
如果后续TC接受到此TCC事务的Confirm请求,TC应先查看本地数据库中此事务状态,如果已经处于Cancel或者已经Cancel,则应该拒绝Confirm请求。
同时,TC对数据库中事务的状态修改,应该使用数据库事务来确保隔离性和一致性,因为可能有多个TC同时存在。
回滚异常
异常流程如下:
- TC向RM发送Try请求
- 由于网络原因Try请求仍然处于发送中,没有到达RM
- AP调用TC取消TCC事务,或者TCC事务过期,TC自行取消
- TC调用RM的Cancel接口取消子事务
- 异常点1 :RM收到Cancel请求,发现此子事务没有执行过Try,产生异常
- 异常点2 :当RM收到Cancel请求后,之前由于网络原因阻塞的Try请求到达RM,如果RM执行这个Try,则会产生数据不一致的异常。
- 异常点3:由于重试策略,导致AP向RM发送了多于1次的Try请求,或者TC向RM发送了多次Confirm或者Cancel请求。
所以,为了避免上面异常情况,需要进行如下检查
RM收到Try操作, 检查是否有Try记录
- 如果有记录:如果已经执行过Try,直接返回成功,确保幂等性
如果没有Try记录,则检查是否有Cancel记录,
- 如果有Cancel记录,则拒绝执行Try
- 如果没有Cancel记录,则执行Try,且更改子事务状态为"已经执行try"。
RM收到Cancel操作,查看此子事务是否有Cancel记录,
- 如果有Cancel记录,则直接返回Cancel成功,确保幂等性
如果没有Cancel记录,则检查是否有Try记录
- 如果有Try记录,则执行Cancel动作
- 如果没有Try记录,则将此Cancel请求记录下来,如果后续收到Try请求,则应该拒绝Try请求
本地事务
无论是RM还是TC或AP,在修改其数据时要考虑时序问题和时钟漂移问题导致的乱序,利用本地数据库的事务隔离(可序列化级别)特性来检查事务状态和修改状态。
如:
RM收到Try请求后,需要检查是否有Cancel记录或Try记录,没有才能需要执行Try。检查和执行需要在一个事务中,避免其他线程或进程同时对子事务进行Cancel或者Try修改。
开发示例
以银行转账为示例,演示如果通过SDK来实现一个tcc事务。
此演示使用了SDK的高级API,屏蔽了各种异常情况的处理,提高了开发的效率。
开发示例
https://github.com/ikenchina/octopus/blob/master/README_tcc_demo.md
SDK
client sdk实现了AP和RM的sdk封装,方便进行tcc开发。
client SDK:https://github.com/ikenchina/octopus/blob/master/README_tcc_sdk.md
API设计
https://github.com/ikenchina/octopus/octopus/README_tcc_api.md
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。