如何设计一个支持 10 万 QPS 的秒杀系统?

张开发
2026/6/21 23:13:46 15 分钟阅读
如何设计一个支持 10 万 QPS 的秒杀系统?
作为一名经历过多次电商大促考验的 Java 后端开发者我深知秒杀系统是对技术架构的终极考验。它不仅需要应对瞬时万级 QPS 的冲击更要在库存安全、用户体验、成本控制之间找到平衡。本文将从业务痛点出发分享一套经过实战验证的秒杀系统设计方案涵盖架构分层、核心模块实现与工程化经验。一、业务特性与核心挑战分析1. 秒杀业务的三大核心特性•流量突增日常流量 100QPS → 秒杀瞬时 10 万 QPS典型 1000 倍突发•库存有限单个商品库存通常≤1000 件库存扣减必须精准避免超卖 / 少卖•短事务性核心流程仅包含 库存校验→扣减→订单生成要求 RT50ms2. 技术实现的五大痛点二、全链路架构分层设计1. 七层防护架构•前端层 → 接入层按钮防重复点击防用户重复提交•网关层 → 接入层令牌桶限流流量控制•接入层 → 应用层人机校验防御自动化攻击•应用层 → 缓存层队列削峰应对流量高峰•缓存层 → 数据库层库存预热预加载热点数据•数据库层 → 存储层行锁优化并发控制•存储层 → 日志系统异步落盘提升IO性能2. 关键分层设计解析1前端层流量第一道防线•按钮置灰点击后禁用 3 秒拦截 50% 重复请求•动态令牌调用秒杀接口前需先获取 Redis 令牌seckill:token:{userId}•浏览器缓存缓存秒杀倒计时减少无效 API 调用2网关层流量清洗中心// 基于Spring Cloud Gateway的限流配置 Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(seckill, r - r.path(/seckill/**) .filters(f - f.requestRateLimiter(config - config.setRedisRateLimiter( new RedisRateLimiter(10, 20) // 每秒10请求突发容量20 ) )) .uri(lb://seckill-service)) .build(); }3应用层业务逻辑核心•独立域名隔离秒杀业务使用独立域名seckill.example.com避免影响主站•线程池隔离为秒杀业务单独配置线程池corePoolSize200,maxPoolSize5004缓存层库存前置处理•双写一致性Redis 库存与 DB 库存通过异步队列保持最终一致•热点分片按商品 ID 哈希分片如seckill:stock:1001分散 Redis 压力三、核心模块实现详解1. 库存预热模块核心代码public classStockPreheatService { privatefinal JedisCluster jedisCluster; privatefinal SeckillGoodsMapper goodsMapper; // 预热库存到Redis活动开始前10分钟执行 publicvoidpreheatStock(Long goodsId, Integer stock) { // 初始化库存使用Lua脚本保证原子性 StringluaScriptif redis.call(exists, KEYS[1]) 0 then redis.call(set, KEYS[1], ARGV[1]) redis.call(set, KEYS[2], ARGV[2]) end; jedisCluster.eval(luaScript, 2, seckill:stock: goodsId, // 库存键 seckill:version: goodsId, // 版本号键 String.valueOf(stock), 1); // 初始版本号 } // 扣减库存无锁化设计 publicbooleandeductStock(Long goodsId) { StringluaScriptlocal stock tonumber(redis.call(get, KEYS[1])) if stock 0 then redis.call(decr, KEYS[1]) redis.call(incr, KEYS[2]) return 1 else return 0 end; Longresult (Long) jedisCluster.eval(luaScript, 2, seckill:stock: goodsId, seckill:version: goodsId); return result 1; } }2. 分布式令牌生成防刷机制public classTokenService { privatefinal RedisTemplateString, Integer redisTemplate; // 生成秒杀令牌每个用户限领1个 publicbooleangenerateToken(Long userId, Long goodsId) { Stringkeyseckill:token: userId : goodsId; return redisTemplate.opsForValue().setIfAbsent(key, 1, 60, TimeUnit.SECONDS); } // 校验令牌并删除防止重复使用 publicbooleanvalidateToken(Long userId, Long goodsId) { Stringkeyseckill:token: userId : goodsId; return redisTemplate.delete(key); } }3. 异步队列削峰Kafka 实现public classSeckillProducer { privatefinal KafkaTemplateString, SeckillRequest kafkaTemplate; // 发送秒杀请求到队列削峰填谷 publicvoidsendSeckillRequest(SeckillRequest request) { kafkaTemplate.send(seckill_topic, request.getGoodsId().toString(), request); } } Service publicclassSeckillConsumer { privatefinal StockPreheatService stockService; privatefinal OrderService orderService; KafkaListener(topics seckill_topic, groupId seckill_group) publicvoidprocessSeckillRequest(SeckillRequest request) { // 1. 库存扣减 if (stockService.deductStock(request.getGoodsId())) { // 2. 生成订单数据库事务 createOrder(request); } } privatevoidcreateOrder(SeckillRequest request) { OrderEntityordernewOrderEntity(); order.setGoodsId(request.getGoodsId()); order.setUserId(request.getUserId()); order.setCreateTime(LocalDateTime.now()); orderService.save(order); } }四、数据库层防超卖设计1. 库存扣减的三级校验•Redis 预扣通过 Lua 脚本保证原子性扣减内存级校验•数据库行锁扣减时使用SELECT ... FOR UPDATE锁定库存记录•版本号校验通过UPDATE goods SET stock stock - 1 WHERE id? AND version?防止 ABA 问题2. 数据库表结构优化CREATE TABLE seckill_goods ( id BIGINTPRIMARY KEY AUTO_INCREMENT, goods_id BIGINTNOT NULL COMMENT 商品ID, stock INTNOT NULL COMMENT 库存, version INTDEFAULT0 COMMENT 乐观锁版本号, create_time TIMESTAMPDEFAULTCURRENT_TIMESTAMP, update_time TIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP ); -- 扣减库存SQL带版本号校验 UPDATE seckill_goods SET stock stock -1, version version 1 WHERE goods_id ? AND stock 0AND version ?五、工程化最佳实践1. 压测与容量规划•基准测试使用 JMeter 模拟 1 万并发单节点压测阈值 QPS800•弹性扩展根据压测结果按 1:1000 比例部署服务器10 万 QPS 需 125 台节点•应急预案准备熔断组件Hystrix当 Redis 响应时间 100ms 时熔断库存查询2. 监控与报警体系•核心指标• Redis 命中率目标 99%• 队列堆积量阈值 10 万条• 数据库连接数阈值 80%•报警机制通过PrometheusGrafana实时监控异常时触发企业微信 / 短信报警3. 流量调度策略•预热期活动前 30 分钟逐步增加 CDN 节点缓存同步预热 Redis 集群•高峰期活动开始后 5 分钟启用 Nginx 限流模块limit_req_zone拒绝超过阈值的请求•降温期库存售罄后返回友好提示页面关闭异步队列消费线程六、避坑指南与经验总结1. 三大核心坑点解决方案2. 八年实战经验总结•能在内存解决的问题绝不下数据库90% 的性能问题可以通过 Redis 预热解决•分布式锁不是银弹优先使用无锁化设计如AtomicLong/Lua脚本必须加锁时采用分片锁•流量控制比流量处理更重要前端 网关层至少过滤 80% 的无效请求•最终一致性优于强一致性订单生成可异步处理库存扣减必须保证强一致

更多文章