从核心原理到生产级方案,一次性讲透秒杀系统的设计思路、常见问题与工程实践
秒杀系统是一种应对瞬时超高并发的电商场景系统,核心特征是在极短时间内(通常几秒到几分钟),大量用户争抢极少量商品。
| 维度 | 普通电商 | 秒杀系统 |
|---|---|---|
| 并发量 | 平稳,可预测 | 瞬时暴增 100-1000 倍 |
| 库存 | 充足 | 极度稀缺(几十~几百件) |
| 请求有效比 | 高(大部分浏览→下单) | 极低(99%+ 请求无效) |
| 核心矛盾 | 功能丰富、体验好 | 高可用、防超卖、防刷 |
| 缓存策略 | 常规缓存加速 | 多级缓存 + 本地缓存 |
| 数据库压力 | 可通过读写分离缓解 | 必须避免请求穿透到 DB |
| 设计原则 | 尽量让用户买到 | 尽量让无效请求提前拦截 |
瞬时流量可达日常的百倍甚至千倍,系统可能在几秒内收到数十万请求
库存只有 100 件却卖出了 120 件,这是最严重的业务事故
黄牛用脚本秒杀,正常用户根本抢不到
秒杀期间系统不能挂,挂了比卖超还严重
| 层级 | 拦截手段 | 拦截比例 | 效果 |
|---|---|---|---|
| L1 客户端 | 按钮置灰、倒计时、验证码、请求节流 | ~30% | 减少重复/无效点击 |
| L2 CDN/Nginx | 限流(令牌桶/漏桶)、静态资源CDN缓存 | ~40% | 流量在边缘被截断 |
| L3 网关层 | 用户ID限流、IP限流、Token校验、黑名单 | ~15% | 识别并拒绝恶意请求 |
| L4 服务层 | Redis 库存预判、本地缓存标记、分布式锁 | ~14% | 无库存直接返回 |
| L5 数据层 | 乐观锁/悲观锁、唯一索引、事务 | ~1% | 最终兜底防超卖 |
| 方案 | 实现方式 | 性能 | 一致性 | 复杂度 | 推荐度 |
|---|---|---|---|---|---|
| 方案一 | 下单减库存(DB 直接扣) | ❌ 低 | ❌ 有超卖风险 | 低 | ❌ 不推荐 |
| 方案二 | 付款减库存 | ⚠️ 中 | ❌ 可能卖不出 | 中 | ⚠️ 有缺陷 |
| 方案三 | 预扣库存(Redis + DB 兜底) | ✅ 高 | ✅ 最终一致 | 高 | ✅ 推荐 |
库存 = 1,两个请求同时到达:
结果:卖出了 2 件,实际只有 1 件
使用原子操作 / 乐观锁:
结果:只卖 1 件,正确!
当下游服务(如积分服务、库存服务)异常率超过阈值,自动熔断,直接返回降级结果
系统压力过大时主动牺牲非核心功能,保全核心链路
| 降级级别 | 降级内容 |
|---|---|
| L1 轻度 | 关闭推荐、评论等非核心功能 |
| L2 中度 | 静态化页面,跳过实时价格查询 |
| L3 重度 | 排队提示"活动火爆,请稍后再试" |
| L4 极端 | 直接返回售罄,停止接收新请求 |
| 层级 | 存储 | 内容 | 命中率 | 延迟 |
|---|---|---|---|---|
| L1 本地缓存 | Caffeine / Guava | 售罄标记、活动配置 | ~95% | < 1ms |
| L2 分布式缓存 | Redis Cluster | 库存数量、用户购买标记 | ~90% | 1-5ms |
| L3 数据库 | MySQL | 订单持久化、最终库存 | - | 10-50ms |
大量请求查询不存在的数据,绕过缓存直达DB
方案:
热点key过期瞬间,大量请求同时打到DB
方案:
并发场景下可能出现:
缓存与DB不一致!
延迟双删 + 消息队列兜底:
最终一致性保障
| 维度 | RocketMQ | Kafka | RabbitMQ |
|---|---|---|---|
| 吞吐量 | ⭐⭐⭐⭐⭐ (10w+) | ⭐⭐⭐⭐⭐ (100w+) | ⭐⭐⭐ (万级) |
| 延迟 | ms 级 | ms 级 | μs 级 |
| 可靠性 | 极高(同步刷盘+同步双写) | 高(副本机制) | 高(镜像队列) |
| 事务消息 | ✅ 原生支持 | ❌ 需自行实现 | ❌ 不支持 |
| 顺序消费 | ✅ 队列级别有序 | ✅ Partition 有序 | ⚠️ 单Consumer有序 |
| 秒杀推荐 | ✅ 首选(阿里出品,专为电商) | ✅ 备选(超大规模) | ❌ 吞吐不够 |
| 方案 | 实现 | 性能 | 可靠性 | 适用场景 |
|---|---|---|---|---|
| Redis | SETNX + 过期时间 | ⭐⭐⭐⭐⭐ | ⚠️ 需防锁续期失败 | 秒杀首选 |
| ZooKeeper | 临时顺序节点 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 强一致性要求高 |
| MySQL | SELECT FOR UPDATE | ⭐⭐ | ⭐⭐⭐⭐ | 兜底方案 |
| 算法 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 固定窗口 | 固定时间窗口内计数 | 实现简单 | 窗口边界突刺 | 简单限流 |
| 滑动窗口 | 窗口平滑滑动 | 解决边界突刺 | 内存开销稍大 | 精确限流 |
| 漏桶算法 | 恒定速率出水 | 流量平滑 | 无法应对突发 | 保护DB |
| 令牌桶 | 恒定速率放令牌 | 允许合理突发 | 实现稍复杂 | 秒杀首选 ⭐ |
| 维度 | 限流规则 | 目的 |
|---|---|---|
| 全局 QPS | 10w/s | 保护系统总容量 |
| 单用户 | 5次/分钟 | 防刷+公平 |
| 单 IP | 100次/秒 | 防恶意攻击 |
| 单商品 | 库存×3 | 超卖保护 |
| 接口级别 | 根据容量设定 | 精细控制 |
| 层级 | 手段 | 原理 | 防住什么 |
|---|---|---|---|
| L1 | 图形验证码/滑块 | 人机识别,增加操作成本 | 简单脚本 |
| L2 | 短信/邮箱验证 | 绑定真实身份 | 批量账号 |
| L3 | IP 频次限制 | 单IP单位时间请求数上限 | 同IP多号 |
| L4 | 设备指纹 | 识别同一设备多账号 | 设备刷单 |
| L5 | 风控规则引擎 | 行为特征分析+评分 | 高级黄牛 |
| L6 | 动态令牌(Token) | 秒杀URL动态化,防直接调用 | 接口盗刷 |
| 工具 | 类型 | 特点 | 适用场景 |
|---|---|---|---|
| JMeter | GUI压测 | 功能全面、插件丰富 | 接口压测、场景编排 |
| wrk / wrk2 | 命令行压测 | 轻量高效、支持恒定吞吐 | 快速基准测试 |
| Gatling | 代码化压测 | Scala DSL、报告美观 | 持续集成压测 |
| Locust | Python压测 | 代码即脚本、Web UI | 灵活场景编写 |
四重保障:
核心:Redis 做性能防线,DB 做正确性兜底。
生产环境 Redis 至少 6 节点集群(3主3从),RDB+AOF 持久化。
RocketMQ 的事务消息是最佳方案,保证本地事务与消息发送的原子性。
| 指标 | 目标值 | 说明 |
|---|---|---|
| 峰值 QPS | 10w+ | 根据业务预估,预留 2 倍 buffer |
| RT P99 | < 200ms | 99% 请求 200ms 内响应 |
| 可用性 | 99.99% | 全年不可用 < 52.6 分钟 |
| 超卖率 | 0% | 绝对不允许超卖 |
| 错误率 | < 0.1% | 非业务拒绝类的系统错误 |
评估方法:全链路压测 + 线上影子表验证 + 容量规划(机器数 = 峰值QPS / 单机QPS × 冗余系数)
核心架构演进:
关键:没有银弹,100w 并发靠的是层层拦截 + 水平扩展 + 异步削峰的组合拳。
幂等性 = 同一操作执行多次结果相同
秒杀系统的本质是用空间换时间、用异步换同步、用缓存换DB。
不是让所有请求都成功,而是让合法请求尽可能成功,让非法请求尽早失败。
不是追求强一致性,而是保证最终一致性,用对账和补偿兜底。