秒杀系统设计(学习):从 “稳准快” 到落地的全流程方案

秒杀系统设计(学习):从 “稳准快” 到落地的全流程方案

目录

一、秒杀系统的核心目标:稳、准、快

二、秒杀架构的核心原则:4 要 1 不要

三、分层优化:从用户端到数据库,层层挡压力

1. 第一层:用户端 + CDN(挡掉 70% 的请求)

2. 第二层:接入层 + 应用层(再挡 25% 的请求)

3. 第三层:缓存层(Redis)(再挡 4% 的请求)

4. 第四层:消息队列(削峰)

5. 第五层:数据库(最后 1% 的请求)

四、秒杀的关键问题:怎么解决 “超卖” 和 “热点”?

1. 减库存:秒杀必须用 “下单减库存”,配合双重校验

2. 热点处理:秒杀商品是 “超级热点”,必须隔离

3. 流量削峰:把 “1 秒的高峰” 拉成 “3 秒的平缓流量”

五、兜底方案:即使出问题,也要 “优雅降级”

1. 限流:超过容量就 “请稍后再试”

2. 熔断:依赖服务挂了,就暂时不用

3. 降级:核心功能保留,非核心功能关掉

4. 核对:事后校验,避免数据不一致

六、秒杀系统设计总结:核心是 “层层过滤,抓大放小”

秒杀的核心矛盾很明确:短时间内海量请求(比如 1 秒 10 万次)争抢少量商品(比如 100 件),既要保证系统不崩溃(稳)、不超卖不漏卖(准),又要让用户感觉快(低延迟)。下面从 “核心目标→架构原则→分层优化→关键问题解决→兜底方案” 讲秒杀系统的设计逻辑。

一、秒杀系统的核心目标:稳、准、快

先明确三个不能妥协的目标,所有设计都围绕这三点展开:

稳:秒杀开始时,哪怕请求超预期(比如预估 10 万 QPS,实际来了 50 万),系统也不能崩,至少要 “优雅降级”(比如部分用户排队,而不是直接报错)。

准:商品库存是 100 件,就只能成交 100 单,多 1 单超卖、少 1 单漏卖都不行 —— 这是业务底线。

快:用户点击 “抢购” 后,不能卡半天;即使没抢到,也要快速告诉用户结果,避免用户反复刷新加重系统负担。

二、秒杀架构的核心原则:4 要 1 不要

普通分布式系统的原则要结合秒杀场景做调整,核心是 “尽量把压力挡在离数据库最远的地方”:

原则秒杀场景的具体落地解决的问题数据要尽量少1. 秒杀页面只保留 “商品图 + 标题 + 抢购按钮”,去掉评论、推荐等无关模块; 2. 动态数据(库存、倒计时)只传 “数字”,不传冗余字段。减少网络传输量,降低服务器编码 / 解码压力(比如页面大小从 1MB 缩到 100KB)。请求数要尽量少1. 合并 CSS/JS 文件(比如把 3 个 JS 合并成 1 个); 2. 秒杀按钮点击后,不刷新整个页面,只发小请求查库存。减少浏览器 HTTP 请求次数(比如从 10 次请求缩到 2 次),避免连接建立 / 断开的开销。路径要尽量短1. 秒杀系统独立部署(单独域名、单独服务器集群),不与普通购物系统共用资源; 2. 秒杀核心逻辑(查库存、下单)不调用非必要服务(比如不查优惠券、不校验积分)。避免秒杀流量冲垮普通业务,减少跨服务调用的延迟(比如从 “调用 5 个服务” 缩到 “调用 1 个服务”)。依赖要尽量少1. 秒杀只强依赖 “商品库存” 和 “用户登录状态”,其他依赖(比如物流预查询)全部做成 “弱依赖”(能降级); 2. 弱依赖故障时,直接跳过(比如不查物流,下单后再补)。避免 “非核心服务挂了,导致核心秒杀流程崩了”(比如优惠券服务故障,不影响用户抢秒杀商品)。不要有单点1. 秒杀用的 Redis、MySQL 都做集群(Redis 主从 + 哨兵,MySQL 主从 + 分库分表); 2. 秒杀服务器多机部署,用负载均衡分摊流量。避免某一台机器 / 服务挂了,整个秒杀就停了(比如 Redis 主库挂了,从库能立刻顶上)。

三、分层优化:从用户端到数据库,层层挡压力

秒杀的请求流量像 “洪水”,要在每一层都设置 “堤坝”,尽量把无效请求过滤掉,只让少量有效请求到达数据库(数据库是最后一道防线,也是最脆弱的)。

1. 第一层:用户端 + CDN(挡掉 70% 的请求)

目标:让用户 “少发请求、发小请求”,尽量在离用户最近的地方返回数据。

