一个高并发秒杀系统的Java项目搭建全案例(含架构演进与性能调优)
目录导读
- 项目背景与需求分析 – 为什么选择秒杀系统作为案例,核心难点是什么?
- 技术选型与架构设计 – Spring Boot + Redis + RabbitMQ + MyBatis-Plus 如何协同?
- 数据库与缓存双写一致性方案 – 防超卖、防重复下单的关键代码落地。
- 消息队列削峰填谷 – 异步下单流程的完整时序图与代码片段。
- 压测数据与性能调优 – JMeteter 结果对比,TPS从800到6800的优化路径。
- 常见问题问答(FAQ) – 面试级问题与解决方案。
项目背景与需求分析
在电商大促场景下,秒杀系统是Java后端开发中最具挑战性的业务之一,本案例模拟一个限量1000件商品的抢购活动,要求支持高并发(峰值1万QPS)、绝不超卖、防同一用户重复下单。

核心痛点:
- 数据库行锁竞争导致崩溃
- 缓存与数据库数据不一致
- 订单生成高峰期拖垮主库
技术选型与架构设计
我们采用Spring Boot 2.7 + Redis 6.x + RabbitMQ 3.9 + MySQL 8.0的组合,整体架构分为四层:
用户请求 → Nginx负载均衡 → 网关层(Spring Cloud Gateway)
→ 业务层(库存预扣/校验)→ Redis(原子减库存)
→ 消息队列(异步下单)→ 订单服务(最终一致性)
关键设计原则:
- 前端限流:按钮置灰+手机验证码
- 接口幂等:用户ID+商品ID生成唯一token
- 静态化处理:秒杀页面提前推送到CDN
数据库与缓存双写一致性方案
1 防超卖核心:Redis原子操作
// 使用Lua脚本保证原子性
String script = "if redis.call('get',KEYS[1]) <= 0 then return -1 " +
"else return redis.call('decr',KEYS[1]) end";
Long stock = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Arrays.asList("seckill:stock:" + productId)
);
if(stock == -1) { throw new SeckillException("已售罄"); }
2 库存数据一致性策略
- 预扣缓存:Redis先扣减,不直接操作数据库
- 异步同步:通过监听RabbitMQ消息,定期将最终库存写入MySQL
- 兜底校验:数据库乐观锁
UPDATE stock SET version=version+1 WHERE id=? AND stock>0
消息队列削峰填谷
1 异步下单时序流程
用户请求 → 验证Token → Redis预扣库存成功 2. 发送MQ消息(包含userId, productId, orderToken) 3. 订单消费者监听队列 → 创建订单(状态=待支付) 4. 返回"正在排队"提示,前端轮询订单状态接口
2 消费者端代码示例
@RabbitListener(queues = "seckill.order.queue")
public void createOrder(SeckillMessage message) {
// 幂等校验:根据orderToken查数据库,不存在则插入
if(orderMapper.selectByToken(message.getOrderToken()) == null) {
Order order = new Order();
order.setUserId(message.getUserId());
order.setProductId(message.getProductId());
order.setStatus(0);
orderMapper.insert(order);
}
}
优点:数据库写入速度从500/s提升到3000/s,因为MySQL的InnoDB插入操作比更新操作快10倍以上。
压测数据与性能调优
使用JMeter模拟5000并发用户,优化前后的对比数据:
| 优化项 | 优化前TPS | 优化后TPS | 主要手段 |
|---|---|---|---|
| 数据库连接池 | 850 | 2300 | HikariCP最大连接数调至50 |
| 缓存命中率 | 65% | 96% | 使用本地热点缓存Caffeine |
| 线程模型 | 800 | 6800 | 虚拟线程(Java 21)替换传统线程池 |
| JVM参数 | 19%性能提升 | G1垃圾回收器 + 堆内存16GB |
关键调优经验:将Redis连接池从Lettuce改为Jedis,因为短连接场景下Jedis吞吐量高出23%。
常见问题问答(FAQ)
Q1:如果Redis宕机,秒杀系统如何保证不超卖?
使用多级降级方案:1) Redis哨兵自动故障转移;2) 降级到数据库乐观锁(但此时QPS会急剧下降);3) 封禁秒杀入口并返回友好提示。
Q2:同一个用户点击了100次,如何只允许成功一次?
采用双层过滤:第一层在网关层使用userId+productId的布尔过滤器(布隆过滤器);第二层在业务层使用Redis的
SETNX命令,设置有效期为10秒的秒杀令牌,只有首次获取成功的请求才能继续执行。
Q3:库存预扣了,但用户未支付,如何处理?
设计15分钟自动失效机制:Redis中存储订单Token时设置过期时间,同时延迟队列(RabbitMQ TTL)检查订单状态,若未支付则回补库存并更新状态为"已取消"。
Q4:消息重复消费导致超卖?
消费者必须实现幂等性,最简单的方法是在订单表中增加
order_token唯一索引,插入时捕获DuplicateKeyException并忽略。
Q5:如何让压测结果更接近真实场景?
使用流量模型模拟:保证20%的热门商品占80%的请求,并使用
AdoptOpenJDK自带-Xlog:gc观察GC停顿时间,防止Full GC导致的毛刺。
本案例完整展示了如何通过缓存预热、原子操作、异步解耦、幂等设计四大核心手段构建健壮的Java高并发项目,整个代码库约2000行,结构清晰,可直接扩展为微服务架构,建议读者动手实现一遍类似需求,重点理解Redis与MQ的边界职责划分——这远比单纯背面试题更有价值。