前言
在一次生产问题排查中,发现对应的业务同步的异步任务执行失败了,报错原因是同步的主表数据不存在,重试异步任务后,任务却执行成功了,好奇怪的现象,说明当异步任务第一次执行时,数据库还没有存在对应的数据?
原因分析
业务代码简单如下
@Transactional
public void addOrder(OrderSaveDTO dto) {
...
this.saveOrder(order);
taskService.releaseTask(taskDTO);
}
代码逻辑就是先创建订单对应的业务,然后发布异步任务去同步订单所关联的业务,例如库存、物流等业务,异步任务实现是通过发MQ消息去异步执行业务,在异步任务执行中,还需要查询订单信息去同步数据,根据报错原因说明任务执行时,订单数据还没有入库,问题点就是这里了,#saveOrder方法执行了,但是对应的数据库事务还没有提交成功,就发布异步任务去同步数据,导致了异步任务执行数据库数据不一致的问题产生。
在业务数据的事务还没有执行完消息就已经发出去了, 导致后续的一些数据或逻辑上的问题产生。
解决方案
那逻辑要怎么完善呢?业务数据的事务还没执行完,我们就发异步任务去同步关联数据,那我们就等业务数据的事务提交后,再发异步任务去同步关联数据不就行了。
利用TransactionSynchronizationManager的registerSynchronization()方法注册TransactionSynchronization实现类
改造点很简单,我们只需要在原有的业务方法中添加如下代码, 就可以完成在事务提交后去处理后续的业务逻辑,有一个点很重要,就是一定要使用Spring的事务管理,要在业务方法上加上@Transactional,不然代码是没有效果的,你都没开启事务管理,哪有什么事务提交的后续逻辑处理。
@Transactional
public void addOrder(OrderSaveDTO dto) {
...
this.saveOrder(order);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter(){
@Override
public void afterCommit() {
taskService.releaseTask(taskDTO);
}
});
}
原理
Spring 事务的扩展 – TransactionSynchronization
事务操作的时候它的当前线程还保存了 TransactionSynchronization 对象。而这个对象伴随着 Spring 对 事务处理的各个生命周期都会有相应的扩展
public interface TransactionSynchronization extends Flushable {
/** 事务提交状态 */
int STATUS_COMMITTED = 0;
/** 事务回滚状态 */
int STATUS_ROLLED_BACK = 1;
/**系统异常状态 */
int STATUS_UNKNOWN = 2;
void suspend();
void resume();
void flush();
// 事务提交之前
void beforeCommit(boolean readOnly);
// 事务成功或者事务回滚之前
void beforeCompletion();
// 事务成功提交之后
void afterCommit();
// 操作完成之后(包含事务成功或者事务回滚)
void afterCompletion(int status);
}
我们所使用的TransactionSynchronization#afterCommit方法,在Spring中对于处理事务完成后执行特定逻辑的一个回调方法。具体来说,当事务成功提交后,Spring 会调用已注册的TransactionSynchronization 的 afterCommit 方法,以便执行一些事务后的清理工作或者触发其它相关的操作。