这里写自定义目录标题
10.1 什么是TCC?
1、TCC的基本理解
TCC是三个单词的缩写(try-confirm-cancel) TCC分布式事务最核⼼的思想就是在应⽤层(可以理解为我们编码)将⼀个完整的事务分为3个阶段,Try阶段、Confirm确认阶段、Cancel取消阶段
TCC虽然也是基于两阶段提交协议,但是和AT的两阶段提交协议不同,AT的两阶段提交我们只关注业务逻辑的编写,提交还是回滚都是通过undo_log实现,⽽TCC的业务逻辑,提交逻辑,回滚逻辑都由⾃⼰完成
2、三个阶段详解
-
try 阶段
严格意义上 try阶段不会做任何业务逻辑,仅仅做业务的⼀致性检查和做相应资源的预留
1:完成所有业务的检查 确保数据的⼀致性
2:预留必要的业务资源,确保数据的隔离性 -
confirm阶段
当分⽀的所有的try阶段都执⾏完成之后,开始执⾏confirm阶段,通常情况下 如果采⽤了TCC⽅案解决事务时都会认为confirm阶段是不会出错的,换句话说 只要try阶段执⾏成功了 confirm阶段⼀定会成功,如果confirm真的出错了,要提供重试机制或者⼈⼯⼲预 并且在confirm阶段不会做任何的业务检查 直接操作try阶段预留的业务资源 -
cancel阶段
当try阶段执⾏时出现异常或执⾏错误的情况下,此时需要执⾏回滚操作, 此时就会执⾏cancel⽅法,在cancel的⽅法中释放try阶段预留的资源,和confirm阶段⼀样 如果try阶段执⾏成功,⼀般我们都会认为cancel也会成功,如果真的失败了 此时需要⼈⼯介⼊或者提供重试机制
10.2 TCC模式的原理流程图
10.3 TCC解决多数据源问题
1、环境搭建
见 gitee : https://gitee.com/houchen1996/seata-shangma 的模块 tcc-multidatasource-demo
2、TCC模式解决分布式事务问题
1) 添加 Seata 依赖
2)添加Seata配置
3)编写方法,定义 commit 操作,和canel操作
4)使用注解 @LocalTCC和@TwoPhaseBusinessAction(name = “jian”, commitMethod = “commit”, rollbackMethod = “cancel”)
5)使用 @GlobalTransactional 注解
测试 : 请求 http://localhost:8080/user/transfer?fromId=1&toId=1&monoey=100
【注意】TCC虽然没有undo_log表,但是在预留资源的时候还需要列的支持
10.4 TCC⾯临的问题 - 2、空回滚问题
1、什么空回滚?
空回滚表示 在⼀次事务中 并没有执⾏try⽅法,直接执⾏了rollback进⾏了回滚从⽽导致数据错误
举例子: 张三给李四赚钱5000, 执⾏了张三的try⽅法,由于钱不够,并没有执⾏转钱锁定资源 ⽽是直接⾛了rollback⽅法
2、举例
还是上面的工程, 在转账操作中加上如果转账金额大于账户金额,抛出异常的逻辑
@Override
public void reduceMoney(BusinessActionContext context, int userId, double money) {
Double moneyById = nongUserMapper.findMoneyById(userId);
if (moneyById < money) {
throw new RuntimeException("余额不足");
}
nongUserMapper.reduceMoney(userId, money);
}
请求: http://localhost:8080/user/transfer?fromId=1&toId=1&monoey=5000
查看结果
3、空回滚解决
10.5 TCC⾯临的问题 - 1、幂等性问题
1、幂等性问题
如果服务器⽹络异常 或者⽹络延迟等等,导致超时 ⼀般的TCC管理器都会有重试机制,因为超时⽽触发重试 (commit或cancel) 就有可能导致数据不⼀致 所以要求confirm和cancel⽅法必须满⾜幂等性
TCC 中保持幂等性理解:
在⼀次事务中,try阶段执⾏⼀次, rollback或者commit执⾏⼀次。 幂等性表示 在⼀次事务中,就算try阶段执⾏多次,rollback执⾏多次或者commit⽅法执⾏多次,对当前事务是没有影响的,事务的结果和执⾏⼀次的结果是⼀致的。
2、幂等性问题解决
改造 NongUserService
@Override
public void reduceMoney(BusinessActionContext context, int userId, double money) {
if (StringUtils.isEmpty(TransactionResultHolder.getResult(getClass(), context.getXid()))) {
Double moneyById = nongUserMapper.findMoneyById(userId);
if (moneyById < money) {
throw new RuntimeException("余额不足");
}
nongUserMapper.reduceMoney(userId, money);
//锁定资源后记录状态
TransactionResultHolder.setResult(getClass(), context.getXid(), "xxxx");
}
}
10.6 TCC面临的问题 - 3、悬挂问题
1、什么是悬挂
最简单的理解:
执⾏事务时 由于各种原因 先⾛回滚或者提交 再⾛try阶段
2、如何解决悬挂问题
1) 新建表结构
CREATE TABLE IF NOT EXISTS `tcc_fence_log`
(
`xid` VARCHAR(128) NOT NULL COMMENT 'global id',
`branch_id` BIGINT NOT NULL COMMENT 'branch id',
`action_name` VARCHAR(64) NOT NULL COMMENT 'action name',
`status` TINYINT NOT NULL COMMENT 'status(tried:1;committ
ed:2;rollbacked:3;suspended:4)',
`gmt_create` DATETIME(3) NOT NULL COMMENT 'create time',
`gmt_modified` DATETIME(3) NOT NULL COMMENT 'update time',
PRIMARY KEY (`xid`, `branch_id`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_status` (`status`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
2) 业务系统的业务方法,开启防悬挂
10.7 TCC微服务场景
1、微服务场景搭建
见 gitee : https://gitee.com/houchen1996/seata-shangma 的模块seata-tcc-cloud-order
2、TCC解决微服务的分布式事务问题
见 gitee : https://gitee.com/houchen1996/seata-shangma 的模块seata-tcc-cloud-order
3、TCC使用表结构方式同时解决上述提到的三大问题
暂时没看,实在不想敲了!