秒杀系统设计
秒杀是电商系统中最极端的场景——瞬时流量是平时的数千倍,库存扣减必须绝对正确(超卖是资损事故),用户体验还要好。设计秒杀系统的本质是:如何用最少资源,安全地让最想买的人抢到商品。
一、业务特点与挑战
1 | 瞬时极高并发 |
核心挑战:
- 瞬时流量可能是平时的 100~1000 倍
- 库存扣减必须在并发下绝对正确
- 用户体感要流畅(页面快速响应)
二、容量估算
假设:10 万库存的 iPhone,100 万人同时抢购。
1 | 峰值 QPS ≈ 100 万(实际集中在前 0.5 秒) |
分层拦截漏斗:
1 | 100% 请求(100 万 QPS) |
三、前端层优化
- 静态化:秒杀页面做成纯静态 HTML,提前推送到 CDN。唯一动态的是”库存数量”和”按钮状态”,通过异步接口获取
- 按钮防抖:点击后置灰 3 秒,禁止重复提交
- 验证码滑块:抢购按钮点击时弹出,人机识别
- 页面版本控制:秒杀 URL 动态加签,每场活动不同 URL,防止提前暴露
四、网关层
1 | # Nginx 单 IP 限流:每 IP 每秒最多 10 个请求 |
黑白名单:白名单(内部测试/VIP 用户)走独立通道;黑名单(检测到异常行为的 IP/设备)动态加入,实时拦截。
排队机制:用户进入页面后先获取”排队 Token”,服务端用 Redis 有序集合按时间戳排序,批次放行。拿到 Token 才能进入真正的抢购接口。
五、Redis 预减库存——核心逻辑
这是秒杀系统最重要的设计。库存不直接扣减 MySQL,而是先扣 Redis:
1 | 1. 活动开始前:将库存总数 SET 到 Redis |
为什么用 DECR 而不是先 GET 再 SET? Redis 单线程模型下,DECR 是原子操作。先 GET 再 SET 在并发下会产生竞态条件。
防止重复抢购(Lua 脚本原子操作):
1 | -- 原子操作:判断用户是否抢过 + 扣库存 |
六、异步同步到 MySQL
1 | Redis 扣减成功 → 发送 Kafka 消息 → 消费者批量写入 MySQL |
消息丢失怎么办? Kafka 持久化 + ACK 确认 + 定时对账任务(对比 Redis 库存 vs MySQL 订单数,自动补单或告警)。
七、熔断降级
熔断:错误率 > 50%(滑动窗口)→ 断路器打开 → 快速返回”系统繁忙” → 冷却 30s → 半开放少量探测 → 正常则恢复。
降级:关闭非核心功能(推荐列表、购物车、积分),返回兜底数据”抢购太火爆,请稍后重试”。
隔离:秒杀服务使用独立机器、独立 Redis、独立数据库。即使秒杀链路崩溃,不影响主站下单/支付/浏览。
八、架构演进路径
1 | 第一版(初创期):单体应用 + MySQL |
九、小结
秒杀系统设计的核心是一句话:在流量到达数据库之前,用多层漏斗拦截掉 99% 的请求。前端静态化 + 网关限流 + Redis 原子库存预扣 + MQ 异步消峰,这四层构成了标准方案。