静态数据全缓存:秒杀页面的商品图、标题、规则等 “静态数据”,提前放到 CDN(比如阿里云 CDN、腾讯云 CDN),用户打开页面时直接从 CDN 加载,不碰后端服务器。

动态数据本地暂存:秒杀倒计时(比如 “还有 5 分钟开始”),前端用 JS 定时更新,不用每秒向后端查;库存数先缓存到前端,比如 “当前库存 > 0”,只在用户点击 “抢购” 时再查实时库存。

防重复点击:前端加 “点击锁”,用户点击 “抢购” 后,按钮立刻置灰,避免用户连续点击发多个请求。

2. 第二层:接入层 + 应用层(再挡 25% 的请求)

目标:过滤无效请求(比如未登录、秒杀未开始),缓存热点数据,减轻下游压力。

接入层限流:用 Nginx 做第一道限流,比如按 IP 限制 “每秒最多发 5 次请求”,超过的直接返回 “请稍后再试”—— 这能挡住大部分 “秒杀器” 的高频请求。

应用层本地缓存:把秒杀商品的 “库存数、状态(是否开始)” 缓存到应用服务器的内存(本地缓存,比如 Caffeine),查库存时先读本地缓存,不直接查 Redis/MySQL。 例:10 台应用服务器,每台本地缓存 “秒杀商品 A 库存 100 件”,用户查库存时,90% 的请求直接在本地返回,不用去 Redis 查。

前置校验:在应用层先做 “无效请求过滤”:

秒杀没开始?返回 “活动未开始”;

用户没登录?返回 “请先登录”;

用户已经抢过该商品?返回 “您已参与过”。

3. 第三层:缓存层(Redis)(再挡 4% 的请求)

目标:处理 “高频读 + 高频写(预扣库存)”,Redis 的 QPS 能到 10 万 +,比数据库强 100 倍。

预扣库存:秒杀开始前,把商品库存从 MySQL 同步到 Redis(比如 “商品 A 库存 100”),用户点击 “抢购” 时,先在 Redis 里 “减库存”(DECR 商品A库存),减成功了再走下单流程;减失败(库存 <=0)直接返回 “已抢完”。 关键:Redis 预扣能挡住 99% 的 “抢不到” 请求,只有 1% 的请求能到数据库。

防缓存击穿:秒杀商品是 “超级热点”,如果 Redis 缓存失效,所有请求会瞬间打到 MySQL(缓存击穿)。解决方案:

给秒杀商品的缓存设置 “永不过期”,秒杀结束后再手动删除;

用 “互斥锁”,缓存失效时只让一个线程去查 MySQL,其他线程等待。

分布式锁:如果多个应用服务器同时操作 Redis 库存,用 Redis 的SET NX做分布式锁,避免 “超扣”(比如两个线程同时读库存 = 1,都减到 0,导致实际扣了 2 次)。

4. 第四层:消息队列(削峰)

目标:把 “瞬时高峰” 拉平成 “平缓流量”,避免数据库被瞬间压垮。

请求入队:用户在 Redis 预扣库存成功后,不直接调用数据库下单,而是把 “下单请求” 放进消息队列(比如 RabbitMQ、RocketMQ),应用服务器再 “慢悠悠” 地从队列里取请求,调用数据库下单。 例:1 秒内来了 1 万次预扣成功的请求,消息队列把这些请求按 “每秒 100 次” 的速度发给数据库,数据库能轻松处理。

重试机制:如果数据库下单失败(比如网络波动),消息队列会重试,保证 “只要预扣了库存,就一定能下单”,避免漏单。

5. 第五层:数据库(最后 1% 的请求)

目标:保证数据最终一致性(不超卖),这是底线。

库存字段设为无符号整数:MySQL 的库存字段(比如inventory)设为UNSIGNED INT,如果库存减到负数,SQL 会直接报错,从数据库层面防止超卖。

SQL 加条件判断:下单减库存时,SQL 必须带 “库存>= 购买数量” 的条件,避免并发下超卖: UPDATE 商品表 SET inventory = inventory - 1 WHERE 商品ID=1 AND inventory >= 1; 关键:InnoDB 的行锁会保证同一时间只有一个线程能执行这条 SQL,即使 100 个线程同时来,也只会有 1 个线程减成功。

热点数据隔离:把秒杀商品的库存表单独放在 “热点数据库”,不与普通商品的数据库共用 —— 避免秒杀商品的高频写操作,影响普通商品的查询。

四、秒杀的关键问题:怎么解决 “超卖” 和 “热点”?

前面的分层优化能解决大部分问题,但还有两个核心问题要单独讲:

1. 减库存:秒杀必须用 “下单减库存”,配合双重校验

秒杀场景下,三种减库存方式的对比:

