
在开发高并发系统时,异步编程是提升性能的关键技术之一。今天聊聊我在实际项目中踩过的一些坑。
一、为什么需要异步?
假设这样一个场景:用户下单后需要发送短信通知、更新库存、记录日志。如果同步执行,用户需要等待所有操作完成才能收到响应,体验极差。
异步处理后,核心流程(创建订单)立即返回,其他操作放入消息队列或线程池异步执行,响应时间从秒级降到毫秒级。
二、几种常见的异步方案
1. 线程池 + Future
最传统的方式,简单直接。但要注意线程池配置,核心线程数、最大线程数、队列容量都要根据业务压测来确定。
ExecutorService executor = new ThreadPoolExecutor(
10, 50, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadPoolExecutor.CallerRunsPolicy()
);
2. CompletableFuture
Java 8 引入,支持链式调用和异常处理,比 Future 好用太多。组合多个异步任务时尤其方便。
CompletableFuture.supplyAsync(() -> userService.getUser(id))
.thenAccept(user -> cacheService.cache(user))
.exceptionally(ex -> {
log.error("异步任务失败", ex);
return null;
});
3. 消息队列
RabbitMQ、RocketMQ、Kafka 都是好选择。核心优势:解耦、削峰、可靠。适合跨服务异步调用。
4. 响应式编程
WebFlux、Reactor 这一套学习曲线陡峭,但在高并发 I/O 密集场景下性能卓越。
三、踩过的坑
- 线程池滥用:每次请求都创建新线程池,系统资源很快耗尽。正确做法是全局共享,合理配置。
- 异常被吞:异步任务抛异常,主线程感知不到。一定要加异常处理和日志。
- 上下文丢失:RequestContextHolder 在子线程拿不到。需要手动传递或用 TransmittableThreadLocal。
- 超时没设置:异步任务卡住,线程池堆积,最终 OOM。记得给每个异步操作加超时。
四、最佳实践
- 线程池命名,方便监控和排查
- 统一的异步任务封装,包含日志、监控、告警
- 合理的拒绝策略,CallerRuns 可降级
- 优雅停机,等待任务完成再关闭
- 监控线程池状态,设置告警阈值
结语
异步编程不难,难的是用好。核心原则:该同步时别异步,该异步时别硬撑。性能优化要建立在正确理解业务的基础上,盲目异步反而增加复杂度。
希望这些经验对你有帮助!欢迎留言交流。
觉得有用就点个赞吧~