Java项目搭建案例

wen java案例 4

一个高并发秒杀系统的Java项目搭建全案例(含架构演进与性能调优)

目录导读

  1. 项目背景与需求分析 – 为什么选择秒杀系统作为案例,核心难点是什么?
  2. 技术选型与架构设计 – Spring Boot + Redis + RabbitMQ + MyBatis-Plus 如何协同?
  3. 数据库与缓存双写一致性方案 – 防超卖、防重复下单的关键代码落地。
  4. 消息队列削峰填谷 – 异步下单流程的完整时序图与代码片段。
  5. 压测数据与性能调优 – JMeteter 结果对比,TPS从800到6800的优化路径。
  6. 常见问题问答(FAQ) – 面试级问题与解决方案。

项目背景与需求分析

在电商大促场景下,秒杀系统是Java后端开发中最具挑战性的业务之一,本案例模拟一个限量1000件商品的抢购活动,要求支持高并发(峰值1万QPS)绝不超卖防同一用户重复下单

Java项目搭建案例

核心痛点:

  • 数据库行锁竞争导致崩溃
  • 缓存与数据库数据不一致
  • 订单生成高峰期拖垮主库

技术选型与架构设计

我们采用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的边界职责划分——这远比单纯背面试题更有价值。

抱歉,评论功能暂时关闭!