Java分布式设计:电商秒杀系统架构实战
一、秒杀系统的挑战电商秒杀场景具有高并发、短时高负载、库存精确扣减等特点瞬间流量可达平时百倍甚至千倍。主要挑战包括流量爆发短时间内海量请求涌入远超系统承载能力资源竞争对同一商品库存的并发写操作易出现超卖/少卖响应延迟用户对秒杀结果敏感需要快速反馈成功/失败恶意行为脚本抢购、黄牛刷单、重复下单等二、整体架构设计采用分层解耦削峰填谷快速失败的分布式设计思路text客户端 → CDN/静态化 → 负载均衡(Nginx) → 网关层 → 秒杀业务层 → 消息队列 → 订单/库存服务 ↓ Redis(库存预扣) ↓ 数据库(最终一致性)核心设计原则尽量将请求拦截在上游通过限流、过滤无效请求异步化处理用MQ削峰将同步写转化为异步写无状态化业务节点可水平扩展热点数据隔离秒杀商品数据独立处理三、关键核心技术实现1. 前端与接入层优化页面静态化 CDN加速将秒杀页面HTML、CSS、JS、图片等静态资源托管至CDN动态数据通过独立API接口获取减少服务器压力动静分离Nginx直接返回静态页面动态请求反向代理到后端2. 流量削峰 - 令牌桶/漏桶限流java// 基于Guava RateLimiter的本地限流 Controller public class SecKillController { // 每秒最多放行1000个请求 private final RateLimiter rateLimiter RateLimiter.create(1000.0); PostMapping(/seckill/{productId}) ResponseBody public String seckill(PathVariable Long productId, Long userId) { if (!rateLimiter.tryAcquire(0, TimeUnit.SECONDS)) { return 当前拥挤请稍后再试; } // 执行后续秒杀逻辑 } }分布式限流使用Redis Lua脚本实现更精确的全局限流lua-- 滑动窗口限流Lua脚本 local key KEYS[1] -- 限流key local limit tonumber(ARGV[1]) -- 限流阈值 local window tonumber(ARGV[2]) -- 时间窗口(ms) local now tonumber(ARGV[3]) -- 当前时间戳 redis.call(ZREMRANGEBYSCORE, key, 0, now - window) local current redis.call(ZCARD, key) if current limit then redis.call(ZADD, key, now, now) return 1 else return 0 end3. 库存预热与原子扣减 - Redis预热秒杀开始前将库存加载到RedisjavaPostConstruct public void initStock() { // 商品id - 剩余库存 stringRedisTemplate.opsForValue().set(stock:1001, 100); }原子扣减使用Lua脚本保证减库存和校验的原子性lua-- 扣减库存Lua脚本 local stockKey KEYS[1] -- 库存key local userIdKey KEYS[2] -- 用户购买记录key local userId ARGV[1] local limit tonumber(ARGV[2]) -- 单用户限购数量 -- 检查用户是否已秒杀过 if redis.call(SISMEMBER, userIdKey, userId) 1 then return -2 -- 重复秒杀 end local stock tonumber(redis.call(GET, stockKey)) if stock nil or stock 0 then return -1 -- 无库存 end if stock limit then redis.call(DECRBY, stockKey, limit) redis.call(SADD, userIdKey, userId) return 1 -- 扣减成功 end return 0调用示例javapublic boolean tryReduceStock(Long productId, Long userId, int quantity) { String stockKey stock: productId; String userKey user:seckill: productId; String luaScript —— 上面Lua脚本内容 —— ; DefaultRedisScriptLong redisScript new DefaultRedisScript(luaScript, Long.class); Long result stringRedisTemplate.execute(redisScript, Arrays.asList(stockKey, userKey), userId.toString(), String.valueOf(quantity)); return result ! null result 1; }4. 异步下单 - 消息队列解耦扣减Redis库存成功后发送MQ消息异步创建订单javaService public class SecKillService { Autowired private RocketMQTemplate mqTemplate; public void processSeckill(Long productId, Long userId) { // 1. Redis预扣库存成功后继续 if (!tryReduceStock(productId, userId, 1)) { throw new BizException(秒杀失败); } // 2. 发送消息创建订单 SeckillOrderMessage msg new SeckillOrderMessage(); msg.setProductId(productId); msg.setUserId(userId); msg.setCreateTime(System.currentTimeMillis()); mqTemplate.syncSend(seckill_order_topic, msg); // 立即返回排队中前端轮询订单状态 } }MQ消费端执行真正的事务操作扣数据库库存、创建订单javaRocketMQMessageListener(topic seckill_order_topic, consumerGroup order_group) Component public class SeckillOrderConsumer implements RocketMQListenerSeckillOrderMessage { Transactional(rollbackFor Exception.class) public void onMessage(SeckillOrderMessage msg) { // 使用数据库行锁或乐观锁防止超卖 int updated productMapper.decreaseStock(msg.getProductId(), msg.getQuantity()); if (updated 0) { // 库存不足回滚Redis库存补偿 redisTemplate.opsForValue().increment(stock: msg.getProductId(), msg.getQuantity()); return; } // 创建订单 orderMapper.insert(order); } }5. 数据库层面防超卖 - 乐观锁sql-- 商品表增加version字段 UPDATE product SET stock stock - #{quantity}, version version 1 WHERE id #{productId} AND stock #{quantity} AND version #{version}6. 安全防护用户限购java// 使用Redis记录用户秒杀次数 String limitKey user:rate: userId; Long count redisTemplate.opsForValue().increment(limitKey); if (count 1) { redisTemplate.expire(limitKey, 5, TimeUnit.SECONDS); } if (count 5) { throw new BizException(操作过于频繁); }验证码/脚本防护秒杀前要求输入验证码(如算术题、滑块)增加抢购门槛隐藏秒杀地址秒杀开始前不暴露真实下单接口动态生成token每次请求携带token验证javaGetMapping(/seckill/path/{productId}) public String getSeckillPath(PathVariable Long productId, Long userId) { String path UUID.randomUUID().toString().replace(-, ).substring(0, 8); // 存储到Redis有效期60秒 redisTemplate.opsForValue().set(seckill_path: productId : userId, path, 60, TimeUnit.SECONDS); return path; }四、降级与兜底策略场景降级方案Redis故障直接返回系统繁忙不再穿透到DBMQ积压增加消费者数量限制订单创建速率数据库压力过大启用读写分离订单写入可临时降级为只记录日志后异步处理超出预估流量启用Hystrix熔断快速失败返回提示五、压测与监控JMeter/Locust模拟多用户并发重点测试Redis扣减和MQ吞吐监控指标QPS、RT响应时间、Redis命中率、MQ积压数量、数据库连接池使用率全链路压测提前压测到系统拐点设置合理的限流阈值六、完整业务流程时序text用户 - 前端点击 - 网关限流 - 验证Token/验证码 - Redis预扣库存(原子Lua) - 生成秒杀订单消息(MQ) - 立即返回排队中 - 前端轮询订单状态 - MQ消费者落库 - 更新数据库库存订单 - 轮询接口返回成功/失败七、总结电商秒杀的核心是在保证数据一致性的前提下尽可能提升系统吞吐量。通过上述设计Redis扛住绝大部分读和预扣库存的压力MQ削峰填谷让数据库从洪峰变为平稳流限流降级保护系统不被突发流量击垮Lua脚本保证Redis操作的原子性这套架构经过大促场景的反复验证可以支撑百万级QPS的秒杀需求。实际落地时需要根据自身业务量级和机器配置调整限流阈值、消息队列批量设置、Redis集群模式等细节。