本文主要基于 TCC-Transaction 1.2.3.3 正式版
1. 概述
2. TCC 原理
3. TCC-Transaction 原理
4. 事务与参与者
-
4.1 事务
4.2 参与者
5. 事务管理器
-
5.1 发起根事务
5.2 传播发起分支事务
5.3 传播获取分支事务
5.4 提交事务
5.5 回滚事务
5.6 添加参与者到事务
6. 事务拦截器
-
6.1 Compensable
6.2 可补偿事务拦截器
6.3 资源协调者拦截器
666. 彩蛋
友情提示:欢迎关注公众号【芋道源码】。?关注后,拉你进【源码圈】微信群和【芋艿】搞基嗨皮。
友情提示:欢迎关注公众号【芋道源码】。?关注后,拉你进【源码圈】微信群和【芋艿】】搞基嗨皮。
友情提示:欢迎关注公众号【芋道源码】。?关注后,拉你进【源码圈】微信群和【芋艿】】搞基嗨皮。
1. 概述
本文分享 TCC 实现。主要涉及如下三个 Maven 项目:
tcc-transaction-core
:tcc-transaction 底层实现。tcc-transaction-api
:tcc-transaction 使用 API。tcc-transaction-spring
:tcc-transaction Spring 支持。
你行好事会因为得到赞赏而愉悦
同理,开源项目贡献者会因为 Star 而更加有动力
为 TCC-Transaction 点赞!传送门
OK,开始我们的第一段 TCC 旅程吧。
ps:笔者假设你已经阅读过《tcc-transaction 官方文档 —— 使用指南1.2.x》。
ps2:未特殊说明的情况下,本文事务指的是 TCC事务。
2. TCC 原理
FROM https://support.hwclouds.com/devg-servicestage/zh-cn_topic_0056814426.html
TCC事务
为了解决在事务运行过程中大颗粒度资源锁定的问题,业界提出一种新的事务模型,它是基于业务层面的事务定义。锁粒度完全由业务自己控制。它本质是一种补偿的思路。它把事务运行过程分成 Try、Confirm / Cancel 两个阶段。在每个阶段的逻辑由业务代码控制。这样就事务的锁粒度可以完全自由控制。业务可以在牺牲隔离性的情况下,获取更高的性能。
Try 阶段
-
完成所有业务检查( 一致性 )
预留必须业务资源( 准隔离性 )
Try :尝试执行业务
Confirm / Cancel 阶段:
-
释放 Try 阶段预留的业务资源
Cancel 操作满足幂等性
真正执行业务
不做任务业务检查
Confirm 操作满足幂等性
Confirm :确认执行业务
Cancel :取消执行业务
Confirm 与 Cancel 互斥
整体流程如下图:
红框部分功能由
tcc-transaction-core
实现:-
启动业务活动
登记业务操作
提交 / 回滚业务活动
黄框部分功能由
tcc-transaction-http-sample
实现( 官方提供的示例项目 ):-
Try 操作
Confirm 操作
Cancel 操作
与 2PC协议 比较:
位于业务服务层而非自愿层
没有单独的准备( Prepare )阶段,Try 操作兼备自愿操作与准备能力
Try 操作可以灵活选择业务资源的锁定粒度
较高开发成本
参考资料:
《支付宝运营架构中柔性事务指的是什么?》
《分布式事务的典型处理方式:2PC、TCC、异步确保和最大努力型》
3. TCC-Transaction 原理
在 TCC 里,一个业务活动可以有多个事务,每个业务操作归属于不同的事务,即一个事务可以包含多个业务操作。TCC-Transaction 将每个业务操作抽象成事务参与者,每个事务可以包含多个参与者。
参与者需要声明 try / confirm / cancel 三个类型的方法,和 TCC 的操作一一对应。在程序里,通过 @Compensable 注解标记在 try 方法上,并填写对应的 confirm / cancel 方法,示例代码如下:
// try
@Compensable(confirmMethod = "confirmRecord", cancelMethod = "cancelRecord", transactionContextEditor = MethodTransactionContextEditor.class)
public String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {}
// confirm
public void confirmRecord(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {}
// cancel
public void cancelRecord(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {}
在示例代码中,我们看到 TransactionContext,事务上下文,这个是怎么生成的呢?这里先卖一个关子。
TCC-Transaction 有两个拦截器,通过对 @Compensable AOP 切面( 参与者 try 方法 )进行拦截,透明化对参与者 confirm / cancel 方法调用,从而实现 TCC 。简化流程如下图:
第一个拦截器,可补偿事务拦截器,实现如下功能:
在 Try 阶段,对事务的发起、传播。
在 Confirm / Cancel 阶段,对事务提交或回滚。
为什么会有对事务的传播呢?在远程调用服务的参与者时,会通过"参数"( 需要序列化 )的形式传递事务给远程参与者。
第二个拦截器,资源协调者拦截器,实现如下功能:
在 Try 阶段,添加参与者到事务中。当事务上下文不存在时,进行创建。
实际拦截器对事务的处理会比上图复杂一些,在本文「6. 事务拦截器」详细解析。
在 TCC-Transaction 代码实现上,组件分层如下图:
本文按照如下顺序分享:
「4. 事务拦截器」
「5. 事务管理器」
「6. 事务管理器」
内容是自下而上的方式分享,每个组件可以更加整体的被认识。当然这可能对你理解时产生一脸闷逼,所以推荐两种阅读方式:
简读 x 1 + 深读 x 1
倒着读,发现未分享的方法,全文检索该方法。
事务存储器在《TCC-Transaction 源码解析 —— 事务存储于恢复》详细解析。
事务恢复在《TCC-Transaction 源码解析 —— 事务恢复》详细解析。
4. 事务与参与者
在 TCC 里,一个事务( org.mengyun.tcctransaction.Transaction
) 可以有多个参与者( org.mengyun.tcctransaction.Participant
)参与业务活动。类图关系如下( 打开大图 ):
4.1 事务
Transaction 实现代码如下:
public class Transaction implements Serializable {
private static final long serialVersionUID = 7291423944314337931L;
/**
* 事务编号
*/
private TransactionXid xid;
/**
* 事务状态
*/
private TransactionStatus status;
/**
* 事务类型
*/
private TransactionType transactionType;
/**
* 重试次数
*/
private volatile int retriedCount = 0;
/**
* 创建时间
*/
private Date createTime = new Date();
/**
* 最后更新时间
*/
private Date lastUpdateTime = new Date();
/**
* 版本号
*/
private long version = 1;
/**
* 参与者集合
*/
private List<Participant> participants = new ArrayList<Participant>();
/**
* 附带属性映射
*/
private Map<String, Object> attachments = new ConcurrentHashMap<String, Object>();
/**
* 添加参与者
*
* @param participant 参与者
*/
public void enlistParticipant(Participant participant) {
participants.add(participant);
}
/**
* 提交 TCC 事务
*/
public void commit() {
for (Participant participant : participants) {
participant.commit();
}
}
/**
* 回滚 TCC 事务
*/
public void rollback() {
for (Participant participant : participants) {
participant.rollback();
}
}
}
xid,事务编号( TransactionXid ),用于唯一标识一个事务。使用 UUID 算法生成,保证唯一性。
org.mengyun.tcctransaction.api.TransactionXid
实现javax.transaction.xa.Xid
接口,实现代码如下:public class TransactionXid implements Xid, Serializable {
private static final long serialVersionUID = -6817267250789142043L;
}/**
* xid 格式标识符
*/
private int formatId = 1;
/**
* 全局事务编号
*/
private byte[] globalTransactionId;
/**
* 分支事务编号
*/
private byte[] branchQualifier;-
TODO 为什么要继承 Xid 接口?
status,事务状态( TransactionStatus )。
org.mengyun.tcctransaction.api.TransactionStatus
实现代码如下:public enum TransactionStatus {
/**
* 尝试中状态
*/
TRYING(1),
/**
* 确认中状态
*/
CONFIRMING(2),
/**
* 取消中状态
*/
CANCELLING(3);
}private int id;
transactionType,事务类型( TransactionType )。
org.mengyun.tcctransaction.common.TransactionType
实现代码如下:public enum TransactionType {
}/**
* 根事务
*/
ROOT(1),
/**
* 分支事务
*/
BRANCH(2);
int id;-
在「6.2 可补偿事务拦截器」有详细解析,可以看到看到这两种事务是如何发起。
retriedCount,重试次数。在 TCC 过程中,可能参与者异常崩溃,这个时候会进行重试直到成功或超过最大次数。在《TCC-Transaction 源码解析 —— 事务恢复》详细解析。
version,版本号,用于乐观锁更新事务。在《TCC-Transaction 源码解析 —— 事务存储器》详细解析。
attachments,附带属性映射。在《TCC-Transaction 源码解析 —— Dubbo 支持》详细解析。
提供
#enlistParticipant()
方法,添加事务参与者。提供
#commit()
方法,调用参与者们提交事务。提供
#rollback()
方法,调用参与者回滚事务。
4.2 参与者
Participant 实现代码如下:
public class Participant implements Serializable {
private static final long serialVersionUID = 4127729421281425247L;
/**
* 事务编号
*/
private TransactionXid xid;
/**
* 确认执行业务方法调用上下文
*/
private InvocationContext confirmInvocationContext;
/**
* 取消执行业务方法
*/
private InvocationContext cancelInvocationContext;
/**
* 执行器
*/
private Terminator terminator = new Terminator();
/**
* 事务上下文编辑
*/
Class<? extends TransactionContextEditor> transactionContextEditorClass;
/**
* 提交事务
*/
public void commit() {
terminator.invoke(new TransactionContext(xid, TransactionStatus.CONFIRMING.getId()), confirmInvocationContext, transactionContextEditorClass);
}
/**
* 回滚事务
*/