背景:业务需要异步发送消息、日志落库、统计埋点,让 AI 写异步代码。AI 直接贴 @Async,本地跑起来正常,压测后出现任务堆积、内存暴涨、线程泄漏、事务丢失、异常吞掉,生产偶发任务不执行。
- 场景背景
订单服务:下单成功后,异步执行推送短信、保存操作日志、更新用户积分。开发让 AI 写异步,AI 直接加上 @Async,启动类开启 @EnableAsync。
本地自测:下单后短信、日志都正常。上线压测后出现一堆诡异问题:
• 大量异步任务堆积,CPU、内存持续走高;
• 部分任务直接丢了,没有报错;
• 异步方法里数据库事务失效;
• 异常被吞,找不到失败日志;
• 同类内部调用异步方法,异步完全不生效;
• 默认线程池无界限,流量突增直接 OOM。
-
现象
-
异步不生效:this 调用、方法非 public、final / static 修饰,切面失效;
-
默认线程池无管控:Spring 默认 SimpleAsyncTaskExecutor,不是真正复用线程,或者旧版本默认无界队列,流量上来疯狂创建线程;
-
异常静默丢失:异步方法抛出异常,主线程捕获不到,没有全局异常处理器;
-
事务失效:异步新开线程,事务上下文不会跨线程传递;
-
拒绝策略没配置:队列满了之后,默认策略抛异常或者丢弃任务,没有告警、降级;
-
线程上下文丢失:TraceId、登录用户、请求上下文在线程切换后消失,链路追踪断了;
-
线程池没有命名:日志里线程名都是 task-xxx,排查问题分不清属于哪个业务;
-
没有监控:队列长度、活跃线程数、拒绝任务数没有埋点,堆积了发现晚。
-
根因分析
AI 典型偷懒:只给注解,忽略底层 AOP、线程池配置、上下文传递、异常处理。
① AOP 代理限制
@Async 也是 Spring AOP 切面:
• 同类内部 this.xxx() 调用异步方法 → 不走代理,同步执行;
• 方法必须是 public;private / final / static 注解无效;
• @EnableAsync 默认代理模式,如果配置不当,代理失效。
② 默认线程池是大坑
Spring 如果不自定义 TaskExecutor,很多场景下:
• SimpleAsyncTaskExecutor:每次任务新建线程,不复用,高并发下线程爆炸;
• 如果用默认 ThreadPoolTaskExecutor,队列、核心线程、最大线程、拒绝策略没有限制,极易堆积。
③ 事务不能跨线程
事务是绑定在当前线程的。异步开启新线程,原有事务上下文无法自动传递。如果想在异步里操作 DB,必须单独开启新事务,不能指望主事务。
④ 异常没有捕获
异步方法返回 void 时,异常不会传到主线程。如果没有配置 AsyncUncaughtExceptionHandler,异常直接静默消失,很难定位。
⑤ MDC 链路追踪丢失
MDC(日志的 TraceId)是线程本地存储。新开线程后,默认不会复制上下文,日志链路断裂。
⑥ 拒绝策略不合理
默认拒绝策略 AbortPolicy 直接抛异常;如果业务是通知、日志,更适合丢弃+告警,或者排队限流,而不是直接崩溃。
- 解决方案
依赖
SpringBoot 自带,不需要额外引入包。
启动开启异步
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class OrderApplication {
public static void main(String\[\] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
自定义线程池(工程标准)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("orderAsyncExecutor")
public Executor orderAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数:可根据 CPU 核数调整,IO 密集型可以调大
executor.setCorePoolSize(8);
// 最大线程数
executor.setMaxPoolSize(20);
// 队列容量
executor.setQueueCapacity(200);
// 线程前缀,方便日志排查
executor.setThreadNamePrefix("order-async-");
// 空闲线程存活时间
executor.setKeepAliveSeconds(60);
// 拒绝策略:队列满了,调用者执行,避免直接丢任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务完成再关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}
异步服务示例
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class OrderAsyncService {
// 指定使用自定义线程池
@Async("orderAsyncExecutor")
public void sendSms(Long orderId) {
// 异步业务,独立事务
// 注意:不能复用主线程事务
System.out.println("发送短信:订单 " + orderId);
}
@Async("orderAsyncExecutor")
public void saveLog(Long orderId) {
// 日志落库
}
}
如果需要同类内部调用异步方法,同样开启 exposeProxy = true,通过代理对象调用,不能用 this。
全局异步异常处理器
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import java.lang.reflect.Method;
@Configuration
public class AsyncExceptionConfig implements AsyncConfigurer {
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandler() {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// 告警、日志、投递失败重试
System.err.printf("异步方法异常:%s,参数:%s,异常:%s%n",
method.getName(), params, ex.getMessage());
ex.printStackTrace();
}
};
}
}
传递 MDC 链路上下文
需要包装线程池,把主线程 MDC 复制到异步线程。简单思路:
实现 TaskDecorator,提交任务前拷贝 MDC,任务执行完毕清理。
import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;
import java.util.Map;
public class MdcTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> context = MDC.getCopyOfContextMap();
return () -> {
try {
if (context != null) {
MDC.setContextMap(context);
}
runnable.run();
} finally {
MDC.clear();
}
};
}
}
然后在 ThreadPoolTaskExecutor 配置:
executor.setTaskDecorator(new MdcTaskDecorator());
配套工程规范
-
业务隔离线程池:短信、日志、支付分开线程池,防止一个业务打满拖垮其他异步任务;
-
区分 IO 密集 / CPU 密集:CPU 密集核心线程一般接近 CPU 核数;IO 密集可以适当放大;
-
拒绝策略按需选择:通知类可降级丢弃+告警;核心业务不允许丢,可考虑限流;
-
异步任务幂等:重试场景下防止重复发送短信、重复扣积分;
-
监控埋点:活跃线程、队列积压、拒绝任务数、失败次数接入监控;
-
优雅关闭:服务停机时等待任务执行完成;
-
不要在异步方法依赖主线程事务;
-
长耗时任务单独处理,避免占满线程池。
-
AI 辅助开发避坑总结
• AI 经常只写 @Async + @EnableAsync,跳过线程池自定义;
• 默认线程池极易在线上压测翻车;
• @Async 和 @Transactional、@Cacheable 共享 AOP 代理坑;
• 异步 void 方法异常容易静默丢失;
• MDC、用户上下文、事务不会自动跨线程;
• 线程池必须命名、设置队列上限、拒绝策略、优雅关闭。