1.问题
博主在学习黑马最新的微服务课程的时候,遇到了一个非常疑惑的问题,Seata分布式事务系统失效,配置无误,@GlobalTransaction注解也已经加入到需要开启事务的方法上,但是分支事务抛出异常并不能使得全局事务进行回滚。
@Override
@GlobalTransactional
public void tryPayOrderByBalance(PayOrderFormDTO payOrderFormDTO) {
// 1.查询支付单
PayOrder po = getById(payOrderFormDTO.getId());
// 2.判断状态
if(!PayStatus.WAIT_BUYER_PAY.equalsValue(po.getStatus())){
// 订单不是未支付,状态异常
throw new BizIllegalException("交易已支付或关闭!");
}
// 3.尝试扣减余额
userClient.deductMoney(payOrderFormDTO.getPw(), po.getAmount());
// 4.修改支付单状态
boolean success = markPayOrderSuccess(payOrderFormDTO.getId(), LocalDateTime.now());
if (!success) {
throw new BizIllegalException("交易已支付或关闭!");
}
// 5.修改订单状态
orderClient.markOrderPaied(po.getBizOrderNo());
}
2.排查
博主向ChatGPT进行询问得到了一些答案,但是都没有直击要点,通过一系列的排查,最终发现是分支事务抛出的异常,被以前配置的 FallBack 降级策略给拦截到了,导致 Seata 的 RM 无法监测到,从而无法进行回滚。
1.使用RootContext获取和Seata连接的XID
我们使用 RootContext.getXID() 来判断服务是否能和 Seata 进行正常的连接。
这里使用这个方法,发现每个服务都能获取到 XID。证明连接是没有问题的。
2.undo_log 表有脏数据
当Seata没有正常结束时,每个服务对应数据库中的undo_log表和seata持久化数据库的brach_table、global_table、lock_table、undo_log表都有可能有脏数据没有正确删除,从而导致服务一直回滚,却不成功
解决办法:清除undo_log表以及seata持久化数据库的brach_table、global_table、lock_table、undo_log表中的脏数据
3.Seata 事务处理被 FallBack 回滚函数拦截
这种情况下属于seata服务发现不了下游服务抛出的异常,导致事务不会触发回滚
在这种情况下,我们需要手动控制回滚
手动回滚的方式是使用 GlobalTransactionContext.getCurrent().rollBack(); 方法进行回滚。
boolean success = markPayOrderSuccess(payOrderFormDTO.getId(), LocalDateTime.now());
if (!success) {
// 手动进行全局事务回滚
GlobalTransactionContext.getCurrent().rollback();
throw new BizIllegalException("交易已支付或关闭!");
}
3.总结
这就是关于 Seata 分布式事务失效的解决方法,对于这类问题,需要我们耐心,多方位的思考排查问题,善于使用搜索引擎,获取前人的踩坑点。