seata分布式事务~TCC模式
TCC模式与AT模式非常相似,每个阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。
Try:资源的检测和预留。
Confirm:完成资源操作业务,要求Try成功Confirm一定要成功。
Cancel:预留资源释放,可以理解为try的反向操作。
优点:相比AT模型,无需生成快照,无需使用全局锁,性能最强。
不依赖数据库事务,而是依赖补偿操作,可以用于非事务性数据库。
缺点:有代码侵入,需要人为编写代码。
需要考虑人工编写的Confirm Cancel失败的情况。要做好幂等处理。
TCC模式的一些问题:
空回滚:某分支事务的try阶段阻塞,可能导致全局事务超时而触发第二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚。
业务悬挂:对于已经空回滚的业务,之前被阻塞的try操作恢复,继续执行try,就永远不可能confirm或cancel。
实现TCC模式:
要解决空回滚以及业务悬挂的问题,就需要记录事务的状态。
可以创建一个表来记录当前事务的状态。
DROP TABLE IF EXISTS `account_freeze_tbl`;
CREATE TABLE `account_freeze_tbl` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`freeze_money` int(11) UNSIGNED NULL DEFAULT 0,
`state` int(1) NULL DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
PRIMARY KEY (`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
需要声明一个TCC接口。
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
/**
* TCC事务的接口
*
* @author langhai.cc
* @date 2023-01-10 15:00
*/
@LocalTCC
public interface AccountTCCService {
/**
* try方法声明 余额减少
*
* @param userId
* @param money
*/
@TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "money") int money);
/**
* confirm方法声明
*
* @param ctx
* @return
*/
boolean confirm(BusinessActionContext ctx);
/**
* cancel方法声明
*
* @param ctx
* @return
*/
boolean cancel(BusinessActionContext ctx);
}
TCC接口实现类
/**
* TCC事务的接口 实现类
*
* @author langhai.cc
* @date 2023-01-10 15:10
*/
@Service
@Slf4j
public class AccountTCCServiceImpl implements AccountTCCService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFreezeMapper freezeMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public void deduct(String userId, int money) {
// 0.获取事务id
String xid = RootContext.getXID();
// 业务悬挂处理
AccountFreeze oldFreeze = freezeMapper.selectById(xid);
if(oldFreeze != null){
return;
}
// 1.扣减可用余额
accountMapper.deduct(userId, money);
// 2.记录冻结金额,事务状态
AccountFreeze freeze = new AccountFreeze();
freeze.setUserId(userId);
freeze.setFreezeMoney(money);
freeze.setState(AccountFreeze.State.TRY);
freeze.setXid(xid);
freezeMapper.insert(freeze);
}
@Override
public boolean confirm(BusinessActionContext ctx) {
// 1.获取事务id
String xid = ctx.getXid();
// 2.根据id删除冻结记录
int count = freezeMapper.deleteById(xid);
return count == 1;
}
@Override
public boolean cancel(BusinessActionContext ctx) {
// 0.查询冻结记录
String xid = ctx.getXid();
AccountFreeze freeze = freezeMapper.selectById(xid);
// 获取参数
String userId = ctx.getActionContext("userId").toString();
// 空回滚处理 判断表中是否有事务记录
if(freeze == null){
// 说明没有事务记录 需要处理空回滚
freeze = new AccountFreeze();
freeze.setUserId(userId);
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
freeze.setXid(xid);
freezeMapper.insert(freeze);
return true;
}
// 幂等处理
if(freeze.getState() == AccountFreeze.State.CANCEL){
// 已经处理过一次CANCEL,无需重复处理。
return true;
}
// 1.恢复可用余额
accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());
// 2.将冻结金额清零,状态改为CANCEL
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
int count = freezeMapper.updateById(freeze);
return count == 1;
}
}
当出现异常情况的时候,成功进行回滚,并在mysql中生成记录。
控制台输出:
// TCC模式
branchType=TCC
// 成功进行回滚
Branch Rollbacked result: PhaseTwo_Rollbacked
// 回滚SQL执行 余额增加回来
Preparing: update account_tbl set money = money + 200 where user_id = ?
// 事务回滚记录
Preparing: UPDATE account_freeze_tbl SET user_id=?, freeze_money=?, state=? WHERE xid=?
有任何问题联系 QQ 676558206 email [email protected] [email protected]
非特殊说明,本文版权归 langhai 所有,转载请注明出处。
本文标题: seata分布式事务~TCC模式
延伸阅读
- 浪海博客系统友情链接说明
- 浪海同志的一生
- 浪海皇室 QQ飞车手游
- 浪海博客系统部署说明
- minio 相关说明
- rabbitMQ 相关说明
- mysql相关说明
- java基础面试题002
- gateway服务网关基本使用
- ribbon负载均衡