Spring Boot 中基于线程池的订单创建并行化实践

一、背景

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 线程池配置代码

java 复制代码
@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)

java 复制代码
@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 订单创建核心逻辑

java 复制代码
@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

java 复制代码
@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 原因分析

  1. 每个请求会向线程池提交 3 个并行任务

  2. 200 个并发请求 ≈ 600 个线程池任务

  3. 线程池最大并发执行数为 16

  4. 多余任务进入阻塞队列

  5. 队列满后触发CallerRunsPolicy

  6. 部分任务由 HTTP 工作线程执行,导致请求处理时间变长


6.4 工程结论

  • RT 上升并不代表线程池失效

  • 这是线程池在高并发下的自我保护行为

  • 相比无限创建线程导致系统崩溃,RT 变慢是一种可接受的退化方式

线程池优化的是系统吞吐与稳定性,而不是在无限并发下保持恒定响应时间。

七、问题思考

7.1 为什么不用 @Async?

  • 难以进行复杂任务编排

  • 不利于精细化控制线程池

7.2 为什么不用 parallelStream?

  • 使用公共 ForkJoinPool

  • 线程资源不可控

7.3 线程池并非万能

  • 仍需配合限流、熔断等机制

  • 核心链路与非核心链路应区别对待

相关推荐
廋到被风吹走2 小时前
【Spring】ThreadLocal详解 线程隔离的魔法与陷阱
java·spring·wpf
星辰离彬2 小时前
2025 IDEA运行报错:运行 xxxxApplication 时出错。命令行过长。 通过 JAR 清单或通过类路径文件缩短命令行,然后重新运行。
java·后端·intellij-idea·jar
古城小栈2 小时前
Java 响应式编程:Spring WebFlux+Reactor 实战
java·开发语言·spring
Sunniering2 小时前
使用classfinal-maven-plugin加密 Spring Boot JAR 包配置流程
spring boot·maven·jar·classfinal插件
攻心的子乐2 小时前
sentinel使用指南 限流/熔断 微服务 ruoyi-cloud使用了
java·开发语言
zsyy@2 小时前
Maven本地仓库有jar还会向远程仓库下载依赖的问题
java·服务器·maven
小万是个程序员2 小时前
IDEA 配置热部署(使用idea自带功能,无需插件)
java·ide·intellij-idea
Qiuner2 小时前
Spring Boot AOP (四)与事务、异常处理交互
spring boot·后端·交互
柒.梧.2 小时前
Java核心面试题终极总结:从基础到进阶,覆盖高频考
java·开发语言·面试