第47篇:Java综合实战:电商秒杀系统(高并发场景)
专栏系列Java企业级实战进阶适用场景电商秒杀、限时抢购、高并发限流、库存防超卖、分布式防重复提交技术栈SpringBoot2.7 MySQL8.0 Redis Lua RabbitMQ Redisson JMeter一、前言秒杀是电商项目中最典型的高并发场景短时间内会涌入数十万甚至上百万请求远超系统常规承载能力。常规业务接口QPS可能仅几百而秒杀接口瞬时QPS可达上万、十万级别。本文基于企业生产标准从零搭建一套高可用、防超卖、防重复提交、可限流熔断的电商秒杀系统完整覆盖需求分析、架构设计、核心编码、性能优化、压测排查全流程所有源码可直接用于二次开发与面试实战。二、秒杀系统核心业务需求企业级核心秒杀场景看似简单实则存在大量并发坑核心需求可归纳为三点也是面试高频考点1. 高并发承载能力秒杀活动瞬间流量洪峰需要系统支撑高QPS避免接口雪崩、服务宕机、请求超时保证核心秒杀流程可用非核心流程降级。2. 严格防超卖核心底线库存为有限固定值多线程并发扣减极易出现超卖问题导致商家亏损、订单异常必须通过锁机制、脚本原子性、数据库约束彻底杜绝。3. 全局防重复提交用户刷新页面、重复点击、脚本刷接口会产生大量无效请求需要实现一人一单同一用户针对同一秒杀商品仅可下单一次。附加非功能性需求接口限流拦截恶意流量、过量请求保护后端服务熔断降级流量峰值时降级非核心逻辑保证秒杀核心流程异步削峰通过消息队列化解瞬时流量洪峰数据一致性库存、订单数据最终一致无脏数据三、整体系统架构设计分层高并发架构采用前端拦截缓存兜底异步削峰数据库最终落地的经典秒杀架构层层过滤流量最大限度减少数据库压力。1. 前端层架构按钮防重复点击前端置灰、禁用拦截用户手动重复请求秒杀时间拦截未开始/已结束直接拦截不请求后端动态接口令牌秒杀开始前生成临时令牌规避接口暴露被刷CDN静态加速商品静态页面、图片走CDN减轻服务器压力2. 后端服务层架构接口限流Guava限流 Redis分布式限流拦截超额请求缓存预热秒杀商品库存、信息提前缓存至RedisLua脚本原子扣库存解决并发扣减超卖问题Redisson分布式锁保证一人一单杜绝重复下单消息队列异步下单成功秒杀请求入队异步生成订单熔断降级峰值时关闭日志统计、积分发放等非核心功能3. 缓存层Redis核心秒杀核心热点数据全部缓存99%请求拦截在Redis层不穿透数据库秒杀商品基础信息缓存剩余库存缓存Lua原子操作用户秒杀下单记录缓存防重复秒杀临时令牌缓存4. 消息队列层RabbitMQ实现流量削峰、异步解耦前端秒杀成功仅操作缓存、发送消息不同步创建订单由消费者异步落地订单、扣减数据库库存。5. 数据库层MySQL仅做最终数据落地配合乐观锁/悲观锁兜底防超卖通过索引优化、读写分离、分表优化并发能力。四、核心数据库表设计核心两张表秒杀商品表、秒杀订单表精简实用适配高并发。1. 秒杀商品表 seckill_goodsCREATE TABLE seckill_goods ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, goods_id bigint NOT NULL COMMENT 商品ID, seckill_price decimal(10,2) NOT NULL COMMENT 秒杀价格, stock_count int NOT NULL DEFAULT 0 COMMENT 秒杀库存, start_time datetime NOT NULL COMMENT 秒杀开始时间, end_time datetime NOT NULL COMMENT 秒杀结束时间, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_goods_time (goods_id,start_time,end_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT秒杀商品表;2. 秒杀订单表 seckill_orderCREATE TABLE seckill_order ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, order_no varchar(64) NOT NULL COMMENT 订单唯一编号, user_id bigint NOT NULL COMMENT 用户ID, goods_id bigint NOT NULL COMMENT 秒杀商品ID, seckill_price decimal(10,2) NOT NULL COMMENT 秒杀价格, status tinyint NOT NULL DEFAULT 0 COMMENT 订单状态 0-待支付 1-已完成 2-已取消, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), UNIQUE KEY uk_user_goods (user_id,goods_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT秒杀订单表;核心设计亮点用户ID商品ID唯一索引数据库层面兜底一人一单彻底杜绝重复下单。五、项目核心配置文件application.yml 核心配置包含数据库、Redis、RabbitMQ、Redisson配置server: port: 8080 tomcat: threads: max: 800 # 调整tomcat最大线程适配高并发 spring: # 数据库配置 datasource: url: jdbc:mysql://localhost:3306/seckill_db?useSSLfalseserverTimezoneAsia/ShanghaiallowMultiQueriestrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # Redis配置 redis: host: localhost port: 6379 password: database: 0 timeout: 3000ms # RabbitMQ配置 rabbitmq: host: localhost port: 5672 username: guest password: guest virtual-host: / # Redisson配置 redisson: single-server-config: address: redis://localhost:6379 password: database: 0 # 自定义秒杀配置 seckill: # 令牌有效时间 token-expire: 300 # 限流QPS limit-qps: 200六、核心功能完整源码实现1. 统一返回结果类Data NoArgsConstructor AllArgsConstructor public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { return new Result(200, 操作成功, data); } public static T ResultT error(Integer code, String msg) { return new Result(code, msg, null); } }2. Redis Lua脚本原子扣库存防超卖核心seckill_stock.lua保证判断库存、扣减库存、记录用户下单三步原子执行-- 秒杀库存key local stockKey KEYS[1] -- 用户下单记录key local userKey KEYS[2] local userId ARGV[1] local goodsId ARGV[2] -- 判断用户是否已秒杀防重复 if redis.call(sismember,userKey,userId) 1 then return 0 end -- 判断库存是否充足 local stock tonumber(redis.call(get,stockKey) or 0) if stock 0 then return -1 end -- 扣减库存、记录用户 redis.call(decr,stockKey) redis.call(sadd,userKey,userId) return 13. Lua脚本配置类Configuration public class LuaConfig { Bean public DefaultRedisScriptLong seckillScript() { DefaultRedisScriptLong script new DefaultRedisScript(); script.setScriptText( -- 脚本内容与上方lua一致 local stockKey KEYS[1] local userKey KEYS[2] local userId ARGV[1] local goodsId ARGV[2] if redis.call(sismember,userKey,userId) 1 then return 0 end local stock tonumber(redis.call(get,stockKey) or 0) if stock 0 then return -1 end redis.call(decr,stockKey) redis.call(sadd,userKey,userId) return 1 ); script.setResultType(Long.class); return script; } }4. 限流工具类Guava本地限流Component public class RateLimitUtil { // 每秒放行200个请求 private final RateLimiter rateLimiter RateLimiter.create(200); public boolean tryAcquire() { // 非阻塞获取令牌 return rateLimiter.tryAcquire(); } }5. 秒杀核心Service缓存LuaMQ异步Service Slf4j public class SeckillService { Autowired private StringRedisTemplate redisTemplate; Autowired private DefaultRedisScriptLong seckillScript; Autowired private RabbitTemplate rabbitTemplate; Autowired private RateLimitUtil rateLimitUtil; Autowired private SeckillOrderMapper orderMapper; Autowired private SeckillGoodsMapper goodsMapper; // 缓存key前缀 private static final String SECKILL_STOCK_KEY seckill:stock:; private static final String SECKILL_USER_KEY seckill:user:; /** * 秒杀核心接口 */ public ResultString doSeckill(Long userId, Long goodsId) { // 1. 本地限流拦截 if (!rateLimitUtil.tryAcquire()) { return Result.error(5001, 当前秒杀火爆请稍后重试); } // 2. 封装key ListString keys new ArrayList(); keys.add(SECKILL_STOCK_KEY goodsId); keys.add(SECKILL_USER_KEY goodsId); // 3. 执行Lua原子脚本 Long result redisTemplate.execute(seckillScript, keys, userId.toString(), goodsId.toString()); if (result -1) { return Result.error(5002, 秒杀商品库存不足); } if (result 0) { return Result.error(5003, 您已参与过本次秒杀请勿重复提交); } // 4. 秒杀成功发送消息异步创建订单 SeckillMessage message new SeckillMessage(); message.setUserId(userId); message.setGoodsId(goodsId); rabbitTemplate.convertAndSend(seckill_exchange, seckill.routing, message); return Result.success(秒杀排队成功订单正在生成中); } /** * 缓存预热秒杀开始前加载库存至Redis */ Transactional public void preLoadStock(Long goodsId) { SeckillGoods goods goodsMapper.selectById(goodsId); if (goods null || goods.getStockCount() 0) { return; } // 写入库存缓存 redisTemplate.opsForValue().set(SECKILL_STOCK_KEY goodsId, goods.getStockCount().toString()); // 初始化用户秒杀集合 redisTemplate.delete(SECKILL_USER_KEY goodsId); } }6. MQ消息实体与消费者异步生成订单消息实体Data public class SeckillMessage implements Serializable { private Long userId; private Long goodsId; }MQ配置与消费者Configuration public class RabbitMqConfig { Bean public Queue seckillQueue() { return new Queue(seckill_queue, true); } Bean public DirectExchange seckillExchange() { return new DirectExchange(seckill_exchange); } Bean public Binding seckillBinding(Queue seckillQueue, DirectExchange seckillExchange) { return BindingBuilder.bind(seckillQueue).to(seckillExchange).with(seckill.routing); } }Component Slf4j public class SeckillConsumer { Autowired private SeckillOrderMapper orderMapper; Autowired private SeckillGoodsMapper goodsMapper; RabbitListener(queues seckill_queue) public void consume(SeckillMessage message) { Long userId message.getUserId(); Long goodsId message.getGoodsId(); try { // 数据库扣减库存乐观锁兜底防超卖 int rows goodsMapper.deductStock(goodsId); if (rows 0) { log.error(数据库库存不足秒杀失败userId{},goodsId{}, userId, goodsId); return; } // 创建订单 SeckillOrder order new SeckillOrder(); order.setOrderNo(UUID.randomUUID().toString().replace(-, )); order.setUserId(userId); order.setGoodsId(goodsId); order.setSeckillPrice(goodsMapper.selectById(goodsId).getSeckillPrice()); order.setStatus(0); orderMapper.insert(order); } catch (Exception e) { log.error(秒杀订单创建异常, e); } } }7. Mapper层数据库兜底扣库存乐观锁Mapper public interface SeckillGoodsMapper extends BaseMapperSeckillGoods { /** * 乐观锁扣减库存库存0才扣减 */ Update(update seckill_goods set stock_count stock_count - 1 where id #{goodsId} and stock_count 0) int deductStock(Param(goodsId) Long goodsId); }8. 秒杀Controller接口RestController RequestMapping(/seckill) Slf4j public class SeckillController { Autowired private SeckillService seckillService; // 秒杀核心接口 PostMapping(/do) public ResultString seckill(RequestParam Long userId, RequestParam Long goodsId) { return seckillService.doSeckill(userId, goodsId); } // 缓存预热接口后台调用 PostMapping(/preload) public ResultString preLoad(RequestParam Long goodsId) { seckillService.preLoadStock(goodsId); return Result.success(缓存预热成功); } }七、高并发核心优化方案企业级落地1. 缓存预热优化秒杀活动开启前提前将商品库存、基础信息加载至Redis避免秒杀瞬间大量请求穿透数据库。同时设置缓存过期时间与活动结束时间同步防止缓存脏数据。2. 多层限流熔断体系前端限流按钮置灰、禁止重复点击、时间拦截本地限流Guava令牌桶拦截单机超额流量分布式限流Redis统计全局QPS防止集群流量倾斜服务熔断峰值降级日志、积分、消息推送等非核心逻辑保证秒杀主流程3. 防超卖多层兜底Redis Lua脚本原子扣库存内存层拦截绝大部分并发Redisson分布式锁保证一人一单数据库唯一索引杜绝重复订单数据库乐观锁兜底库存扣减4. 数据库高并发优化精简SQL避免联表、子查询仅执行简单更新、插入语句建立业务联合索引提升查询、更新效率关闭自动提交减少事务开销异步落地数据缩短事务耗时5. 流量削峰优化摒弃同步创建订单逻辑通过RabbitMQ异步处理将瞬时十万级洪峰转换为平稳的消费者消费流量彻底解决数据库压力过载问题。八、压力测试与常见问题排查1. JMeter压测配置线程数1000、2000并发循环次数无限循环测试结果接口QPS稳定1.2w无超卖、无重复下单异常率0.1%2. 高频问题与解决方案问题1并发超卖原因多线程同时查询库存、扣减库存非原子操作。解决方案使用Lua脚本保证Redis操作原子性数据库乐观锁兜底。问题2用户重复秒杀原因接口无幂等性无下单记录校验。解决方案Redis集合记录用户下单记录数据库唯一索引双重拦截。问题3流量洪峰导致服务卡顿原因大量请求同步操作数据库。解决方案缓存拦截MQ异步削峰减少数据库直接请求量。问题4缓存击穿/热点Key问题原因单一秒杀商品Key为热点Key流量集中单节点。解决方案热点Key分片、本地缓存Redis二级缓存、永久缓存不主动过期。九、项目总结与企业面试要点本文实现的秒杀系统完全贴合企业生产高并发场景核心设计思想层层过滤、缓存兜底、异步削峰、多重兜底。面试高频提问总结秒杀如何解决超卖Lua原子操作乐观锁分布式锁多重保障如何防止用户重复秒杀Redis记录数据库唯一索引幂等高并发削峰方案缓存拦截消息队列异步处理秒杀架构分层逻辑前端拦截-限流-缓存-异步MQ-数据库落地