01-秒杀系统设计详解

张开发
2026/4/14 6:30:15 15 分钟阅读

分享文章

01-秒杀系统设计详解
秒杀系统设计详解一、知识概述秒杀系统是电商领域最具挑战性的高并发场景之一,典型特征是瞬时高并发、库存有限、时间敏感。一个成功的秒杀系统需要在极短时间内处理海量请求,同时保证数据一致性和用户体验。核心挑战:流量突增:平时QPS可能只有几十,秒杀开始瞬间可能达到数万甚至数十万库存竞争:大量用户同时抢购有限库存,如何避免超卖系统稳定性:流量洪峰下保证系统不崩溃公平性:如何保证用户公平参与,防止黄牛作弊技术指标:QPS:10万+响应时间:P99 100ms可用性:99.99%超卖率:0%二、知识点详细讲解2.1 秒杀系统架构设计2.1.1 整体架构┌─────────────────────────────────────────────────────────────┐ │ 客户端层 │ │ (App/Web/H5 → CDN静态资源 → 本地缓存) │ └────────────────────┬────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 接入层 │ │ (DNS负载均衡 → Nginx网关 → 限流熔断) │ └────────────────────┬────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 应用层 │ │ (API网关 → 秒杀服务 → 订单服务 → 支付服务) │ └────────────────────┬────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 数据层 │ │ (Redis缓存 → MySQL主从 → 消息队列) │ └─────────────────────────────────────────────────────────────┘2.1.2 核心流程秒杀前:预热缓存:将商品信息、库存加载到Redis生成令牌:预生成秒杀令牌,防止脚本刷单CDN预热:静态资源提前推送到CDN秒杀中:用户请求 → 限流校验 → 令牌验证库存预扣减(Redis原子操作)异步创建订单(消息队列)返回排队结果秒杀后:订单支付(限时未支付自动取消)库存回滚(订单取消时)数据同步到MySQL2.2 库存扣减方案2.2.1 方案对比方案实现方式优点缺点适用场景数据库乐观锁UPDATE ... WHERE stock0 AND version=old强一致性性能差,大量失败低并发场景Redis DECRDECR stock_key高性能需要预加载库存中等并发Redis Lua脚本原子性扣减高性能+原子性需要脚本维护高并发场景预扣减+异步Redis预扣+MQ异步下单削峰填谷最终一致性超高并发2.2.2 Redis Lua脚本扣减-- seckill_deduct.lualocalstock_key=KEYS[1]localuser_key=KEYS[2]localuser_id=ARGV[1]localdeduct_num=tonumber(ARGV[2])-- 检查是否已购买(防重复)ifredis.call('sismember',user_key,user_id)==1thenreturn-1-- 已购买end-- 检查库存localstock=tonumber(redis.call('get',stock_key))ifnotstockorstockdeduct_numthenreturn0-- 库存不足end-- 扣减库存redis.call('decrby',stock_key,deduct_num)-- 记录购买用户redis.call('sadd',user_key,user_id)return1-- 扣减成功2.3 防超卖机制2.3.1 多层次防护/** * 秒杀服务 - 防超卖实现 */@ServicepublicclassSeckillService{@AutowiredprivateStringRedisTemplateredisTemplate;@AutowiredprivateRocketMQTemplaterocketMQTemplate;// Redis Lua脚本privatestaticfinalStringDEDUCT_SCRIPT="local stock = tonumber(redis.call('get', KEYS[1]))\n"+"if not stock or stock tonumber(ARGV[1]) then\n"+" return 0\n"+"end\n"+"redis.call('decrby', KEYS[1], ARGV[1])\n"+"return 1";/** * 秒杀入口 - 多层次防护 */@TransactionalpublicSeckillResultdoSeckill(LonguserId,LongproductId,intquantity){// 第一层:本地标记(JVM级别快速失败)if(!localStockFlag.get(productId)){returnSeckillResult.fail("商品已售罄");}// 第二层:Redis令牌桶限流if(!rateLimiter.tryAcquire()){returnSeckillResult.fail("系统繁忙,请稍后重试");}// 第三层:用户购买资格校验if(hasPurchased(userId,productId)){returnSeckillResult.fail("您已购买过该商品");}// 第四层:Redis原子扣减库存Longresult=redisTemplate.execute(newDefaultRedisScript(DEDUCT_SCRIPT,Long.class),Collections.singletonList("seckill:stock:"+productId),String.valueOf(quantity));if(result==null||result==0){// 更新本地标记localStockFlag.put(productId,false);returnSeckillResult.fail("商品已售罄");}// 第五层:异步创建订单(消息队列)SeckillOrderorder=SeckillOrder.builder().userId(userId).productId(productId).quantity(quantity).status(OrderStatus.PENDING).createTime(newDate()).build();rocketMQTemplate.asyncSend("seckill-order-topic",order,newSendCallback(){@OverridepublicvoidonSuccess(SendResultsendResult){log.info("订单消息发送成功: {}",order);}@OverridepublicvoidonException(Throwablee){log.error("订单消息发送失败,需要回滚库存",e);rollbackStock(productId,quantity);}});

更多文章