SpringBoot基于线程池的订单创建并行化实践过程
作者:kkkkkkkkl24
文章介绍了电商系统订单创建接口的并行处理方案,通过分析业务流程、选择合适的线程池和技术,解决了高并发场景下的性能问题
一、背景
1.1 业务背景
以电商系统「订单创建」接口为例
一个用户下单请求,往往需要完成多个业务步骤:
- 校验库存
- 校验用户信息
- 计算订单价格
- 锁库存
- 创建订单
1.2 问题描述
传统实现方式:串行执行
在高并发场景下:
- 接口 RT 高
- 线程被长时间占用
- 系统吞吐下降
1.3 技术挑战
- 哪些任务可以并行?
- 如何安全、高效地并行?
- 如何在 Spring Boot 中正确使用线程池?
二、业务场景分析与并行拆分
2.1 订单创建流程拆解
| 步骤 | 是否存在依赖 | 是否可并行 |
|---|---|---|
| 校验库存 | 无 | ✅ |
| 校验用户 | 无 | ✅ |
| 计算价格 | 无 | ✅ |
| 锁库存 | 依赖库存校验 | ❌ |
| 创建订单 | 依赖前置结果 | ❌ |
2.2 并行化设计思路
- 无依赖的校验类任务 → 并行
- 存在业务依赖的核心流程 → 串行
- 线程池只用于短生命周期任务
三、技术选型与整体设计
3.1 为什么不直接 new Thread?
- 线程创建成本高
- 无法控制并发量
- 高并发下容易导致 JVM 失控
3.2 为什么选择线程池 + CompletableFuture?
- 线程复用,降低系统开销
- 明确的并发上限
- 支持任务编排(allOf / thenCombine)
3.3 在 Spring Boot 中的正确姿势
- 线程池必须交由 Spring 管理
- 使用
ThreadPoolTaskExecutor - 为业务定制专用线程池,避免互相影响
四、线程池设计与配置(Core)
4.1 线程池配置代码
@Configuration
public class OrderThreadPoolConfig {
@Bean("orderExecutor")
public ThreadPoolTaskExecutor orderExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8);
executor.setMaxPoolSize(16);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("order-create-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
4.2 关键参数说明
corePoolSize:常态并发能力maxPoolSize:应对突发流量queueCapacity:缓冲任务,防止雪崩RejectedExecutionHandler:选择CallerRunsPolicy实现自然限流
4.3 线程池定位
- Web 请求内使用
- IO + 轻计算混合型线程池
- 非长任务、非阻塞型任务
五、核心业务实现
5.1 校验 Service(模拟 RPC / DB)
@Service
public class OrderCheckService {
public boolean checkStock(Long skuId) {
sleep(100);
return true;
}
public boolean checkUser(Long userId) {
sleep(80);
return true;
}
public int calcPrice(Long skuId) {
sleep(120);
return 99;
}
private void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
5.2 订单创建核心逻辑
@Service
public class OrderService {
@Resource(name = "orderExecutor")
private Executor orderExecutor;
@Autowired
private OrderCheckService orderCheckService;
public String createOrder(Long userId, Long skuId) throws Exception {
long start = System.currentTimeMillis();
CompletableFuture<Boolean> stockFuture =
CompletableFuture.supplyAsync(
() -> orderCheckService.checkStock(skuId),
orderExecutor);
CompletableFuture<Boolean> userFuture =
CompletableFuture.supplyAsync(
() -> orderCheckService.checkUser(userId),
orderExecutor);
CompletableFuture<Integer> priceFuture =
CompletableFuture.supplyAsync(
() -> orderCheckService.calcPrice(skuId),
orderExecutor);
CompletableFuture.allOf(
stockFuture, userFuture, priceFuture).join();
if (!stockFuture.get()) {
throw new RuntimeException("库存不足");
}
int price = priceFuture.get();
lockStock(skuId);
saveOrder(userId, skuId, price);
return "success, cost=" +
(System.currentTimeMillis() - start) + "ms";
}
private void lockStock(Long skuId) {
sleep(50);
}
private void saveOrder(Long userId, Long skuId, int price) {
sleep(80);
}
private void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
5.3 Controller
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/create")
public String createOrder(
@RequestParam Long userId,
@RequestParam Long skuId) throws Exception {
return orderService.createOrder(userId, skuId);
}
}
六、JMeter 压测与结果分析
6.1 压测配置说明
- 并发线程数:200
- Ramp-Up:1 秒
6.2 压测现象
系统启动初期:
- 接口 RT ≈ 288ms
持续压测后:
- RT 逐渐上升
- 峰值约 1888ms
6.3 原因分析
- 每个请求会向线程池提交 3 个并行任务
- 200 个并发请求 ≈ 600 个线程池任务
- 线程池最大并发执行数为 16
- 多余任务进入阻塞队列
- 队列满后触发
CallerRunsPolicy - 部分任务由 HTTP 工作线程执行,导致请求处理时间变长
6.4 工程结论
- RT 上升并不代表线程池失效
- 这是线程池在高并发下的自我保护行为
- 相比无限创建线程导致系统崩溃,RT 变慢是一种可接受的退化方式
线程池优化的是系统吞吐与稳定性,而不是在无限并发下保持恒定响应时间。
七、问题思考
7.1 为什么不用 @Async?
- 难以进行复杂任务编排
- 不利于精细化控制线程池
7.2 为什么不用 parallelStream?
- 使用公共 ForkJoinPool
- 线程资源不可控
7.3 线程池并非万能
- 仍需配合限流、熔断等机制
- 核心链路与非核心链路应区别对待
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
