知识背景
对于业务流程中的订单流水跟踪,或一些消息发送通知,这些流程都是一些不必要的耗时和不需要立即执行结果的逻辑,这些场景下的业务逻辑可以使用@Async进行异步的处理和操作但是使用的过程当然仍然有很多值得注意的地方,比如异步处理的补充和通知机制,如果处理不好的话,可能会造成隐患。
spring 的 @Async默认使用的是Spring自带的 SimpleAsyncTaskExecutor 线程池,推荐使用自定义线程池执行异步的操作。自定义@Async线程池有如下的模式
- 重新实现AsyncConfigurer 优点是可以定义异常处理机制
- 继承AsyncConfigurerSupport
- 配置由自定义的TaskExecutor替代内置的任务执行器
最常用也比较好维护的一种方法,可以为不同的异步任务划分不同的线程池。这种方式可以更灵活地管理和优化线程资源,确保不同类型的任务不会相互干扰。
使用自定义线程池执行异步任务
由于spring 的 @Async注解基于Spring AOP 实现,基本原理就是使用AOP来拦截标记了@Async注解的方法,并通过定义的线程池在单独的线程中异步执行。因此在使用中需要注意的就是注意AOP的失效场景:
- 不是public方法
- 返回值不为
void或者Future - 使用
static静态修饰符 - 不能在同类中进行调用
在Async方法上标注@Transactional是没用的,这是因为Spring 的事务管理是基于线程绑定的,事务上下文不会传播到新的线程中,因为Async最终会交由新的线程实现。因此不能直接给异步方法启用Spring事务管理,但是异步方法中调用同步的事物方法是可行的。
场景实践
异步流水记录 @Async + @TransactionalEventListener 方案
业务流程简介
创建订单的时候,涉及三个步骤 上传费用信息、三方系统下单、和订单入库
前两个步骤都是gRPC走消息队列,全部成功后,执行订单入库操作;
通过@TransactionalEventListener监听并执行相应的事物机制,Spring的发布订阅模型是同步的。@TransactionEventListener也是同步的
所以需要同时处理事物执行后对应的事件方法配合异步线程池进行异步处理。通过监听事务事件可以实现比如成功后推送消息,或者失败后的补充或提示的机制。
前置条件
- 自定义异步线程池配置

- 订单创建监组件类
@Getter public class CreateOrderPassEvent extends ApplicationEvent { private final Integer id; private final String requestId; public CreateOrderPassEvent(Object source,Integer id,String requestId) { super(source); this.id = id; this.requestId = requestId; } } @Getter public class CreateOrderFailEvent extends ApplicationEvent { private final String requestId; public CreateOrderFailEvent(Object source,String requestId) { super(source); this.requestId = requestId; } } - 标识创建订单时需要发布的事件
@Slf4j
@Component
public class CreateOrderListener {
@Resource
private IWorkShopOrderLogService workShopOrderLogService;
/**
* 订单创建成功后执行的操作
* @param event 提交事件
*/
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Async("asyncServiceExecutor")
public void onCreateOrderPass(CreateOrderPassEvent event){
// 模拟异步延时通知
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("===============create order pass: {}", event);
WorkShopOrderLog log = new WorkShopOrderLog()
.setRequestId(event.getRequestId())
.setOrderId(event.getId())
.setOldStatus(WorkShopOrderStatusEnum.WAIT_PAY.getCode())
.setNewStatus(WorkShopOrderStatusEnum.WAIT_PAY.getCode());
workShopOrderLogService.save(log);
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
@Async("asyncServiceExecutor")
public void onCreateOrderFail(CreateOrderFailEvent event){
log.info("===============create order fail: {}", event);
WorkShopOrderLog log = new WorkShopOrderLog()
.setRequestId(event.getRequestId())
.setOldStatus(WorkShopOrderStatusEnum.CREATE_FAIL.getCode())
.setNewStatus(WorkShopOrderStatusEnum.CREATE_FAIL.getCode());
workShopOrderLogService.save(log);
}
}
业务逻辑添加
service层开启事物,同时执行具体逻辑前发布事件,个人经验,执行前先发布失败的事件,成功后再发布成功的事件,当然最好使用API的方式避免使用注解可能导致的监听器未被执行导致业务逻辑失效的问题。
@TransactionalEventListener同@EventListener一样是存在一个加载时机,对于发布在事务中,@EventListener监听器方法会在事务提交之前立即执行执行,@TransactionalEventListener事件监听器方法会在事务提交之后根据设定的事物状态执行。

验证和分析
下单后,如果抛出异常,那么会执行监听的逻辑记录异常流水日志,同时控制台打印日志,可以注意到使用的是异步线程处理的,这不会影响到主流程的业务。

执行成功则是调用了成功的异步方法处理,失败的事件不会执行。

总结和感想
将推送消息,记录日志这些业务流程中的次要行为使用异步处理是很常见的,但是拥有不同的各种方案可以执行。
并不存在一个万能的方案,都需要根据实际的业务情况去考虑。异步任务最总要的就是理清楚哪些是必须等异步做完,哪些不用等异步做完的,同时当订单状态涉及三方系统,不能通过分布式事务确保系统和系统之间订单状态的一致性时,就需要考虑事务补偿机制。
感谢你的耐心读到了这里,希望我的想法和记录可以帮助到你,抛砖引玉,客观感想。