Seata1.3(TCC模式) + Zookeeper +Dubbo 整合
Seata下载
注:TCC模式
AT模式参考前边,有写过
TCC模式的解决方案是最终一致性,通过两个阶段去处理业务,第一步预处理阶段,第二步提交或者回滚
测试代码:
业务场景:发布任务,扣除任务佣金,之后业务异常
@Override
@GlobalTransactional(rollbackFor = Exception.class)
public JsonResult releaseTaskTCC() {
Task task = Task.getTask();
//预发布任务
taskTccAction.prepareCreateOrder(null, task);
//扣除发布任务所需要的佣金 1块钱
memberAccountService.updateAccountTCC(task.getStoreId(), MemberAccountConstant.OUT, BigDecimal.ONE);
//扣除完之后模拟异常,看扣除的钱是否回滚
int i = 1 / 0;
return JsonResult.builder().build();
}
发布任务TCC的两个阶段业务处理接口
/**TCCAction**/
@LocalTCC
public interface TaskTccAction {
/**
* 第一阶段的方法,通过注解指定第二阶段的两个方法名
* @param businessActionContext 上下文对象,用来在两个阶段之间传递数据
* @param task
* @return
*/
@TwoPhaseBusinessAction(name = "orderTccAction", commitMethod = "commit", rollbackMethod = "rollback")
boolean prepareCreateOrder(BusinessActionContext businessActionContext,@BusinessActionContextParameter(paramName = "task") Task task);
/**
* 第二阶段 - 提交
* @param businessActionContext
* @return
*/
boolean commit(BusinessActionContext businessActionContext);
/**
* 第二阶段 - 回滚
* @param businessActionContext
* @return
*/
boolean rollback(BusinessActionContext businessActionContext);
}
发布任务TCC的两个阶段业务处理实现
/**
* 发布任务TCC
*
* @author zjj
*/
@Component
@Slf4j
public class TaskTccActionImpl implements TaskTccAction {
@Resource
private TaskMapper taskMapper;
@Resource
private TaskTxLogMapper taskTxLogMapper;
@Transactional(rollbackFor = Exception.class)
@Override
public boolean prepareCreateOrder(BusinessActionContext businessActionContext, Task task) {
String xid = businessActionContext.getXid();
long branchId = businessActionContext.getBranchId();
log.info("创建 TaskTccActionImpl 第一阶段,预留资源 - xid:{}", xid);
//幂等判断 判断order_tx_log表中是否有try日志记录,如果有则不再执行
if (taskTxLogMapper.isExistTaskTxLog(xid, TransactionalStatusEnum.STATUS_TRY.getCode()) > 0) {
log.info("TaskTccActionImpl try 已经执行,无需重复执行,xid:{}", xid);
return true;
}
//try悬挂处理,如果cancel、confirm有一个已经执行了,try不再执行
if (taskTxLogMapper.isExistTaskTxLog(xid, TransactionalStatusEnum.STATUS_COMMIT.getCode()) > 0 || taskTxLogMapper.isExistTaskTxLog(xid, TransactionalStatusEnum.STATUS_COMMIT.getCode()) > 0) {
log.info("TaskTccActionImpl try悬挂处理 cancel或confirm已经执行,不允许执行try,xid:{}", xid);
return true;
}
task.setTxStatus(TransactionalStatusEnum.STATUS_TRY.getCode());
taskMapper.insert(task);
//事务成功,保存一个标识,供第二阶段进行判断
Integer status = TransactionalStatusEnum.STATUS_TRY.getCode();
TaskTxLog taskTxLog = buildOrderTxLog(xid, branchId, status);
taskTxLogMapper.insert(taskTxLog);
return true;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean commit(BusinessActionContext businessActionContext) {
String xid = businessActionContext.getXid();
long branchId = businessActionContext.getBranchId();
log.info("创建 TaskTccActionImpl 第二阶段提交,修改订单状态1 - xid:{}", xid);
// 防止空提交,如果try阶段没有成功,就是一个空提交,直接返回
if (taskTxLogMapper.isExistTaskTxLog(xid, TransactionalStatusEnum.STATUS_TRY.getCode()) <= 0) {
log.info("TaskTccActionImpl confirm 空提交...xid:{}", xid);
return true;
}
// 防止幂等性,如果commit阶段重复执行则直接返回
if (taskTxLogMapper.isExistTaskTxLog(xid, TransactionalStatusEnum.STATUS_COMMIT.getCode()) > 0) {
log.info("TaskTccActionImpl confirm 已经执行,无需重复执行...xid:{}", xid);
return true;
}
//修改任务状态
Task task = JSON.parseObject(businessActionContext.getActionContext("task").toString(), Task.class);
task.setTaskId(task.getTaskId());
task.setTxStatus(TransactionalStatusEnum.STATUS_COMMIT.getCode());
taskMapper.updateById(task);
//增加一条confirm日志,用于幂等、回滚处理
Integer status = TransactionalStatusEnum.STATUS_COMMIT.getCode();
TaskTxLog taskTxLog = buildOrderTxLog(xid, branchId, status);
taskTxLogMapper.insert(taskTxLog);
return true;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean rollback(BusinessActionContext businessActionContext) {
String xid = businessActionContext.getXid();
long branchId = businessActionContext.getBranchId();
log.info("创建 TaskTccActionImpl 第二阶段回滚,删除订单 - xid:{}", xid);
//第一阶段没有完成的情况下,不必执行回滚
//因为第一阶段有本地事务,事务失败时已经进行了回滚。
//这就是空回滚
if (taskTxLogMapper.isExistTaskTxLog(xid, TransactionalStatusEnum.STATUS_TRY.getCode()) <= 0) {
log.info("TaskTccActionImpl try 没有成功,不必执行回滚,xid:{}", xid);
return true;
}
//幂等性控制:如果重复执行回滚则直接返回
if (taskTxLogMapper.isExistTaskTxLog(xid, TransactionalStatusEnum.STATUS_ROLLBACK.getCode()) > 0) {
log.info("TaskTccActionImpl rollback 不需重复执行回滚,xid:{}", xid);
return true;
}
Task task = JSON.parseObject(businessActionContext.getActionContext("task").toString(), Task.class);
taskMapper.deleteById(task.getTaskId());
//回滚结束时,记录回滚标记
Integer status = TransactionalStatusEnum.STATUS_ROLLBACK.getCode();
TaskTxLog taskTxLog = buildOrderTxLog(xid, branchId, status);
taskTxLogMapper.insert(taskTxLog);
return true;
}
/**
* 构造日志实体
*
* @param xid 全局事务id
* @param branchId 分支事务id
* @param status 状态
* @return
*/
private TaskTxLog buildOrderTxLog(String xid, long branchId, Integer status) {
TaskTxLog taskTxLog = new TaskTxLog();
taskTxLog.setBranchId(branchId);
taskTxLog.setCreateTime(new Date());
taskTxLog.setUpdateTime(new Date());
taskTxLog.setStatus(status);
taskTxLog.setXid(xid);
return taskTxLog;
}
}
修改用户资金接口
@Override
public JsonResult updateAccountTCC(Long userId, String memberAccountConstant, BigDecimal money) {
memberAccountTccAction.prepareCreateOrder(null, memberAccountConstant, userId, money);
return JsonResult.builder().build();
}
修改用户资金TCC的两个阶段接口
/**
* @author zjj
*/
@LocalTCC
public interface MemberAccountTccAction {
/**
* 第一阶段的方法,通过注解指定第二阶段的两个方法名
*
* @param businessActionContext 上下文对象,用来在两个阶段之间传递数据
* @return
*/
@TwoPhaseBusinessAction(name = "orderTccAction", commitMethod = "commit", rollbackMethod = "rollback")
boolean prepareCreateOrder(BusinessActionContext businessActionContext,
@BusinessActionContextParameter(paramName = "memberAccountConstant")String memberAccountConstant,
@BusinessActionContextParameter(paramName = "userId") Long userId,
@BusinessActionContextParameter(paramName = "money") BigDecimal money);
/**
* 第二阶段 - 提交
*
* @param businessActionContext
* @return
*/
boolean commit(BusinessActionContext businessActionContext);
/**
* 第二阶段 - 回滚
*
* @param businessActionContext
* @return
*/
boolean rollback(BusinessActionContext businessActionContext);
}
修改用户资金TCC的两个阶段接口实现
@Component
@Slf4j
public class MemberAccountTccActionImpl implements MemberAccountTccAction {
@Resource
private IMemberAccountService memberAccountService;
@Resource
private MemberAccountTxLogMapper memberAccountTxLogMapper;
@Transactional(rollbackFor = Exception.class)
@Override
public boolean prepareCreateOrder(BusinessActionContext businessActionContext, String memberAccountConstant, Long userId, BigDecimal money) {
String xid = businessActionContext.getXid();
long branchId = businessActionContext.getBranchId();
log.info("创建 任务 第一阶段,预留资源 - xid:{}", xid);
//幂等判断 判断order_tx_log表中是否有try日志记录,如果有则不再执行
if (memberAccountTxLogMapper.isExistMemberAccountTxLog(xid, TransactionalStatusEnum.STATUS_TRY.getCode()) > 0) {
log.info("MemberAccountTccAction try 已经执行,无需重复执行,xid:{}", xid);
return true;
}
//try悬挂处理,如果cancel、confirm有一个已经执行了,try不再执行
if (memberAccountTxLogMapper.isExistMemberAccountTxLog(xid, TransactionalStatusEnum.STATUS_COMMIT.getCode()) > 0 || memberAccountTxLogMapper.isExistMemberAccountTxLog(xid, TransactionalStatusEnum.STATUS_COMMIT.getCode()) > 0) {
log.info("MemberAccountTccAction try悬挂处理 cancel或confirm已经执行,不允许执行try,xid:{}", xid);
return true;
}
//一阶段处理
memberAccountService.handleTxFrozenAmount(userId, money, memberAccountConstant, TXEnum.PRE);
//事务成功,保存一个标识,供第二阶段进行判断
Integer status = TransactionalStatusEnum.STATUS_TRY.getCode();
MemberAccountTxLog memberAccountTxLog = buildMemberAccountTxLog(xid, branchId, status);
memberAccountTxLogMapper.insert(memberAccountTxLog);
return true;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean commit(BusinessActionContext businessActionContext) {
String xid = businessActionContext.getXid();
long branchId = businessActionContext.getBranchId();
log.info("创建 MemberAccountTccAction 第二阶段提交,修改订单状态1 - xid:{}", xid);
// 防止空提交,如果try阶段没有成功,就是一个空提交,直接返回
if (memberAccountTxLogMapper.isExistMemberAccountTxLog(xid, TransactionalStatusEnum.STATUS_TRY.getCode()) <= 0) {
log.info("MemberAccountTccAction confirm 空提交...xid:{}", xid);
return true;
}
// 防止幂等性,如果commit阶段重复执行则直接返回
if (memberAccountTxLogMapper.isExistMemberAccountTxLog(xid, TransactionalStatusEnum.STATUS_COMMIT.getCode()) > 0) {
log.info("MemberAccountTccAction confirm 已经执行,无需重复执行...xid:{}", xid);
return true;
}
//二阶段资金处理 提交操作
long userId = Long.parseLong(businessActionContext.getActionContext("userId").toString());
String money = businessActionContext.getActionContext("money").toString();
String memberAccountConstant = businessActionContext.getActionContext("memberAccountConstant").toString();
memberAccountService.handleTxFrozenAmount(userId, new BigDecimal(money), memberAccountConstant, TXEnum.COMMIT);
//增加一条confirm日志,用于幂等、回滚处理
Integer status = TransactionalStatusEnum.STATUS_COMMIT.getCode();
MemberAccountTxLog memberAccountTxLog = buildMemberAccountTxLog(xid, branchId, status);
memberAccountTxLogMapper.insert(memberAccountTxLog);
return true;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean rollback(BusinessActionContext businessActionContext) {
String xid = businessActionContext.getXid();
long branchId = businessActionContext.getBranchId();
log.info("创建 MemberAccountTccAction 第二阶段回滚,删除订单 - xid:{}", xid);
//第一阶段没有完成的情况下,不必执行回滚
//因为第一阶段有本地事务,事务失败时已经进行了回滚。
//这就是空回滚
if (memberAccountTxLogMapper.isExistMemberAccountTxLog(xid, TransactionalStatusEnum.STATUS_TRY.getCode()) <= 0) {
log.info("MemberAccountTccAction try 没有成功,不必执行回滚,xid:{}", xid);
return true;
}
//幂等性控制:如果重复执行回滚则直接返回
if (memberAccountTxLogMapper.isExistMemberAccountTxLog(xid, TransactionalStatusEnum.STATUS_ROLLBACK.getCode()) > 0) {
log.info("MemberAccountTccAction rollback 不需重复执行回滚,xid:{}", xid);
return true;
}
//二阶段回滚处理
long userId = Long.parseLong(businessActionContext.getActionContext("userId").toString());
String money = businessActionContext.getActionContext("money").toString();
String memberAccountConstant = businessActionContext.getActionContext("memberAccountConstant").toString();
memberAccountService.handleTxFrozenAmount(userId, new BigDecimal(money), memberAccountConstant, TXEnum.ROLLBACK);
//回滚结束时,记录回滚标记
Integer status = TransactionalStatusEnum.STATUS_ROLLBACK.getCode();
MemberAccountTxLog memberAccountTxLog = buildMemberAccountTxLog(xid, branchId, status);
memberAccountTxLogMapper.insert(memberAccountTxLog);
return true;
}
/**
* 构造日志实体
*
* @param xid 全局事务id
* @param branchId 分支事务id
* @param status 状态
* @return
*/
private MemberAccountTxLog buildMemberAccountTxLog(String xid, long branchId, Integer status) {
MemberAccountTxLog memberAccountTxLog = new MemberAccountTxLog();
memberAccountTxLog.setBranchId(branchId);
memberAccountTxLog.setCreateTime(new Date());
memberAccountTxLog.setUpdateTime(new Date());
memberAccountTxLog.setStatus(status);
memberAccountTxLog.setXid(xid);
return memberAccountTxLog;
}
}