实现秒杀一人一单并发压测 _ 黑马点评业务实战

SpringBoot 实现秒杀一人一单并发压测 | 黑马点评业务实战

一、前言

在电商秒杀、优惠券抢购业务中,并发安全 和 接口承载能力 是核心技术痛点。黑马点评项目中的优惠券秒杀模块,核心需求是 一人一单(单个用户仅能抢购一张优惠券),在高并发场景下极易出现超卖、重复下单、并发冲突等问题。

为了验证秒杀接口的并发安全性、校验一人一单拦截逻辑的有效性,同时测试接口 QPS 承载能力,我们需要编写专业的并发压测代码。本文将详细拆解一套基于 Junit+线程池的秒杀并发压测实战代码,适配黑马点评完整业务场景。

二、压测场景说明

1. 业务规则

  • 优惠券秒杀限制:一人一单,同一个用户只能成功抢购一次

  • 基础能力:控制优惠券库存,防止超卖

  • 并发场景:多用户、高频次重复请求秒杀接口,模拟真实高并发抢购场景

2. 压测目标

  • 验证 一人一单 拦截逻辑是否生效,用户重复抢购是否被正确拦截

  • 验证秒杀接口并发安全,是否存在超卖问题

  • 统计接口 QPS、总耗时、成功/失败请求数,评估接口性能

三、完整压测代码

基于 SpringBootTest、Junit、线程池实现并发压测,代码贴合黑马点评原生业务,无需额外改造即可直接运行:

@SpringBootTest(classes = HmDianPingApplication.class)
class SeckillPressureTest {

    @Autowired
    private IVoucherOrderService voucherOrderService;

    // 初始化固定线程池,模拟并发请求
    private final ExecutorService es = Executors.newFixedThreadPool(500);

    @Test
    void testSeckillQps() throws InterruptedException {
        // 压测核心参数:500 并发线程,每个线程请求 20 次
        int threadCount = 500;
        int perThread = 20;
        // 待秒杀的优惠券 ID,替换为业务真实有效 ID
        long voucherId = 24;

        // 线程计数器,等待所有并发线程执行完毕
        CountDownLatch latch = new CountDownLatch(threadCount);
        // 存储成功的订单 ID,去重统计有效下单数(适配一人一单)
        ConcurrentHashMap<Long, Boolean> successMap = new ConcurrentHashMap<>();
        // 统计失败请求总数
        AtomicInteger failCount = new AtomicInteger(0);

        // 记录压测开始时间
        long begin = System.currentTimeMillis();

        // 批量创建并发线程
        for (int i = 0; i < threadCount; i++) {
            final long userId = i + 1;
            es.submit(() -> {
                try {
                    // 模拟登录用户,绑定当前线程用户 ID
                    UserDTO userDTO = new UserDTO();
                    userDTO.setId(userId);
                    UserHolder.saveUser(userDTO);
                    // 单线程循环多次发起秒杀请求,模拟高频重试
                    for (int j = 0; j < perThread; j++) {
                        Result result = voucherOrderService.seckillVoucher(voucherId);
                        if (result.getData() != null) {
                            // 下单成功,存入订单 ID 自动去重
                            successMap.put((Long) result.getData(), true);
                        } else {
                            // 下单失败,计数器累加
                            failCount.incrementAndGet();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 单个线程执行完毕,计数器减一
                    UserHolder.removeUser();
                    latch.countDown();
                }
            });
        }

        // 阻塞等待所有线程执行完成
        latch.await();
        // 记录压测结束时间
        long end = System.currentTimeMillis();

        // 计算压测核心指标
        int totalRequest = threadCount * perThread;
        long costTime = end - begin;
        double qps = (double) totalRequest / (costTime / 1000.0);

        // 打印压测结果
        System.out.println("================== 秒杀压测结果 =================");
        System.out.println("总请求数:" + totalRequest);
        System.out.println("成功下单数量:" + successMap.size());
        System.out.println("接口QPS:" + String.format("%.2f", qps));
        System.out.println("总耗时:" + costTime + "ms");
    }
}

四、核心代码逻辑详解

1. 并发场景模拟

代码通过 Executors.newFixedThreadPool(500) 创建 500 个固定线程,模拟高并发抢购场景。同时设置 每个线程循环请求 20 次,模拟用户频繁重试抢购的真实场景,最大程度还原线上流量峰值。

通过 CountDownLatch 实现线程阻塞,保证主线程等待所有并发子线程执行完毕后,再统计压测数据,避免数据统计不全的问题。

2. 一人一单场景适配

压测中为每个线程分配 独立唯一的用户 ID(userId = i + 1),模拟 500 个不同用户同时抢购。同时使用 ConcurrentHashMap 存储成功的订单 ID,利用 Map 键唯一的特性 自动去重。

核心校验逻辑:单个用户无论请求多少次,最终仅能生成一笔有效订单。如果压测成功数大于用户数,说明接口存在一人多单的并发漏洞。

3. 压测指标统计

代码自动计算三大核心性能指标,无需手动统计:

  • 总请求数:并发线程数 × 单线程请求次数,本次压测总请求 10000 次

  • 有效成功数:去重后的真实下单订单数,校验一人一单和库存防超卖逻辑

  • QPS:每秒处理的请求数,衡量接口并发处理能力

  • 总耗时:整体压测执行时长,辅助评估接口响应速度

4. 线程用户隔离

通过 UserHolder.saveUser(userDTO) 模拟用户登录状态,黑马点评项目通过 ThreadLocal 存储用户信息,每个线程绑定独立用户 ID,完美模拟多用户并发场景,避免用户上下文混乱导致的压测数据失真。

五、压测结果分析与校验标准

1. 正常结果标准(接口无漏洞)

  • 有效成功下单数 ≤ 优惠券库存数:无超卖问题

  • 有效成功下单数 ≤ 并发用户数(500):一人一单拦截逻辑生效,无用户重复下单

  • 失败请求数为大量正常拦截请求,属于业务正常现象

2. 异常结果排查

  • 成功数大于库存数:秒杀接口无库存锁,存在超卖 Bug,需优化分布式锁、乐观锁逻辑

  • 成功数大于用户数:一人一单拦截失效,未做用户下单唯一校验,并发下出现重复下单

六、压测优化拓展建议

1. 现有代码优势

零依赖、轻量级,无需 JMeter、LoadRunner 等第三方压测工具,基于原生 Java 代码即可完成并发压测,适配项目单元测试流程,可集成到项目测试体系中。

2. 生产级优化方向

  • 线程池优化:替换为 ThreadPoolExecutor 自定义线程池,规避 Executors 线程池缺陷

  • 参数可配置化:将线程数、请求次数、优惠券 ID 写入配置文件,无需改代码即可调整压测参数

  • 结果日志持久化:将压测结果写入日志文件,方便后续性能对比分析

  • 超时机制:增加接口请求超时判断,统计超时失败率

七、总结

本文实现的秒杀并发压测代码,精准适配黑马点评 一人一单 核心业务场景,不仅能够完成接口 QPS 性能压测,更核心的是可以 校验并发安全逻辑,快速定位超卖、重复下单等线上高危问题。

在秒杀功能开发完成后,通过该压测用例可快速验证代码健壮性,是电商秒杀业务开发、测试、性能调优的必备实战方案。轻量级代码、简单易落地,适合初学者学习并发压测思想,也可直接用于项目实战。

(注:部分内容可能由 AI 生成)

posted @ 2026-07-08 20:24  solarlemon  阅读(48)  评论(0)    收藏  举报