减库存方式适用场景秒杀场景的问题秒杀的选择与优化下单减库存库存少、用户付款率高无明显问题(秒杀用户抢到后基本会付款)✅ 首选:Redis 预扣 + MySQL 最终扣,双重保证不超卖。付款减库存库存多、付款率低超卖风险高(100 件商品可能有 1000 人下单,付款时才发现没库存)❌ 不选:秒杀用户体验差,且超卖风险高。预扣库存(限时)普通电商逻辑复杂(要定时释放库存),秒杀场景下没必要(付款率高)❌ 不选:增加系统复杂度,不如直接下单减库存简单。

优化方案:Redis 预扣(快速过滤)+ MySQL 最终扣(保证一致)

步骤 1:用户点击抢购,Redis 执行DECR 商品A库存,如果结果 > 0,说明预扣成功;

步骤 2:预扣成功后,请求入消息队列,异步调用 MySQL 执行 “UPDATE 商品表 SET inventory = inventory -1 WHERE 商品 ID=1 AND inventory>=1;”;

步骤 3:如果 MySQL 执行成功(影响行数 = 1),下单成功;如果执行失败(影响行数 = 0,说明 Redis 预扣时多扣了),回滚 Redis 库存(INCR 商品A库存)。

2. 热点处理:秒杀商品是 “超级热点”,必须隔离

秒杀的热点很集中 —— 就那几个商品,所有请求都盯着它们,必须单独处理:

业务隔离:秒杀商品单独报名,报名后标记为 “热点商品”,走专门的秒杀流程(不与普通商品共用接口)。

系统隔离:秒杀系统用单独的域名(比如seckill.xxx.com)、单独的服务器集群,流量不与普通购物系统互通。

数据隔离:秒杀商品的库存表、订单表单独放在 “热点数据库”,甚至单独的 MySQL 实例 —— 避免秒杀的高频写操作,拖慢普通商品的数据库。

3. 流量削峰:把 “1 秒的高峰” 拉成 “3 秒的平缓流量”

秒杀的流量特点是 “瞬间爆发”(比如 0 点整突然来 10 万 QPS),必须用手段 “拉长请求时间”:

答题验证:用户点击 “抢购” 后,先弹出简单的验证码(比如 “请选择下图中的自行车”),验证通过才允许下单。 作用:1. 防秒杀器(机器识别验证码难);2. 拉长请求时间(用户答题需要 1-2 秒),把 1 秒的高峰拉成 3 秒,服务器压力降为 1/3。

排队机制:用 “排队页面” 代替直接抢购 —— 用户点击 “抢购” 后,进入排队页面(显示 “您排在 1000 位,预计 3 秒后处理”),服务器按排队顺序处理请求。 原理:用消息队列的 “FIFO” 特性,把请求按顺序排队,避免同时处理大量请求。

五、兜底方案:即使出问题,也要 “优雅降级”

秒杀场景下,总有意料之外的情况(比如 Redis 集群挂了、请求超预期),必须有兜底方案:

1. 限流:超过容量就 “请稍后再试”

接入层限流:Nginx 按 IP / 用户 ID 限流,比如每秒最多 5 次请求;

应用层限流:用 Guava 的 RateLimiter,每台服务器每秒最多处理 1000 个秒杀请求;

Redis 限流:用INCR 限流键,超过阈值就返回 “系统繁忙”。

2. 熔断:依赖服务挂了,就暂时不用

如果秒杀依赖的 “用户服务” 挂了,暂时不校验用户信息(只校验登录状态),先让秒杀流程走通;等用户服务恢复后,再补校验。

3. 降级:核心功能保留,非核心功能关掉

秒杀高峰期,关掉 “订单详情页的评论”“商品推荐” 等非核心功能,把服务器资源全部留给 “查库存、下单” 核心流程。

4. 核对:事后校验,避免数据不一致

秒杀结束后,用 “离线核对” 保证数据正确:

对比 Redis 预扣库存和 MySQL 实际库存,看是否一致;

对比订单数和库存减少数,确保 “订单数 = 库存减少数”(避免漏单或超卖);

发现不一致,手动修正(比如多扣了库存,就加回去;漏下单了,就补下单)。

六、秒杀系统设计总结:核心是 “层层过滤,抓大放小”

秒杀不是 “堆服务器” 就能解决的,而是靠 “策略”:

从外到内,层层挡压力:CDN 挡静态请求→应用层挡无效请求→Redis 挡高频请求→消息队列削峰→数据库保一致;

抓核心矛盾:优先保证 “不超卖” 和 “系统不崩”,其次再优化 “快”;

简单优先:能用 Redis 预扣,就不用复杂的分布式事务;能用本地缓存,就不用分布式缓存。

相关推荐

铤鹿是什么意思?
365bet注册送

铤鹿是什么意思?

📅 07-19 👁️ 2041
打印讲义、备注或幻灯片
真的365会不会黑款

打印讲义、备注或幻灯片

📅 08-22 👁️ 3413
时代的弃儿 混合硬盘怎么就成了昙花一现的鸡肋?