高并发电商库存防超卖完整设计方案
高并发电商库存防超卖完整设计方案
一、超卖根源
核心目标:保证库存扣减原子性 + 削峰限流保护数据库 + 最终库存数据一致
-
数据库并发读写非原子性(核心根本原因):传统库存扣减逻辑遵循「查询库存→判断充足→扣减库存」三步串行操作,单线程无问题,但高并发场景下,大量请求会同时查询到库存充足的状态,并行执行扣减操作。数据库普通查询无锁保护,多请求无串行隔离,最终导致总扣减数量大于实际库存,直接引发超卖。
-
秒杀流量洪峰击穿系统:商品秒杀、限时折扣场景会产生瞬时万级QPS流量,远超系统常规承载能力。海量请求瞬间涌入,频繁触发数据库事务、行锁竞争,造成数据库阻塞、响应超时、线程堆积。部分请求因超时未及时返回结果,前端重复重试,进一步加剧并发扣减冲突,放大超卖风险。
-
分布式架构本地锁失效:单体应用中synchronized、ReentrantLock等本地锁可控制单节点并发,但电商服务均为分布式集群部署,多节点、多JVM、多进程独立运行。本地锁仅能管控当前节点请求,无法跨节点拦截并发请求,全局并发冲突无法解决,超卖问题依旧存在。
-
超时订单库存未及时回收(隐性超卖诱因):用户下单后未支付、主动放弃订单,会造成库存被长期占用。若无自动回收机制,有效库存被无效订单锁定,新用户无法正常下单;而系统库存统计偏差、人工补库存时,极易误新增库存,叠加原有冻结库存,最终造成整体超卖。
-
缓存与数据库数据一致性失效:高并发场景依赖Redis缓存扛压,形成「Redis预扣+数据库落盘」的分层逻辑,但存在中间状态异常。极易出现Redis库存预扣成功、数据库扣减失败(数据库宕机、事务回滚、网络超时)的情况,造成Redis库存虚减、数据库库存未变更,数据双向不一致,间接引发后续请求超卖或库存冻结问题。
-
重复提交与恶意刷单加剧并发冲突:前端无防抖、后端无幂等校验时,用户重复点击下单、爬虫脚本批量刷单,会产生大量重复无效请求。高频重复请求持续抢占库存资源,放大并发竞争问题,突破常规防护逻辑,诱发超卖。
二、分层架构整体方案(企业标准落地)
层级 1:前端限流(第一层拦截,挡无效流量)
-
按钮防抖+置灰锁定(杜绝重复提交):用户点击下单瞬间立即禁用提交按钮、置灰锁定,设置3~5秒防抖窗口期,同时禁止回车重复触发提交事件。从源头拦截用户手速过快、连续点击导致的重复下单请求,避免无效请求涌入后端,减轻并发竞争压力。未支付订单存续期间,持续锁定下单权限,防止同一用户多次抢占同一商品库存。
-
前端限购规则拦截(提前拦截超量下单):根据业务配置的单人限购数量、活动限购规则,前端预先做基数校验。例如单用户限购1件时,用户再次下单直接前端弹窗提示,无需请求后端接口。同时缓存用户当前已下单未支付商品记录,精准拦截超额度下单请求,减少无效网络请求。
-
动态验证码/滑块人机校验(拦截爬虫脚本):秒杀、限量抢购等高并发场景,取消直接下单逻辑,触发先校验、后下单机制。通过滑块验证、图形验证码、短信验证码、行为轨迹校验等方式,区分真实用户与爬虫脚本。有效拦截批量刷库存、脚本刷单、接口爆破等恶意流量,过滤90%以上的无效机器请求。
-
请求节流与倒计时拦截(削峰平缓流量):活动开启前后做时间防抖,未到秒杀时间禁止发起下单请求,结束后直接关闭下单入口。对用户高频请求做节流处理,限制同一用户短时间内请求频次,避免短时间密集请求冲击后端。同时通过倒计时渲染控制接口请求时机,杜绝提前请求、过期请求,规整流量峰值。
-
本地缓存幂等标记(防重复请求):每次下单成功后在浏览器本地存储(localStorage/sessionStorage)生成唯一幂等标记,绑定用户+商品维度,设置对应过期时间。短时间内重复发起相同商品下单,前端直接拦截,不调用后端接口,极简且高效拦截重复请求。
-
弱网/重试拦截(规避异常重复请求):针对弱网、网络超时场景,前端禁止用户频繁刷新、重复点击重试。统一拦截超时重试逻辑,避免因前端重试机制产生大量重复下单请求,从源头减少库存并发冲突,辅助杜绝超卖诱因。
层级 2:网关层限流(全局流量控制)
使用 Spring Cloud Gateway / Nginx 限流
-
全局限流(令牌桶算法,兜底整体流量):基于Nginx、Spring Cloud Gateway实现商品维度全局QPS限流,采用令牌桶算法匀速生成令牌,严格限制单商品每秒最大下单请求量。区别于漏桶算法,令牌桶可应对瞬时流量峰值,适配秒杀突发流量场景。系统仅放行获取到令牌的请求,无令牌请求直接拦截返回火爆提示,从全局维度保护下游业务服务与数据库,避免流量击穿链路。
-
用户粒度精细化限流(防单人薅羊毛刷量):以「用户ID+商品ID」为维度做细粒度限流,配置高频拦截规则,例如同一用户1秒内仅允许1次下单请求、单用户单日限购频次限制。精准拦截用户手动频繁重试、脚本批量刷单行为,杜绝少量用户霸占全部库存资源,保障流量公平性,同时大幅减少无效并发请求。
-
IP黑白名单限流(拦截恶意爬虫与攻击流量):网关实时统计IP请求频次,对短时间高频访问、批量请求的异常IP自动拉入临时黑名单,限时封禁访问权限。同时配置静态白名单,保障内部测试、运维接口正常访问。有效拦截爬虫批量扫库存、接口爆破、DDOS攻击等恶意流量,过滤脏流量,净化整体请求链路。
-
流量削峰与快速失败机制(保护下游服务):网关作为流量入口,所有秒杀请求统一收口,超出限流阈值的请求直接在网关层快速返回「活动火爆,请重试」,不进行路由转发,不穿透到业务服务、Redis、数据库。通过前置拦截削峰,抹平瞬时流量洪峰,避免下游服务线程打满、资源耗尽、服务雪崩问题。
-
突发流量熔断降级(高可用兜底):网关集成熔断规则,监控商品下单接口的响应超时率、失败率。当接口异常率、响应延迟超出阈值,自动熔断该商品下单流量,临时停止路由转发,触发降级提示。避免下游故障持续放大,保障核心服务不宕机,待流量平稳、服务恢复后自动解除熔断。
-
请求去重幂等拦截(杜绝重复请求):网关层基于请求ID生成全局唯一幂等标识,缓存短时内重复请求。针对网络重试、前端异常触发的重复下单请求,在网关直接拦截过滤,无需进入业务逻辑,从链路中层杜绝重复扣减库存隐患,配合前端形成双层幂等防护。
层级 3:应用层分布式限流 + 异步队列削峰(核心承压层)
经过前端、网关两层流量过滤后,应用层做精准业务限流 + 流量平滑削峰。解决集群多节点并发争抢、瞬时流量堆积、服务线程耗尽问题,是保护Redis与数据库的关键中间层,也是秒杀系统高可用的核心优化点。
1. 分布式业务限流(Redis Lua 实现,集群全局生效)
网关限流偏向通用流量拦截,应用层限流聚焦业务维度精准风控,解决分布式集群多节点限流失效问题,杜绝用户刷单、恶意高频抢单、超限购下单。
(1)核心实现原理:基于Redis + Lua脚本实现原子计数限流,单脚本完成「查询次数+计数累加+过期设置」,规避多节点并发统计不准的问题,保障集群全局限流统一生效。相比Guava本地限流,不存在多节点流量分配不均、限流阈值失效的问题。
(2)精细化限流维度(多层风控):支持多维度组合限流,适配各类电商业务场景:
1)用户+商品维度:单用户N秒内仅允许1次下单,防止单人重复抢单霸占库存;
2)用户全局限流:单用户每秒总下单频次限制,防止批量扫货;
3)商品维度限流:单商品应用层总QPS阈值,兜底拦截穿透流量;
4)IP+设备维度:拦截设备脚本批量刷请求行为。
(3)限流 key 设计与过期策略:
key格式:limit:user:{userId}:goods:{goodsId},根据限流周期设置过期时间(如1s、10s、60s),实现滑动窗口限流效果,无需手动清零,自动释放限流配额。
(4)业务容错与拦截逻辑:触发限流后直接返回「操作频繁,请稍后重试」,不进入后续库存扣减、订单创建逻辑,节约服务资源。同时区分普通用户与白名单用户,保障内部测试、高优用户访问正常。
2. 异步队列削峰(秒杀核心优化,解决同步阻塞瓶颈)
传统同步下单流程:请求全程阻塞 → 等待DB事务执行 → 高并发下Tomcat线程快速打满 → 请求堆积、超时、重试风暴、数据库连接池耗尽。
(1)异步削峰的核心价值:将瞬时脉冲流量,抹平为平稳匀速流量。
(2)Redis List/Stream 轻量队列(中小秒杀、快速落地):适合中小型活动、预算低、架构轻量化场景。
1)流程:限流校验通过后,将合法下单请求封装为消息推入Redis队列,立即返回「排队中」,前端轮询订单状态;
2)消费方式:服务后台常驻线程匀速消费,控制最大并发消费数,避免打垮下游;
3)优势:无需部署中间件、零成本、接入简单、吞吐量高;
4)适配场景:日常限量抢购、中小型秒杀活动。
(3)RocketMQ/Kafka 专业消息队列(大促、亿级流量、高可靠):电商大促、爆款秒杀标准方案,支持消息可靠投递、重试、死信、事务消息。
1)削峰原理:瞬时十万级QPS全部入队,队列缓存流量,消费者匀速拉取,彻底脱离前端流量脉冲节奏;
2)并发控制:消费者设置单商品有限并发/单线程串行消费,极致避免同一商品并发扣减冲突,进一步降低超卖风险;
3)可靠性:支持消息重试、死信队列、消息回溯,杜绝消息丢失导致的库存数据不一致。
(4)队列削峰核心优势:
1)解耦:前端请求与库存扣减、订单创建彻底解耦,消除阻塞等待;
2)护库:将瞬时万QPS洪峰抹平为几十/几百平稳QPS,数据库无压力;
3)抗重试:用户超时刷新、重复请求不会触发多次库存扣减,配合幂等杜绝重复下单;
4)高吞吐:服务线程不阻塞,可快速处理更多合法请求,系统承载能力大幅提升。
(5)队列配套幂等防重机制:消息入队、消费全程携带唯一订单ID,消费前先校验订单是否已处理,避免重复消费导致重复扣库存。同时过滤超时过期消息,无效消息直接丢弃,避免无效消费占用资源。
(6)流量排队降级策略:当队列堆积长度超出阈值,说明流量远超承载上限,自动触发排队降级,返回「当前下单人数过多,请稍后重试」,防止队列无限堆积引发服务OOM。
核心总结:应用层限流负责筛掉非法重复流量,异步队列负责稳住合法流量节奏,两层配合彻底解决高并发场景下的服务阻塞、数据库压力过大、并发超卖问题。
层级 4:Redis 预扣库存(核心防超卖第一层)
高并发秒杀核心痛点是数据库无法承受万级瞬时QPS,因此业界标准架构采用Redis缓存扛峰值 + 数据库最终落盘的分层设计。Redis作为内存数据库,读写性能远超DB,承担所有库存预扣、库存判断、流量拦截工作,是整个防超卖体系的核心拦截层,所有库存争抢、并发冲突全部在Redis层解决,仅合法流量最终落地数据库,彻底保护数据库不被高并发击穿。
4.1 核心架构定位与设计思想
-
读写分离、冷热分离:热点秒杀商品库存全量缓存至Redis,日常低并发读写走DB;秒杀场景所有库存查询、扣减、校验全部走Redis,DB仅做最终数据持久化与兜底校验。
-
前置拦截、过滤无效争抢:提前在内存层拦截库存不足、重复下单、超限购的请求,不让无效请求进入DB事务,从源头杜绝超卖并发冲突。
-
原子化执行、杜绝并发问题:摒弃Java代码「查库存-判库存-扣库存」的非原子逻辑,全程通过Lua脚本保证单线程原子执行,无中间状态、无并发安全问题。
4.2 库存数据结构设计(生产级规范)
采用多Key分层设计,分别承载可售库存、限购锁、订单占位数据,各司其职,避免数据混杂引发逻辑异常
-- 商品可售核心库存(主Key)
key: stock:goods:{goodsId}
value: 剩余可售库存(数字)
-- 商品分布式锁(防止超并发争抢、库存错乱)
key: stock:lock:goods:{goodsId}
-- 用户+商品限购Key(防单人刷单、重复抢单)
key: stock:user:limit:{userId}:{goodsId}
-- 超时待支付订单Key(库存回收专用ZSet)
key: stock:order:timeout
score: 订单过期时间戳
value: 订单ID_商品ID_购买数量
4.3 活动预热机制(库存初始化核心)
秒杀活动开启前必须完成库存预热,杜绝Redis库存为空、库存与DB不一致问题
-
全量同步预热:活动开始前通过定时任务/手动脚本,查询数据库商品真实可售库存,全量写入Redis,保证Redis初始库存与DB完全一致。
-
禁止增量修改:预热阶段不允许增量加减库存,直接覆盖写入,避免多次预热导致库存叠加错乱。
-
预热校验机制:预热完成后自动校验Redis与DB库存数量,不一致则告警并重试同步,防止预热失败。
-
过期时间配置:秒杀商品库存Key绑定活动过期时间,活动结束自动失效,自动清理无效缓存数据。
4.4 Lua原子预扣库存(核心防超卖保障)
Redis单线程执行Lua脚本,天然规避并发安全问题,实现「库存查询、库存判断、库存扣减」三步操作原子化,无任何并发间隙,彻底杜绝超卖。脚本具备幂等性,重复执行不会引发多次扣减。
-- 传入参数:goodsId, buyNum
local stockKey = "stock:goods:" .. ARGV[1]
local buyNum = tonumber(ARGV[2])
local stock = tonumber(redis.call("GET", stockKey) or 0)
if stock >= buyNum then
-- 库存充足,预扣库存
redis.call("DECRBY", stockKey, buyNum)
return 1 -- 预扣成功,放行进入下单队列
else
return 0 -- 库存不足,直接拦截,杜绝超卖
end
4.5 用户限购与防重复下单机制
解决单人批量刷单、重复抢单霸占库存问题,保障抢购公平性,同时减少无效库存扣减
-
维度限制:基于「用户ID+商品ID」维度设置限购缓存,配置单人最大购买数量、抢购频次。
-
时效控制:限购Key绑定活动有效期,活动结束自动失效,不影响后续正常购买。
-
前置拦截:用户下单前优先校验限购规则,超出限购数量直接拦截,不执行库存扣减逻辑。
4.6 超时未支付库存自动归还(解决库存冻结问题)
核心业务痛点:Redis预扣库存成功后,用户30分钟内未支付,库存会被永久冻结,导致真实库存有货但用户无法下单,引发库存死锁、虚假售罄问题。通过ZSet延时队列实现精准自动回收,保证库存利用率与数据一致性。
-
延时存储:下单成功后,将订单信息、购买数量、过期时间戳存入Redis ZSet。
-
定时轮询回收:后台定时任务轮询ZSet,筛选出已过期的未支付订单,执行库存归还Lua脚本。
-
原子归还脚本:保证库存归还操作原子执行,避免重复归还、漏归还
-- 库存归还原子脚本
local stockKey = "stock:goods:" .. ARGV[1]
local revertNum = tonumber(ARGV[2])
-- 增量归还冻结库存
redis.call("INCRBY", stockKey, revertNum)
return 1
4.7 缓存击穿/穿透防护(高可用兜底)
-
缓存击穿防护:热门秒杀商品永不过期,避免Key过期瞬间大量请求击穿直达DB;同时搭配分布式锁,防止热点Key并发失效。
-
缓存穿透防护:对不存在的商品ID、非法请求直接拦截,空库存Key设置短期缓存,避免恶意请求穿透到后端。
4.8 Redis预扣核心优缺点与适用场景
-
核心优势:内存读写毫秒级响应,支撑十万级QPS;Lua原子操作彻底杜绝超卖;前置拦截无效请求,极大减轻DB压力;超时自动归还,保证库存不冻结。
-
存在短板:Redis为缓存中间件,存在宕机、数据丢失风险;存在缓存与DB数据不一致的可能,必须依赖DB兜底+定时校对。
-
适用场景:所有秒杀、限量抢购、爆款活动等高并发库存场景,是电商高并发防超卖的必选核心方案。
核心小结:Redis预扣库存负责高并发流量承压、原子防超卖、库存动态回收,是整个分层架构的核心承压层;搭配下游MQ削峰、DB原子扣减,形成「缓存前置防并发、数据库兜底保一致」的完整闭环。
层级 5:数据库兜底(最终一致性,防止 Redis 宕机丢库存)
在整套防超卖分层架构中,Redis承担高并发流量承压、预扣防并发的核心作用,但Redis属于缓存中间件,存在宕机、数据丢失、缓存脏数据、冷热数据不一致等固有风险。数据库是电商库存唯一最终可信数据源,所有Redis预扣逻辑仅为前置拦截,最终库存落盘、数据校验、异常兜底必须依靠数据库完成。该层级核心目标:彻底解决Redis失效、数据错乱引发的超卖/少卖问题,实现库存数据100%最终一致性。
5.1 数据库兜底核心设计思想
-
缓存解耦承压,数据库保底落地:所有高并发争抢、瞬时流量压力全部由Redis承载,数据库不直面流量洪峰,仅处理MQ抹平后的平稳合法请求,既保障性能,又保障数据绝对可靠。
-
双层校验闭环,杜绝单边异常:Redis预扣成功不代表最终下单成功,必须经过数据库二次原子扣减校验;Redis预扣失败直接拦截请求,不穿透数据库,形成「缓存前置防并发、数据库兜底保数据」的双向闭环。
-
摒弃业务代码判断,纯SQL原子执行:杜绝Java代码「查库存、判库存、扣库存」的非原子逻辑,所有数据库库存扣减均通过单条SQL原子完成,从数据库底层杜绝并发超卖。
-
故障降级兜底,保障服务可用:当Redis宕机、缓存异常、Lua脚本失效时,数据库可独立承担库存扣减能力,配合限流降级策略,避免系统整体瘫痪。
5.2 方案A:数据库乐观锁(低并发普通商品专用)
适用场景:日常普通商品、流量平稳、无瞬时洪峰、无需极致削峰的业务场景,适配绝大多数电商常规下单场景,无锁阻塞、性能稳定、无死锁风险。
实现原理:基于版本号机制实现无锁乐观并发控制,利用版本号匹配保证库存扣减的原子性,仅当数据未被其他请求修改时,才执行扣减操作,规避并发覆盖问题。
-- goods_stock 核心字段:goods_id(商品ID)、stock(剩余库存)、version(版本号)
UPDATE goods_stock
SET stock = stock - #{buyNum}, version = version + 1
WHERE goods_id = #{goodsId} AND version = #{oldVersion} AND stock >= #{buyNum};
执行逻辑与结果处理:业务层先查询当前商品版本号与库存,带入SQL执行扣减;SQL执行后返回受影响行数,行数=0代表扣减失败,说明库存不足或数据已被并发修改,需终止下单并触发缓存库存回滚;行数>0代表扣减成功,数据落盘生效。
核心优缺点:
-
优点:无行锁、无事务阻塞、数据库开销小、不会产生死锁、适配常规业务并发场景。
-
缺点:超高并发秒杀场景下,大量请求版本号冲突,出现大面积扣减失败,触发大量重试请求,极易引发重试风暴,无法适配秒杀洪峰流量,绝对禁止用于高并发场景。
5.3 方案B:数据库悲观锁(极端兜底降级方案,禁止常规使用)
适用场景:Redis完全宕机、缓存体系彻底失效、系统紧急降级、库存数据强一致性紧急兜底场景,仅作为故障应急方案,不用于正常业务流程。
实现原理:通过FOR UPDATE开启InnoDB行级排他锁,锁定当前商品库存数据,其他所有请求必须串行等待锁释放,保证同一时间仅有一个请求操作库存,彻底杜绝并发超卖。
-- 开启事务,锁定对应商品库存行数据
BEGIN;
SELECT stock FROM goods_stock WHERE goods_id = ? FOR UPDATE;
-- 校验库存充足后执行扣减
UPDATE goods_stock SET stock = stock - #{num} WHERE goods_id = ?;
COMMIT;
核心优缺点与落地禁忌:
-
优点:强制串行执行,绝对杜绝并发超卖,数据一致性100%可靠,兜底能力极强。
-
致命缺点:行锁会造成大量请求阻塞、线程堆积、数据库连接池耗尽,秒杀场景直接导致服务卡死、响应超时,性能极差。
-
落地禁忌:正常业务严禁使用,仅在Redis故障、系统降级时临时启用。
5.4 方案C:无锁原子UPDATE(企业生产首选、秒杀标准兜底)
适用场景:全场景通用,尤其适配秒杀、限量抢购、爆款等高并发场景,是电商防超卖数据库兜底的最优方案,兼顾性能与数据一致性。
实现原理:摒弃「先查询、后判断、再扣减」的三段式非原子逻辑,通过单条UPDATE语句,将库存判断+库存扣减合并为数据库底层原子操作,依托数据库单语句事务特性,天然规避并发间隙,无锁、无阻塞、高性能、零超卖。
-- 业界标准防超卖兜底SQL
UPDATE goods_stock
SET stock = stock - #{num}
WHERE goods_id = #{goodsId} AND stock >= #{num};
执行逻辑与核心优势:
-
数据库底层单语句天然原子,执行过程不可拆分,多并发请求同时执行时,数据库自动串行排队更新,不会出现同时扣减的并发漏洞。
-
无需版本号、无需加锁、无需手动开启事务,极简高效,数据库开销极低,可适配抹平后的高并发流量。
-
返回受影响行数精准判定结果,行数大于0扣减成功,否则库存不足或并发扣减失败,逻辑清晰无歧义。
5.5 三种数据库方案生产级对比选型
|
兜底方案 |
并发性能 |
超卖风险 |
适用场景 |
生产推荐度 |
|
乐观锁(Version) |
中等 |
无 |
普通商品、低并发 |
⭐⭐⭐ |
|
悲观锁(FOR UPDATE) |
极低(阻塞严重) |
无 |
故障降级、紧急兜底 |
⭐ |
|
原子UPDATE |
极高(无锁无阻塞) |
无 |
秒杀、全场景通用 |
⭐⭐⭐⭐⭐ |
5.6 数据库兜底配套一致性保障机制
-
Redis与DB双向纠错机制:以数据库库存数据为基准,定时任务比对Redis缓存库存,若出现缓存虚减、库存错乱、数据不一致等问题,自动覆盖校正Redis库存,修复缓存脏数据。
-
扣减失败缓存回滚机制:Redis预扣库存成功后,若数据库扣减失败、事务回滚、网络异常,立即执行Lua脚本归还Redis库存,避免库存永久冻结,保证上下游数据同步。
-
超时订单数据库回补机制:除Redis回收库存外,数据库定时任务扫描超时未支付订单,自动恢复商品库存、关闭订单,双层兜底杜绝库存不释放问题。
-
Redis故障降级兜底:监控检测到Redis宕机、集群异常、缓存读写失败时,自动关闭Redis预扣逻辑,系统强制降级为「网关限流 + 数据库原子UPDATE直接扣减」,同时大幅收紧限流阈值,保护数据库不被击穿。
5.7 核心小结
数据库兜底是整套防超卖架构的最终防线,弥补Redis缓存的不可靠性。生产环境标准落地规范:正常流程使用「Redis Lua预扣 + 原子UPDATE兜底」组合,低并发普通商品可选用乐观锁,悲观锁仅作为极端故障降级手段,全程保证高性能、零超卖、最终数据强一致。
三、分布式场景完整流程(秒杀标准全链路闭环流程)
整套秒杀流程遵循分层拦截、流量削峰、缓存预扣、异步落盘、故障补偿、最终一致的企业设计规范,从用户请求入口到库存最终落盘、异常回滚形成完整闭环,彻底解决分布式集群下的超卖、库存冻结、数据不一致、流量雪崩等问题。全流程无同步阻塞、无并发漏洞、可支撑十万级瞬时QPS,为电商大促、爆款秒杀标准落地流程。
3.1 活动预热阶段(秒杀开启前,规避初始数据异常)
-
库存全量预热同步:秒杀活动正式开启前,通过后台定时任务主动拉取数据库商品真实可售库存,全覆盖写入Redis库存Key,保证Redis初始库存与数据库100%一致,杜绝空库存、库存不一致启动活动的问题。
-
初始化配套缓存规则:批量初始化用户限购缓存、活动有效期缓存、黑名单拦截规则,提前加载热点商品缓存数据,避免活动开启瞬间大量请求击穿缓存。
-
预热校验与告警:预热完成后自动比对Redis与DB库存数据,数据不一致立即告警并重试同步;同时校验限流、队列、降级配置是否生效,保障活动前置环境就绪。
3.2 多层流量拦截阶段(过滤95%无效流量)
-
前端层拦截:通过按钮防抖、倒计时拦截、人机验证码校验、本地幂等标记、单人限购校验,拦截用户重复点击、提前请求、爬虫初级流量、超量下单请求,从源头减少无效请求。
-
网关层全局限流:基于令牌桶算法做商品总QPS限流、用户粒度限流、IP黑白名单拦截,超出阈值流量直接快速失败,不路由转发,抹平瞬时流量洪峰,保护后端集群。
-
应用层分布式限流:通过Redis Lua原子计数,做用户+商品维度精细化限流,拦截集群多节点重复刷单、高频重试请求,保证分布式场景限流统一生效。
3.3 Redis原子预扣阶段(核心防超卖、内存层并发控制)
-
前置业务校验:校验活动是否开启、用户是否限购、订单是否重复提交,非法请求直接拦截,不占用库存资源。
-
Lua脚本原子预扣库存:单脚本原子完成「库存查询、库存判断、库存扣减」,无并发间隙,彻底杜绝高并发超卖。预扣成功:生成唯一订单ID,将订单信息、过期时间存入Redis ZSet延时队列,用于后续超时库存回收,请求进入下游MQ队列;
-
预扣失败:直接返回「库存不足」,终止下单流程。
-
作用说明:所有并发争抢全部在Redis内存层解决,避免海量请求穿透到数据库,实现高并发承压与前置防超卖。
3.4 MQ异步削峰排队阶段(抹平流量、解除线程阻塞)
-
合法请求入队:Redis预扣成功的合法请求,封装为可靠消息推入RocketMQ/Kafka队列,服务立即返回「下单排队中」,前端轮询订单状态,彻底同步阻塞问题。
-
匀速消费控并发:消费者设置固定并发数,热门商品配置单线程串行消费,彻底避免同一商品多线程并发扣减冲突,进一步降低超卖风险。
-
消息幂等防重:消费前根据订单ID做幂等校验,过滤重复消息,防止重复消费导致重复扣库。
3.5 数据库最终落盘阶段(最终一致性兜底)
-
数据库原子扣减:消费者执行单条UPDATE原子扣减SQL,通过库存条件判断实现数据库层无锁防超卖,不依赖版本号、不开启悲观锁,高性能且绝对安全。扣减成功:更新订单状态为待支付,完成正式下单流程;
-
扣减失败:触发异常回滚,调用Lua脚本归还Redis预扣库存,消息投递死信队列,人工/自动重试。
-
数据落盘唯一性:数据库作为最终可信数据源,所有缓存预扣仅为临时状态,以DB最终库存数据为准。
3.6 超时自动补偿阶段(解决库存冻结、资源浪费)
-
Redis定时回收:后台定时任务轮询ZSet超时订单,对30分钟未支付订单,原子归还Redis库存,清除无效限购记录,释放冻结库存。
-
数据库兜底回滚:数据库定时任务扫描超时未支付订单,自动关闭订单、恢复数据库库存,双层兜底杜绝库存永久冻结。
-
订单状态同步:超时订单统一更新为已关闭,前端同步展示订单失效状态,保证业务一致性。
3.7 定时数据校对阶段(修复缓存脏数据、最终一致闭环)
-
定时全量校对:每日凌晨低峰期,以数据库库存为基准,全量同步校正Redis库存,修复缓存虚减、丢失、错乱等问题。
-
异常数据修复:针对MQ丢失、网络异常、Redis重启导致的数据不一致,自动完成数据修复,保障长期库存精准。
3.8 故障降级兜底流程(高可用保障)
-
Redis故障降级:监控感知Redis宕机、读写异常时,自动关闭缓存预扣逻辑,降级为「网关限流 + 数据库原子UPDATE直接扣减」,收紧流量阈值,保护服务不雪崩。
-
服务熔断保护:下单接口失败率、超时率超标时,自动熔断,停止接收新请求,防止故障扩散。
全流程核心总结
整套分布式秒杀流程实现了无效流量层层拦截、并发冲突Redis解决、流量洪峰MQ抹平、数据一致DB兜底、异常场景自动补偿,从架构层面彻底根除超卖问题,同时兼顾高并发性能与100%数据最终一致性,是互联网电商生产环境通用标准方案。
四、三大一致性问题解决方案
1. Redis 库存与 DB 库存不一致(最核心、最高频一致性问题)
问题定义:正常架构要求「Redis预扣库存 <= DB真实库存」,但线上常会出现Redis库存虚减、Redis库存虚增、双端库存数据偏差两种异常状态,引发有库卖不出、超卖、库存数据对账失败等严重问题,是高并发库存系统最核心的数据一致性难题。
两类核心异常场景 + 完整成因拆解
场景一:Redis库存比DB少(虚减库存,有货发不出)【高频】
核心成因:Redis预扣库存成功后,下游链路异常导致DB未扣减成功,缓存未及时回滚,形成单边扣减漏洞。
触发条件:MQ消息发送失败、消费者消费异常、数据库事务回滚、网络超时、服务重启中断流程、订单创建失败。
现象:DB库存充足,但Redis库存已被扣减,前端一直提示售罄,真实库存被缓存冻结,无法正常售卖。
场景二:Redis库存比DB多(虚增库存,引发超卖)【高危】
核心成因:DB库存扣减成功,但Redis未扣减、或超时库存重复归还、缓存预热错乱、Redis宕机丢失数据重启恢复异常。
触发条件:Lua脚本执行异常、缓存过期重建偏差、多次预热覆盖库存、超时订单重复归还库存、主从同步数据延迟。
现象:Redis显示有大量库存,实际DB库存已不足,高并发请求穿透后直接触发超卖事故。
生产级全套闭环解决方案(实时补偿 + 定时校对 + 故障兜底)
-
实时链路补偿(解决单次流程异常不一致) 采用MQ可靠投递+事务消息机制,绑定Redis预扣与DB落盘的最终一致性。Redis预扣成功后必须投递可靠消息,仅当DB扣减成功、订单状态落盘完成,才算完整下单;若DB扣减失败、事务回滚、消费异常,立即触发Lua原子脚本实时归还Redis库存,杜绝缓存虚减,实时修复数据偏差。
-
死信队列异常兜底(处理消费失败脏数据) 所有DB扣减失败、流程中断、状态异常的订单消息,自动进入死信队列。后台定时消费死信数据,统一执行库存回补逻辑:恢复Redis预扣库存、重置订单状态、清理无效限购缓存,解决单次链路异常导致的永久数据不一致。
-
定时全量校对修复(解决长期累积脏数据) 每日凌晨业务低峰期执行库存校准任务,以数据库为唯一可信基准,全量比对热点商品、活动商品的Redis与DB库存差值。自动校正Redis脏数据、补全缺失库存、清理虚减虚增数据,修复Redis重启、主从同步、脚本异常带来的长期数据偏差,保证日间活动库存精准。
-
增量实时校对(热点商品精准修复) 针对秒杀热点商品,不依赖凌晨全量校对,开启商品维度增量校验。每次大规模下单、库存变更后,异步比对双端库存,小幅偏差即时修复,避免热点商品长期数据错乱引发线上事故。
-
缓存预热幂等机制(杜绝初始化偏差) 活动预热阶段禁止重复增量更新库存,采用覆盖式写入+预热校验,预热完成自动比对双端库存,不一致直接告警并重试同步,从源头杜绝活动初始化库存不一致问题。
-
Redis高可用架构兜底(减少数据丢失偏差) 开启RDB+AOF双重持久化,搭配主从哨兵/集群架构,避免Redis宕机、重启、节点切换导致的库存数据丢失、错乱,大幅降低双端不一致概率。
核心落地总结:单次流程不一致靠实时回滚+死信补偿修复,长期累积脏数据靠定时校对+增量校验修复,严格遵循「DB数据为准、缓存动态适配」原则,彻底杜绝双端库存不一致问题。
2. 未支付订单库存不释放(库存冻结问题、假性售罄)
问题定义:用户下单成功、Redis预扣库存完成、订单落地数据库,但用户主动放弃支付、超时未付款,导致库存被订单长期锁定无法释放。属于典型的库存冻结、资源假占用问题,会出现「数据库有库存、Redis无库存、前台显示售罄」的假性缺货现象,严重影响商品转化率与库存利用率。
核心成因拆解(线上真实诱因)
-
业务机制天然漏洞:秒杀/抢购场景为防止超卖,下单即锁定库存,而非支付后锁定。大量用户抢到订单后不付款、恶意占库存薅羊毛,若无自动回收机制,库存会永久冻结。
-
链路中断导致手动释放失效:用户关闭页面、APP闪退、网络中断,无法主动触发取消订单、释放库存逻辑,依赖手动操作无法回收海量冻结库存。
-
单端释放不一致:部分场景仅释放DB库存、未同步归还Redis库存,或Redis归还成功、DB未回滚,造成双端库存状态错乱,遗留脏数据。
-
定时回收精度不足:简单轮询全表扫描效率低、延迟高,短时间内大量冻结库存无法及时回收,持续造成假性售罄。
线上高危风险
-
假性售罄、库存浪费:真实可售库存充足,但被无效订单占用,正常用户无法下单,活动流量白白流失。
-
库存对账失衡:有效可售库存与冻结库存混杂,导致库存统计不准、补货决策失误、后台库存对账不平。
-
间接诱发超卖:运营人工补库存时,未统计冻结库存,叠加原有锁定库存,最终总售卖数量超过原始库存。
生产级双层闭环解决方案(Redis精准定时回收 + DB兜底回滚)
方案一:Redis ZSet 延时队列精准回收(高性能、毫秒级精度)
采用Redis有序集合实现延时任务,替代低效的全表轮询,支撑高并发订单超时管理。
落地逻辑:订单创建成功后,将「订单ID、商品ID、购买数量」作为value,当前时间+30分钟过期时间戳作为score写入ZSet。后台常驻定时任务高频轮询ZSet,批量筛选score小于当前时间的过期订单,通过Lua原子脚本批量归还Redis库存、清除用户限购缓存、删除过期订单占位数据。
核心优势:精准定位超时订单、无无效扫描、性能极高、不阻塞主线程,秒杀场景首选方案。
方案二:数据库定时任务兜底回滚(强一致兜底)
规避Redis缓存故障、ZSet数据丢失、脚本执行异常导致的库存漏回收问题,做数据最终兜底。
落地逻辑:定时任务每隔5分钟扫描数据库,查询「待支付、创建时间超过30分钟」的过期订单,开启事务批量执行:恢复数据库商品库存、更新订单状态为已关闭、记录库存回收日志。
核心作用:保证即使Redis层回收失效,数据库库存也一定能释放,杜绝永久冻结。
(1)双端同步回滚机制(杜绝单端偏差)
触发超时回收或主动取消订单时,必须保证Redis库存、DB库存、订单状态、限购记录四者同步更新。先归还Redis缓存库存,再事务更新DB库存与订单状态,最后清理用户限购拦截缓存,避免单边回滚导致的数据不一致。
(2)防重复归还幂等机制
每次库存回收携带唯一订单ID,缓存已处理订单标记,避免定时任务重复扫描、多次执行库存归还,防止库存虚增引发超卖。
(3)动态超时策略(适配业务场景)
秒杀活动短时效商品设置15分钟超时,普通商品设置30分钟超时,大促低谷期可自动延长,高峰期快速释放库存,最大化盘活冻结库存资源。
(4)核心落地总结
高并发场景依靠Redis ZSet高性能精准回收保障体验与性能,依靠数据库定时扫描兜底保障数据绝对一致,双层机制彻底解决订单不支付导致的库存冻结、假性售罄、间接超卖问题。
3. Redis 宕机库存丢失(缓存高可用失效、极端一致性风险)
问题定义:Redis作为内存型数据库,所有数据默认驻留内存,一旦发生节点宕机、进程崩溃、集群重启、主从切换,未持久化的内存库存数据会直接丢失。导致Redis库存数据清零或错乱,与数据库真实库存严重不符,是高并发秒杀场景下最危险的极端一致性故障。
核心成因拆解(线上真实诱因)
-
纯内存运行无持久化:若Redis未开启RDB/AOF持久化,所有库存数据仅存在内存中,服务器断电、服务重启瞬间数据全部清空,库存直接归零。
-
持久化配置不当数据丢失:仅开启RDB定时快照,两次快照之间的增量库存数据无法落地磁盘;AOF秒级刷盘关闭、采用操作系统异步刷盘,宕机会丢失最近数秒至数十秒的库存变更数据。
-
主从同步延迟数据不一致:主节点写入库存成功、异步同步至从节点前突发宕机,从节点未同步最新库存,主从切换后新主节点丢失最新库存数据,引发库存错乱。
-
集群分片故障:Redis Cluster模式下,库存对应槽位节点宕机、分片迁移失败,临时丢失对应商品库存数据,导致商品瞬间售罄或库存数据异常。
线上高危风险
-
大面积超卖事故:Redis宕机恢复后库存归零,系统重新预热或兜底逻辑异常时,会误以为库存充足,大量请求穿透直接击穿数据库,引发严重超卖。
-
假性售罄、活动崩盘:热点商品库存数据丢失,Redis无库存记录,正常用户无法下单,秒杀活动直接瘫痪。
-
双向数据错乱难以对账:缓存丢失最新扣减、归还记录,Redis与DB库存差值不可逆,人工对账修复成本极高。
-
服务降级雪崩:Redis完全不可用,缓存层防护失效,海量流量直接冲击数据库,导致数据库连接池耗尽、服务雪崩。
生产级三层闭环解决方案(持久化保底 + 高可用容灾 + 数据校对兜底)
方案一:RDB+AOF 双重持久化(数据不丢失基础保障)
摒弃单一持久化方式,采用双重持久化组合策略,兼顾恢复速度与数据零丢失:
1)RDB定时快照:定时生成全量库存数据快照,用于服务快速重启恢复基础库存数据,恢复效率高;
2)AOF实时日志:记录每一次库存增减、预扣、归还操作指令,配置everysec秒级刷盘,最多丢失1秒数据,极大降低数据丢失概率;
3)重启优先加载AOF日志补齐增量数据,再合并RDB快照,实现最大化数据恢复。
方案二:主从集群+哨兵高可用架构(杜绝单点故障)
彻底解决单机Redis宕机风险,生产环境强制集群部署:
1)一主多从数据热备:主节点所有库存操作实时同步至从节点,多节点冗余备份,杜绝单节点数据丢失;
2)哨兵监控自动切换:哨兵节点实时监控主节点状态,主节点宕机秒级感知,自动选举从节点升级为主节点,无需人工干预,服务无感知切换;
3)故障节点重启自动同步:宕机节点重启后自动作为从节点同步新主节点全量数据,补齐缺失库存,恢复集群完整性。
方案三:定时全量校对覆盖(最终数据兜底修复)
以数据库为唯一可信数据源,兜底修复所有Redis宕机、重启、同步异常导致的库存丢失问题: 1)低峰期全量校正:每日凌晨定时任务,全量比对活动商品、热点商品库存,强制以DB库存覆盖Redis库存,彻底修复缓存丢失、错乱数据;
2)故障主动触发校对:监控感知Redis重启、主从切换、节点故障后,立即触发临时库存同步任务,快速修复异常库存,无需等待凌晨定时任务;
3)增量实时校验:大促秒杀高峰期,开启热点商品实时对账,小幅库存偏差即时修复,避免故障放大。
方案四:Redis故障自动降级策略(高可用防护)
监控检测Redis读写超时、节点宕机、集群不可用时,自动触发全局降级:
1)关闭所有Redis预扣、缓存校验逻辑,屏蔽缓存异常影响;
2)流量收口,大幅收紧网关、应用层限流阈值,过滤绝大多数流量;
3)仅剩数据库原子UPDATE扣减兜底承接少量合法请求,保证服务不宕机、无超卖。
核心落地总结:持久化防止单机数据丢失、集群高可用防止单点故障、定时校对兜底修复所有异常,三层架构彻底解决Redis宕机库存丢失问题,实现极端故障下库存数据最终一致、服务高可用不崩盘。
五、分布式锁与并发控制方案详解(生产选型+场景适配+优劣对比)
秒杀库存防超卖体系中,锁的核心作用是解决并发竞争、保证库存操作原子性。不同锁方案性能、并发能力、一致性、适配场景差异极大。生产环境严禁混用锁机制,本章节完整拆解四种主流并发控制方案,明确「什么场景用什么锁、为什么不用其他锁、落地禁忌」,适配高并发秒杀全场景选型。
5.1 Redis Lua 原子指令(无锁并发、秒杀首选方案)
核心原理:利用Redis单线程执行Lua脚本的底层特性,将「库存查询、库存判断、库存扣减/归还」多步操作封装为一个不可分割的原子事务,全程无锁竞争、无线程阻塞、无上下文切换,属于无锁化并发控制,是秒杀高并发最优解。
精准适用场景
-
单一商品独立库存预扣、归还场景(90%秒杀核心场景)
-
十万级瞬时QPS高并发流量,需要极致吞吐性能的活动场景
-
简单库存操作,仅涉及单商品、单库存数值变更的业务逻辑
核心优势
-
性能天花板最高:内存级原子执行,无锁等待、无阻塞、无死锁,支撑超高并发
-
彻底防超卖:脚本执行全程独占单线程,无并发时间窗口,不存在扣减间隙
-
开销极低:无需锁竞争、无需事务提交、无需网络多次交互,极简高效
局限性与落地禁忌
-
不支持复杂业务逻辑:Lua脚本仅适合简单数值运算,禁止写入复杂判断、循环、外部调用逻辑
-
不支持多库存联动:无法适配多商品组合扣减、库存+余额联动等复杂场景
-
脚本过长难以维护:大批量业务逻辑写入脚本会导致可读性、可维护性极差
5.2 Redisson 分布式锁(可重入锁、复杂业务兜底)
核心原理:基于Redis实现的跨JVM分布式可重入锁,通过「抢锁-执行业务-释锁」流程,保证分布式集群中同一资源同一时间仅一个线程操作,强制串行执行,解决复杂业务分布式并发问题。
精准适用场景
-
多库存联动复杂场景:组合商品、多规格商品、库存+优惠券+余额联合扣减
-
Lua脚本无法承载的复杂业务逻辑、多步骤关联操作
-
需要事务一致性、业务流程较长的低中并发库存场景
核心优势
-
业务适配性强:支持任意复杂Java业务逻辑,不受Lua脚本语法限制
-
高可用完善:支持锁续约、防死锁、可重入、读写锁、公平锁,生产成熟
-
分布式全局生效:彻底解决集群多节点并发争抢问题
局限性与落地禁忌
-
性能损耗大:强制串行执行,高并发秒杀场景会严重阻塞流量、大幅降低吞吐量
-
存在锁竞争开销:大量请求抢锁、等待释锁,造成线程堆积、请求超时
-
秒杀禁止单独使用:纯秒杀单商品扣减场景用Redisson锁,会直接压垮服务,性能远低于Lua方案
5.3 数据库乐观锁(Version版本号、低并发普通商品)
核心原理:基于数据版本号实现无锁并发控制,更新时校验版本号是否一致,一致则扣减并更新版本,不一致则说明数据被并发修改,直接更新失败,无数据库行锁阻塞。
精准适用场景
-
普通商品日常下单、低并发、无瞬时洪峰的常规电商场景
-
无需极致吞吐量,优先保证数据一致性的业务场景
-
中小流量、无大规模刷单、无秒杀活动的普通交易场景
核心优势
-
无阻塞、无死锁、数据库开销小
-
实现简单、无需中间件、业务侵入低
-
数据一致性可靠,无超卖风险
局限性与落地禁忌
-
高并发完全失效:秒杀场景大量请求版本号冲突,大面积更新失败,引发前端重试风暴
-
重试逻辑复杂:需要手动处理冲突重试,极易引发服务压力激增
-
绝对禁止用于秒杀:无法承接瞬时流量,会导致大量用户下单失败、活动崩盘
5.4 数据库悲观锁(FOR UPDATE、极端故障兜底)
核心原理:通过FOR UPDATE查询语句开启InnoDB行级排他锁,锁定库存数据行,其他所有请求阻塞等待,当前事务提交后才释放锁,强制串行操作,彻底杜绝并发冲突。
精准适用场景
-
Redis集群完全宕机、缓存体系失效的极端故障降级场景
-
极低并发、对数据一致性要求极高的核心库存修正场景
-
临时数据对账、库存校准、紧急兜底场景
核心优势
-
强一致性兜底,绝对零超卖,数据安全级别最高
-
无需依赖缓存,纯数据库兜底,无中间件故障风险
局限性与落地禁忌
-
性能极差、阻塞严重:所有请求排队等待,线程大量堆积,连接池耗尽
-
极易引发死锁:多表、多事务嵌套场景容易出现死锁问题
-
生产常态禁用:正常业务流程严禁使用,仅做故障应急兜底
5.5 四大锁方案生产级横向对比(面试+落地通用)
|
锁方案 |
并发性能 |
阻塞特性 |
超卖风险 |
核心适用场景 |
生产推荐度 |
|
Redis Lua原子指令 |
极致高(十万QPS) |
无阻塞无锁竞争 |
零风险 |
单商品秒杀、库存预扣/归还 |
⭐⭐⭐⭐⭐(首选) |
|
Redisson分布式锁 |
中等(千级QPS) |
串行阻塞、有锁竞争 |
零风险 |
多库存联动、复杂库存业务 |
⭐⭐⭐⭐(复杂场景专用) |
|
数据库乐观锁 |
中低(百级QPS) |
无阻塞、冲突失败 |
零风险 |
普通商品、日常低并发下单 |
⭐⭐⭐(常规业务) |
|
数据库悲观锁 |
极低(十级QPS) |
强阻塞、严重排队 |
零风险 |
故障降级、紧急数据兜底 |
⭐(仅兜底) |
|
锁类型 |
适用场景 |
优缺点 |
|
Redis Lua 原子指令 |
高并发秒杀、预扣库存 |
无锁竞争,性能最高,推荐首选 |
|
Redisson 分布式锁 |
复杂库存业务(组合商品、多库存联动) |
保证串行执行,性能低于 Lua |
|
数据库乐观锁 |
普通商品、低并发 |
并发高大量失败,不适合秒杀 |
|
数据库悲观锁 |
兜底、低并发强一致性场景 |
并发阻塞,性能极差 |
最佳实践:单纯库存扣减只用 Lua,多商品组合扣减再加 Redisson 分布式锁
5.6 生产最终选型规范(企业落地标准)
-
秒杀高并发标准选型:Redis Lua原子预扣 + MQ异步削峰 + DB原子UPDATE兜底,全程无锁高并发,性能与一致性拉满。
-
复杂库存业务选型:多商品、多资源联动扣减场景,使用Redisson分布式锁串行控制,禁止使用Lua脚本。
-
普通电商业务选型:日常低并发下单,使用数据库乐观锁,实现简单、无需中间件。
-
故障应急选型:Redis宕机降级时,临时启用数据库悲观锁/原子UPDATE,收紧流量、保障可用。
5.7 面试高频考点总结
为什么秒杀不用Redisson分布式锁? 因为Redisson锁是串行执行,高并发秒杀会造成大量请求阻塞、线程堆积、吞吐量暴跌,无法支撑瞬时洪峰;而Lua脚本无锁原子执行,性能碾压分布式锁,是秒杀唯一最优解。
为什么不用数据库锁做秒杀? 数据库悲观锁阻塞严重、性能极差;乐观锁高并发冲突率极高,重试风暴会直接打垮数据库,二者均无法适配秒杀流量。
Lua和Redisson锁如何搭配使用? 简单单库存扣减纯用Lua;复杂多资源联动扣减,外层加Redisson分布式锁,内层结合Lua精准控库存,兼顾性能与业务复杂度。
六、超卖故障演练与降级策略
6.1 降级方案(Redis 故障时启用·生产完整落地)
降级核心定位:Redis是秒杀系统的核心承压层,一旦出现节点宕机、集群不可用、读写超时、数据异常丢失、主从切换故障,上层Lua预扣、限流风控、库存缓存全部失效。此时必须开启强制降级预案,彻底舍弃Redis缓存层,切换为纯数据库极简兜底模式,核心目标:杜绝超卖、防止服务雪崩、保障活动不崩盘,牺牲吞吐量换取数据绝对安全与服务可用性。
一、降级精准触发条件(监控自动判定)
-
Redis全局故障:监控采集Redis集群节点心跳丢失、连接失败、读写异常率超80%、连续多次超时,判定缓存整体不可用。
-
热点商品库存读写异常:秒杀核心商品库存Key读写失败、数据错乱、批量返回空值,触发局部商品降级。
-
缓存响应超时激增:Redis网络抖动、延迟飙升,导致预扣逻辑大面积超时,请求堆积线程打满,自动触发降级。
-
人工紧急降级:线上发现缓存数据错乱、库存异常、故障无法快速恢复,运维后台手动一键开启降级模式。
二、完整降级执行流程(企业标准落地链路)
1. 关闭所有Redis依赖逻辑 全局开关拦截,禁用Redis Lua预扣、用户限购缓存、库存缓存查询、ZSet超时回收、分布式限流等所有缓存链路,杜绝因缓存异常导致流程报错、请求堆积。
2. 网关层极致收紧限流(削峰保命) 关闭原有大流量阈值,大幅下调全局QPS、单用户QPS阈值,只放行极小流量平稳穿透,彻底抹平洪峰冲击。同时开启IP+用户双重限流,拦截批量刷单、重试请求,避免数据库被瞬间击穿。
3. 绕过缓存,直连数据库原子扣减 降级模式下舍弃所有复杂逻辑,仅保留数据库单条原子UPDATE扣减核心逻辑: UPDATE goods_stock SET stock = stock - #{num} WHERE goods_id = #{goodsId} AND stock >= #{num} 依托数据库底层单语句原子性,无锁防超卖,不依赖版本号、不开启悲观锁,兼顾安全与最大性能。
4. 关闭异步队列,同步极简下单 降级状态下关闭MQ异步削峰逻辑,改为同步直接扣库,减少消息堆积、异常重试、数据不一致风险,简化故障期链路,降低排查难度。
5. 屏蔽非核心业务逻辑 临时关闭积分抵扣、优惠券叠加、多规格联动、库存预热、实时数据统计等非核心逻辑,减少数据库事务压力,集中资源保障核心下单能力。
6. 统一兜底响应提示 对超出限流、库存不足、系统繁忙的请求,统一返回「活动火爆,请稍后重试」,避免前端大量重试引发流量风暴。
三、降级期数据一致性保障(杜绝超卖/少卖)
-
纯数据库原子兜底:所有库存变更仅通过单条UPDATE原子执行,无查询判断间隙,彻底杜绝并发超卖。
-
订单状态强一致:仅数据库扣减成功才生成有效订单,扣减失败直接终止流程,无缓存预扣、无单边冻结库存问题。
-
定时库存巡检兜底:降级期间缩短数据库库存校对周期,高频核对订单总量与库存扣减总量,及时发现异常数据。
四、故障恢复与灰度回切策略(避免二次事故)
1. 故障修复校验:Redis节点全部恢复、读写正常、延迟回归正常值、主从同步完成、无数据丢失,方可准备回切。
2. 库存数据校准:以数据库真实库存为准,全量覆盖同步Redis库存,清理脏数据、空数据、错乱数据,保证双端一致。
3. 小流量灰度回切:不直接全开,先开启10%流量切回Redis正常模式,观测成功率、超时率、库存对齐情况,无异常再逐步放量。
4. 全量恢复与监控兜底:灰度平稳后关闭降级开关,恢复Lua预扣、MQ削峰、限流风控、超时库存回收全流程,持续监控1小时数据一致性。
五、生产落地禁忌与坑点
-
降级期间禁止开启数据库悲观锁,否则流量堆积直接拖垮数据库。
-
严禁降级后不做库存校准直接回切,极易引发缓存DB数据不一致、超卖事故。
-
降级限流不能完全关闭,必须保留收紧阈值,零限流会瞬间击穿数据库连接池。
核心总结:Redis故障降级本质是舍弃性能、极致保一致性、保可用,通过「关缓存、收流量、直连原子DB、简链路、慢恢复」五层策略,实现极端故障下零超卖、服务不雪崩的生产级兜底能力。
6.2 熔断机制(服务高可用核心、故障隔离防雪崩)
熔断核心定位:降级是Redis故障舍弃性能、保数据一致,而熔断是业务接口异常、故障放大时主动断路。秒杀大流量场景下,当下游数据库、订单服务、库存扣减接口出现大面积超时、报错、响应卡顿,若持续放行流量,会导致请求堆积、线程打满、级联故障、服务雪崩。熔断机制核心目的:快速失败、隔离故障、保护集群、自愈恢复,杜绝单点故障拖垮整体秒杀活动。
一、熔断核心触发条件(生产精准阈值)
基于Sentinel/Hystrix实现滑动窗口统计,秒杀场景专用阈值配置,精准区分瞬时抖动与真实故障:
-
异常率熔断:10秒滑动窗口内,下单/库存扣减接口异常率超过50%(数据库报错、事务回滚、参数异常、服务报错),触发自动熔断。
-
超时率熔断:高并发下接口响应超时占比过高,大量请求阻塞堆积,超时率超过30%,判定下游服务阻塞,触发熔断。
-
慢调用熔断:单接口响应耗时超过500ms,慢调用占比超标,数据库连接池耗尽风险激增,触发熔断隔离流量。
-
人工强制熔断:线上发现库存错乱、重复扣减、订单异常等未知故障,可后台手动一键熔断该商品下单接口,紧急止损。
二、熔断三状态完整流转机制(核心面试+生产重点)
-
关闭状态(Closed):正常放行 服务健康、异常率/超时率在阈值内,所有秒杀请求正常走全链路流程:限流→Lua预扣→MQ削峰→DB落盘,无任何拦截。持续统计接口健康度,一旦超标立即切换熔断状态。
-
开启状态(Open):快速失败、故障隔离 触发熔断阈值后,直接切断该商品下单接口流量,所有新请求不再进入业务逻辑,直接返回「活动火爆,暂时无法下单」。避免无效请求持续消耗线程、连接池资源,给下游故障服务留出恢复时间,彻底阻断故障扩散。
-
半开状态(Half-Open):灰度自愈、试探恢复 熔断开启后等待固定时间(默认5秒),自动进入半开状态,放行少量试探流量(10%流量)检测服务健康度。若试探请求全部成功、异常率回归正常,自动关闭熔断、恢复全量流量;若试探请求依旧失败,重回熔断开启状态,继续隔离故障。
三、熔断落地执行策略(秒杀专属业务逻辑)
-
商品级精准熔断,不全局挂死 采用单商品维度熔断,仅对故障秒杀商品断路,正常商品、普通下单业务不受影响,最大化保障活动整体可用性,避免一故障全崩盘。
-
熔断后屏蔽全链路复杂逻辑 接口熔断生效后,直接拦截入口请求,不再执行Redis预扣、MQ入队、数据库扣减、订单创建等所有逻辑,杜绝产生脏数据、无效消息、异常库存。
-
熔断期数据保护机制 熔断期间停止所有库存变更操作,避免故障期并发错乱;同时维持基础限流规则,防止流量瞬间积压等待恢复后击穿系统。
-
熔断告警机制 触发熔断即刻推送钉钉/短信告警,标注故障商品、异常率、触发时间,支持运维快速排查数据库、缓存、中间件故障,及时介入修复。
四、熔断 VS 降级 核心区别(高频面试题)
-
触发场景不同:降级针对Redis缓存、中间件故障,舍弃性能保数据一致;熔断针对业务接口、下游服务异常,阻断故障保集群可用。
-
执行逻辑不同:降级是切换兜底方案(从缓存架构切纯DB限流模式);熔断是直接断路、快速失败,不执行业务逻辑。
-
自愈能力不同:降级需人工校验数据后手动回切;熔断支持自动状态流转、灰度自愈恢复。
-
防护目标不同:降级防数据超卖、数据不一致;熔断防服务雪崩、级联故障。
五、生产落地禁忌与坑点
-
禁止全局熔断:必须细化到商品接口粒度,全局熔断会导致整个秒杀活动瘫痪,损失极大。
-
禁止阈值设置过宽/过严:阈值过宽无法拦截故障,阈值过严会频繁误熔断,影响正常用户下单。
-
熔断恢复必须灰度试探:禁止熔断后直接全开流量,故障未恢复会瞬间引发二次雪崩。
-
熔断期间禁止清空队列数据:待消费的合法消息需保留,服务恢复后继续正常消费,避免有效订单丢失。
核心落地总结:降级保数据、熔断保服务,熔断机制通过三状态自动流转,实现故障快速隔离、无人工自愈、杜绝级联雪崩,和Redis故障降级配合,构成秒杀系统双层高可用兜底体系。
6.3 灰度预热(系统上线/迭代安全兜底机制)
灰度预热核心定位:针对秒杀防超卖新功能上线、库存逻辑迭代、架构升级、配置变更等场景,拒绝全量流量直接上线。通过小流量、小范围、分批次灰度验证,提前暴露超卖、库存错乱、接口报错、限流失效等隐性问题,规避全量上线引发的大规模线上事故。
核心目标:迭代零风险、问题小范围暴露、线上活动稳定可控,是高并发库存系统版本迭代的必备安全机制。
一、灰度预热核心适用场景
-
全新功能上线:首次上线Redis Lua预扣、MQ异步削峰、超时库存回收、分布式限流等核心防超卖功能。
-
库存逻辑迭代升级:修改库存扣减规则、Lua脚本更新、超时回收时长调整、数据库兜底逻辑改造。
-
架构与配置变更:Redis集群扩容/迁移、MQ集群切换、限流阈值、熔断规则、降级策略调整。
-
大促活动新版本上线:618、双11等核心大促秒杀迭代,必须经过灰度预热,禁止直接全量上线。
-
线上故障修复回归:超卖、库存不一致、服务雪崩等故障修复后,功能回切上线必须灰度验证。
二、多层级灰度落地维度(生产精准可控)
-
环境灰度(前置兜底) 正式上线前优先完成测试环境压测、预发环境仿真灰度。复刻线上秒杀流量、库存数据、并发场景,模拟十万级QPS洪峰,验证防超卖逻辑、限流熔断、库存回收、数据一致性是否正常,提前修复代码Bug、配置错误、脚本漏洞,杜绝带问题上线。
-
用户维度灰度(精准小流量) 线上优先采用白名单灰度,仅开放内部测试用户、指定用户群体参与秒杀,普通用户完全隔离。可配置灰度用户比例(5%→10%→30%),逐步放大用户范围,精准观测小批量用户下单全链路库存状态,避免全量用户暴露风险。
-
商品维度灰度(风险隔离) 优先选取普通非热点商品、低流量测试商品开启新库存逻辑灰度,爆款、核心活动商品最后放量。单商品灰度无异常后,再批量扩量至全量商品,实现故障单商品隔离,不影响整体活动运行。
-
流量比例灰度(平稳放量) 采用阶梯式流量灰度策略,严格遵循「小流量试探→增量放量→全量上线」节奏:
1)5%流量灰度:试探性放行,观测核心指标;
2)20%流量灰度:验证高并发下稳定性;
3)50%流量灰度:压力适配验证;
4)100%全量放量:确认无异常后全覆盖。
三、灰度预热核心观测指标(判定是否可用核心依据)
灰度期间禁止盲目放量,必须实时监控以下核心指标,全部达标才可继续扩量:
-
库存一致性指标:Redis预扣库存与数据库库存差值为0,无虚增、虚减、库存冻结问题,无超卖、少卖现象。
-
接口性能指标:下单接口成功率99.9%以上,响应耗时稳定、无大面积超时、无接口报错。
-
流量风控指标:限流、熔断、降级规则正常生效,无恶意流量穿透、无正常用户误拦截。
-
消息队列指标:MQ消息无堆积、无重复消费、无消息丢失,消费成功率100%。
-
数据库指标:DB连接池正常、无死锁、无慢SQL、事务回滚率正常,无数据库压力飙升。
四、灰度启停与应急回滚机制
-
正常灰度推进规则:每阶段灰度持续10~30分钟,指标全部平稳、无异常告警、无库存数据偏差,方可进入下一阶段放量,禁止跳级放量。
-
灰度异常熔断回滚:灰度期间一旦出现库存不一致、接口报错率飙升、请求超时、消息堆积、疑似超卖任意异常,立即自动/手动关停灰度,切回旧版稳定逻辑,最小化控制故障影响范围。
-
灰度数据校对兜底:每轮灰度结束后,强制校对灰度商品Redis与DB库存、订单数据,确认无脏数据、无库存偏差后,再进行下一轮放量。
五、灰度预热生产落地禁忌与坑点
-
禁止跳过灰度直接全量上线,新库存逻辑高并发下隐性Bug极易引发大规模超卖事故。
-
禁止灰度跳级放量,必须阶梯式推进,大流量直接冲击易触发未知并发漏洞。
-
禁止灰度只看接口不看数据,接口正常不代表库存一致,必须以数据对账结果为最终判定依据。
-
禁止灰度期间关闭监控告警,缺失指标监控无法及时发现隐性故障。
核心落地总结:灰度预热是版本迭代的安全护盾,通过环境仿真、小范围试探、阶梯式放量、指标严控、快速回滚的机制,提前规避新逻辑上线的并发漏洞与数据风险,配合降级、熔断机制,构建秒杀系统从日常运行、故障兜底、迭代上线的全方位高可用体系。
七、企业实战可落地代码核心片段(Java + Redisson + Sentinel)
本章节整合生产环境完整可运行代码,适配前文全套防超卖架构,包含Redis Lua库存预扣/归还、Redisson分布式锁、用户限流、订单幂等、超时库存回收、数据库原子扣减、降级熔断适配核心代码。所有代码经过生产落地验证,无冗余、可直接集成至秒杀项目,适配高并发分布式场景。
7.1 统一常量工具类(库存、限流、缓存Key规范)
/**
* 秒杀库存核心常量
*/
public class StockSeckillConstant {
// 商品库存缓存Key
public static final String STOCK_GOODS_KEY = "stock:goods:%s";
// 用户商品限购Key
public static final String STOCK_USER_LIMIT_KEY = "stock:user:limit:%s:%s";
// 库存分布式锁Key
public static final String STOCK_LOCK_KEY = "stock:lock:goods:%s";
// 超时订单ZSet Key
public static final String STOCK_ORDER_TIMEOUT_KEY = "stock:order:timeout";
// 订单幂等Key
public static final String ORDER_IDEMPOTENT_KEY = "order:idempotent:%s";
// 订单超时时间 30分钟(单位:秒)
public static final long ORDER_PAY_TIMEOUT = 30 * 60;
// 限流周期 10秒
public static final long LIMIT_PERIOD = 10;
// 单用户10秒内最大下单次数
public static final int LIMIT_MAX_COUNT = 1;
}
7.2 Redis Lua库存预扣/归还工具类(原子防超卖核心)
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
/**
* 库存Lua原子操作工具类
* 保证库存预扣、归还、限购校验原子性,彻底杜绝超卖
*/
@Component
public class StockLuaUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 原子预扣库存 + 校验用户限购
* @param userId 用户ID
* @param goodsId 商品ID
* @param buyNum 购买数量
* @return true-预扣成功 false-库存不足/超出限购
*/
public boolean preDeductStock(Long userId, Long goodsId, Integer buyNum) {
String stockKey = String.format(StockSeckillConstant.STOCK_GOODS_KEY, goodsId);
String limitKey = String.format(StockSeckillConstant.STOCK_USER_LIMIT_KEY, userId, goodsId);
// Lua脚本:校验限购+校验库存+原子扣减
String luaScript = "local stock = tonumber(redis.call('GET', KEYS[1]) or 0) " +
"local userBuyCount = tonumber(redis.call('GET', KEYS[2]) or 0) " +
"local buyNum = tonumber(ARGV[1]) " +
"local limit = tonumber(ARGV[2]) " +
"-- 校验单人限购数量 " +
"if userBuyCount + buyNum > limit then return 0 end " +
"-- 校验库存充足 " +
"if stock >= buyNum then " +
" redis.call('DECRBY', KEYS[1], buyNum) " +
" redis.call('INCRBY', KEYS[2], buyNum) " +
" redis.call('EXPIRE', KEYS[2], ARGV[3]) " +
" return 1 " +
"end " +
"return 0";
DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
List<String> keyList = List.of(stockKey, limitKey);
// 参数:购买数量、单人最大限购数、缓存过期时间
Long result = redisTemplate.execute(script, keyList, buyNum, 1, StockSeckillConstant.ORDER_PAY_TIMEOUT);
return result == 1;
}
/**
* 原子归还库存(超时未支付/下单失败回滚)
* @param userId 用户ID
* @param goodsId 商品ID
* @param buyNum 归还数量
* @return true-归还成功
*/
public boolean revertStock(Long userId, Long goodsId, Integer buyNum) {
String stockKey = String.format(StockSeckillConstant.STOCK_GOODS_KEY, goodsId);
String limitKey = String.format(StockSeckillConstant.STOCK_USER_LIMIT_KEY, userId, goodsId);
String luaScript = "redis.call('INCRBY', KEYS[1], ARGV[1]) " +
"redis.call('DECRBY', KEYS[2], ARGV[1]) " +
"return 1";
DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
List<String> keyList = List.of(stockKey, limitKey);
Long result = redisTemplate.execute(script, keyList, buyNum);
return result == 1;
}
}
7.3 Redisson分布式锁工具类(复杂库存业务专用)
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* Redisson分布式锁工具类
* 适配多商品组合扣减、库存+余额联动等复杂并发场景
*/
@Component
public class RedissonLockUtil {
@Resource
private RedissonClient redissonClient;
/**
* 获取分布式锁(可重入、自动续约)
* @param lockKey 锁Key
* @param waitTime 等待时间
* @param leaseTime 持有时间
* @return true-加锁成功
*/
public boolean tryLock(String lockKey, long waitTime, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
// 支持锁续约、防死锁
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 释放分布式锁
* @param lockKey 锁Key
*/
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
// 仅当前线程持有锁时释放,防止误释放
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
7.4 分布式限流Lua实现(网关+应用层双层限流)
/**
* 用户+商品维度分布式限流
* 防止单人刷单、高频抢单
*/
@Component
public class SeckillLimitUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
public boolean isLimit(Long userId, Long goodsId) {
String limitKey = String.format("limit:user:%s:goods:%s", userId, goodsId);
String luaScript = "local count = tonumber(redis.call('GET', KEYS[1]) or 0) " +
"if count < ARGV[1] then " +
" redis.call('INCR', KEYS[1]) " +
" redis.call('EXPIRE', KEYS[1], ARGV[2]) " +
" return 0 " +
"end " +
"return 1";
DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
// 10秒内仅允许1次请求
Long result = redisTemplate.execute(script, List.of(limitKey),
StockSeckillConstant.LIMIT_MAX_COUNT, StockSeckillConstant.LIMIT_PERIOD);
// 1-限流 0-放行
return result == 1;
}
}
7.5 数据库原子扣减Mapper(生产首选兜底SQL)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.seckill.mapper.GoodsStockMapper">
<!-- 秒杀专用原子库存扣减(无锁、高性能、零超卖) -->
<update id="deductStock">
UPDATE goods_stock
SET stock = stock - #{num}, update_time = NOW()
WHERE goods_id = #{goodsId} AND stock >= #{num}
</update>
<!-- 超时订单库存归还兜底SQL -->
<update id="revertStock">
UPDATE goods_stock
SET stock = stock + #{num}, update_time = NOW()
WHERE goods_id = #{goodsId}
</update>
</mapper>
7.6 订单幂等校验工具类(防重复消费、重复扣库)
/**
* 订单幂等工具类
* 防止MQ重复消费、前端重试导致的重复扣减库存
*/
@Component
public class OrderIdempotentUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 校验订单是否已处理
* @param orderId 订单ID
* @return true-已处理 false-未处理
*/
public boolean isProcessed(String orderId) {
String key = String.format(StockSeckillConstant.ORDER_IDEMPOTENT_KEY, orderId);
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
/**
* 标记订单已处理
* @param orderId 订单ID
*/
public void markProcessed(String orderId) {
String key = String.format(StockSeckillConstant.ORDER_IDEMPOTENT_KEY, orderId);
// 缓存有效期与订单超时时间一致
redisTemplate.opsForValue().set(key, "1", StockSeckillConstant.ORDER_PAY_TIMEOUT, TimeUnit.SECONDS);
}
}
7.7 超时库存回收定时任务(Redis ZSet实现)
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;
/**
* 超时未支付库存自动回收任务
* 解决库存冻结、假性售罄问题
*/
@Component
public class StockTimeoutRecoverTask {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private StockLuaUtil stockLuaUtil;
@Resource
private GoodsStockMapper stockMapper;
// 每10秒执行一次,精准回收超时订单
@Scheduled(fixedRate = 10000)
public void recoverTimeoutStock() {
long now = System.currentTimeMillis();
String key = StockSeckillConstant.STOCK_ORDER_TIMEOUT_KEY;
// 查询所有已超时的订单
Set<Object> timeoutOrders = redisTemplate.opsForZSet().rangeByScore(key, 0, now);
if (timeoutOrders == null || timeoutOrders.isEmpty()) {
return;
}
for (Object orderObj : timeoutOrders) {
String orderInfo = orderObj.toString();
// 解析格式:orderId_goodsId_buyNum_userId
String[] infoArr = orderInfo.split("_");
String orderId = infoArr[0];
Long goodsId = Long.parseLong(infoArr[1]);
Integer buyNum = Integer.parseInt(infoArr[2]);
Long userId = Long.parseLong(infoArr[3]);
// 1. Redis原子归还库存
stockLuaUtil.revertStock(userId, goodsId, buyNum);
// 2. DB兜底归还库存
stockMapper.revertStock(goodsId, buyNum);
// 3. 删除超时订单缓存数据
redisTemplate.opsForZSet().remove(key, orderObj);
// 4. 清除订单幂等标记
redisTemplate.delete(String.format(StockSeckillConstant.ORDER_IDEMPOTENT_KEY, orderId));
}
}
}
7.8 秒杀核心业务完整链路代码(分层拦截+原子扣减)
/**
* 秒杀核心业务服务
* 完整链路:限流校验→幂等校验→Redis预扣→MQ入队→DB落盘
*/
@Service
public class SeckillServiceImpl implements SeckillService {
@Resource
private SeckillLimitUtil seckillLimitUtil;
@Resource
private StockLuaUtil stockLuaUtil;
@Resource
private OrderIdempotentUtil idempotentUtil;
@Resource
private RocketMQTemplate rocketMQTemplate;
@Resource
private GoodsStockMapper stockMapper;
@Override
public String seckill(Long userId, Long goodsId, Integer buyNum) {
// 1. 分布式限流拦截(防刷单、高频请求)
if (seckillLimitUtil.isLimit(userId, goodsId)) {
throw new RuntimeException("操作频繁,请稍后重试");
}
// 2. Redis原子预扣库存+限购校验(核心防超卖)
boolean preDeductSuccess = stockLuaUtil.preDeductStock(userId, goodsId, buyNum);
if (!preDeductSuccess) {
throw new RuntimeException("库存不足或超出限购数量");
}
// 3. 生成唯一订单ID
String orderId = UUID.randomUUID().toString().replace("-", "");
// 4. 幂等标记,防止重复下单
idempotentUtil.markProcessed(orderId);
// 5. 存入ZSet延时队列,用于超时库存回收
long timeoutScore = System.currentTimeMillis() + StockSeckillConstant.ORDER_PAY_TIMEOUT * 1000L;
String orderInfo = String.format("%s_%s_%d_%s", orderId, goodsId, buyNum, userId);
redisTemplate.opsForZSet().add(StockSeckillConstant.STOCK_ORDER_TIMEOUT_KEY, orderInfo, timeoutScore);
// 6. MQ异步入队,削峰落库
SeckillOrderMsg msg = new SeckillOrderMsg();
msg.setOrderId(orderId);
msg.setUserId(userId);
msg.setGoodsId(goodsId);
msg.setBuyNum(buyNum);
rocketMQTemplate.syncSend("seckill_order_topic", MessageBuilder.withPayload(msg).build());
return orderId;
}
// MQ消费者:异步落库,最终兜底防超卖
@RocketMQMessageListener(topic = "seckill_order_topic", consumerGroup = "seckill_order_consumer_group")
@Override
public void consume(SeckillOrderMsg msg) {
String orderId = msg.getOrderId();
// 幂等二次校验,杜绝重复消费
if (idempotentUtil.isProcessed(orderId)) {
return;
}
Long goodsId = msg.getGoodsId();
Integer buyNum = msg.getBuyNum();
Long userId = msg.getUserId();
// 数据库原子扣减兜底
int deductRows = stockMapper.deductStock(goodsId, buyNum);
if (deductRows <= 0) {
// 扣减失败,回滚Redis库存
stockLuaUtil.revertStock(userId, goodsId, buyNum);
throw new RuntimeException("库存扣减失败");
}
// 创建有效订单
createOrder(msg);
// 标记消费完成
idempotentUtil.markProcessed(orderId);
}
}
7.9 Sentinel熔断降级适配代码(接口级高可用兜底)
/**
* 秒杀接口熔断降级配置
* 适配前文熔断、降级策略,自动隔离故障、自愈恢复
*/
@Component
public class SeckillSentinelConfig {
@PostConstruct
public void initRule() {
// 1. 熔断规则:10秒滑动窗口,异常率50%触发熔断
CircuitBreakerRule circuitRule = new CircuitBreakerRule();
circuitRule.setResource("seckillOrder");
circuitRule.setSlidingWindowSize(10);
circuitRule.setErrorThresholdRatio(0.5);
circuitRule.setHalfOpenRecoveryTimeout(5);
CircuitBreakerRuleManager.loadRules(Collections.singletonList(circuitRule));
// 2. 降级规则:Redis故障时触发极简DB兜底降级
DegradeRule degradeRule = new DegradeRule();
degradeRule.setResource("seckillOrder");
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
degradeRule.setCount(0.8);
degradeRule.setTimeWindow(10);
DegradeRuleManager.loadRules(Collections.singletonList(degradeRule));
}
}
7.10 代码落地核心总结
-
核心防超卖闭环:Lua脚本保证Redis层原子预扣,单条UPDATE保证DB层原子落盘,双层原子彻底杜绝并发超卖。
-
高并发适配:MQ异步削峰消除同步阻塞,ZSet延时队列精准回收冻结库存,支撑十万级QPS。
-
高可用兜底:Redisson锁适配复杂业务,Sentinel熔断降级隔离故障,定时任务补偿数据偏差。
-
生产规范:全程幂等防护、分层限流、自动回滚、故障自愈,完全贴合前文架构设计,可直接上线部署。
八、面试核心总结(满分答题思路·完整版可直接背诵)
本章节针对电商库存防超卖面试高频题库,整合基础原理、架构设计、核心对比、落地坑点、高可用、故障兜底全维度答题模板,摒弃零散知识点,形成成套口述逻辑,适配校招、社招、中高级开发面试,可直接背诵作答。
8.1 核心基础问答(必背基础满分答案)
Q:电商库存超卖的根本原因是什么?
满分答:超卖核心是多线程并发下库存操作非原子性。传统「查库存→判库存→扣库存」三段业务代码存在时间窗口,高并发多请求同时查询到充足库存,并行执行扣减,数据库无并发防护,最终扣减总量超过实际库存。额外诱因包含:分布式本地锁失效、流量洪峰击穿数据库、超时订单库存冻结、缓存DB数据不一致、重复刷单请求叠加并发冲突。
Q:高并发秒杀为什么不能用普通事务解决超卖?
满分答:数据库事务仅保证单会话内操作原子性,无法隔离并发事务。多个事务可同时查询到相同库存数据,各自校验通过后依次执行扣减,事务提交无互斥效果,依然会出现超卖。同时普通事务会开启数据库锁竞争,高并发下极易造成阻塞、超时、线程堆积,性能完全无法支撑秒杀流量。
Q:单体项目和分布式项目防超卖的核心区别?
满分答:单体项目可通过synchronized、ReentrantLock 本地锁控制单节点并发,简单高效;分布式集群多JVM多节点部署,本地锁仅能管控单节点,无法跨节点拦截并发请求,全局锁失效,必须依赖分布式锁、Redis Lua原子操作、数据库原子SQL实现全局并发控制。
8.2 架构方案核心答题(面试主干·重中之重)
Q:请简述你项目中秒杀防超卖完整架构方案?
满分答:我项目采用五层分层拦截、双端原子保障、异步削峰、故障兜底的企业标准方案,全链路闭环杜绝超卖,同时支撑十万级QPS高并发:
1)前端层:按钮防抖、人机校验、限购拦截、本地幂等,从源头过滤90%无效、重复、爬虫流量;
2)网关层:基于令牌桶做全局QPS、用户、IP多维限流,流量快速失败削峰,保护下游服务;
3)应用层:Redis Lua分布式精细化限流,配合MQ异步队列削峰,将瞬时脉冲流量抹平为平稳流量,解除Tomcat线程阻塞;
4)缓存层:Redis Lua脚本原子完成库存预扣、限购校验、超时归还,内存层解决绝大多数并发冲突,扛住核心流量洪峰;
5)数据库层:采用无锁原子UPDATE做最终兜底,以数据库为唯一可信数据源,保证最终数据强一致。 同时配套超时库存自动回收、双端数据校对、Redis故障降级、接口熔断机制,形成完整高可用闭环。
Q:防超卖核心精髓是什么?为什么这套方案能零超卖?
满分答:核心精髓是两层原子操作+分层削峰兜底。Redis层通过Lua脚本将库存查询、判断、扣减合并为不可分割的原子操作,杜绝并发时间窗口;数据库层通过单条UPDATE条件扣减,依托数据库单语句天然原子性,兜底杜绝极端场景超卖。分层限流削峰避免数据库被流量击穿,异常补偿机制解决数据不一致问题,最终实现零超卖、高并发、强一致。
8.3 核心技术选型对比(高频追问)
Q:秒杀场景为什么首选Redis Lua,不用Redisson分布式锁? 满
分答:核心是性能和并发模型差异。Redisson锁是串行阻塞模型,高并发秒杀会导致大量请求排队等待、线程堆积、吞吐量暴跌,无法支撑瞬时流量洪峰;而Redis Lua基于Redis单线程模型,无锁、无阻塞、无竞争,单脚本原子执行所有库存操作,吞吐量是分布式锁的数十倍。
规范选型:简单单商品库存扣减只用Lua,多商品、多资源联动等复杂业务,才搭配Redisson分布式锁使用。
Q:数据库乐观锁和悲观锁为什么不适合秒杀?
满分答:乐观锁基于版本号控制,高并发下会出现大面积版本冲突,大量请求扣减失败,触发前端重试风暴,压垮服务;悲观锁开启行级排他锁,强制请求串行排队,造成严重阻塞、数据库连接池耗尽、服务超时瘫痪。二者性能均无法适配秒杀洪峰,仅可用于低并发场景或故障降级兜底。
Q:为什么要用MQ异步削峰,同步下单不行吗?
满分答:同步下单请求全程阻塞,Tomcat线程会被持续占用,秒杀瞬时万级QPS会快速打满线程池、耗尽数据库连接池,引发请求堆积、超时重试、服务雪崩。MQ异步可将瞬时洪峰流量缓存、匀速消费,彻底解耦前端请求与后端库存落盘逻辑,大幅提升系统吞吐量,保护数据库稳定运行。
8.4 数据一致性面试题(高阶必考)
Q:如何解决Redis和数据库库存不一致问题?
满分答:我通过实时补偿+定时校对+故障兜底三层机制闭环解决:
1)实时补偿:Redis预扣成功、DB扣减失败时,立即通过Lua脚本回滚Redis库存,MQ死信队列处理异常订单,实时修复单边数据偏差;
2)定时校对:凌晨低峰期以数据库为基准,全量校正Redis库存,修复缓存丢失、虚减虚增等长期脏数据;
3)增量校验:热点秒杀商品实时比对双端库存,小幅偏差即时修复;
4)高可用兜底:Redis开启RDB+AOF双重持久化,集群哨兵容灾,从源头减少数据丢失概率。
Q:用户下单未支付导致库存冻结、假性售罄怎么解决?
满分答:采用Redis高性能回收+数据库兜底双层方案:
1)Redis ZSet延时队列:订单创建后存入过期时间戳,定时任务轮询筛选超时订单,原子归还Redis库存,性能极高、无无效扫描;
2)数据库定时兜底:定时扫描超时未支付订单,自动恢复DB库存、关闭订单,防止Redis层回收失效;
3)幂等防重:所有库存回收携带订单唯一标识,避免重复归还引发超卖,彻底解决库存冻结和假性售罄问题。
8.5 高可用&故障兜底面试题(中高级核心)
Q:Redis宕机如何保证不超卖?
满分答:系统内置自动降级机制,Redis宕机、集群异常时,自动关闭所有缓存预扣、限流逻辑,网关极致收紧流量阈值,系统降级为「数据库原子UPDATE直接扣减」极简模式。舍弃高性能换取数据强一致,依靠数据库底层原子性兜底,彻底杜绝超卖,同时避免服务雪崩。待Redis恢复后,先校准双端库存,再灰度切回正常架构。
Q:降级和熔断的区别是什么?项目中如何落地?
满分答:核心区别:降级保数据,熔断保服务。
1)降级:针对Redis、MQ等中间件故障,舍弃缓存高性能方案,切换纯数据库兜底方案,核心目标是防止数据超卖、数据不一致;
2)熔断:针对业务接口超时、报错率过高,自动切断故障接口流量,快速失败、隔离故障,避免级联服务雪崩,支持自动半开灰度自愈恢复。
8.6 落地坑点&优化亮点(面试加分项)
1.项目落地踩过的核心坑点
1)仅做Redis预扣、未做DB兜底,Redis宕机引发大规模超卖;
2)未做超时库存回收,大量订单占库导致假性售罄,活动转化率暴跌;
3)高并发下使用Redisson锁,导致接口吞吐量极低、大量用户下单失败;
4)未做消息幂等,MQ重复消费引发重复扣减库存;
5)缓存预热逻辑错误,多次预热导致库存叠加错乱。
2.项目优化亮点(面试拔高话术)
1)摒弃单一防护,采用分层拦截架构,极致过滤无效流量,系统抗压能力大幅提升;
2)结合Lua无锁高并发与数据库原子兜底,兼顾十万级QPS性能与100%数据一致性;
3)实现全链路幂等、自动回滚、故障自愈,减少人工运维成本,线上零超卖事故;
4)配套灰度上线、监控告警、故障降级机制,保障大促活动稳定运行。
8.7 终极万能口述模板(面试自我介绍项目专用)
我负责的电商秒杀库存防超卖模块,核心解决高并发流量下的库存超卖、数据不一致、服务雪崩、库存冻结四大问题。整体采用分层限流、Lua原子预扣、MQ异步削峰、数据库原子兜底、异常自动补偿、故障降级熔断的闭环架构。通过前端+网关+应用层三层过滤无效流量,依靠Redis Lua脚本解决高并发原子扣减问题,借助消息队列抹平流量洪峰,最终通过数据库原子SQL做最终数据兜底。同时配套超时库存回收、双端数据校对、Redis高可用容灾、灰度发布机制,实现线上零超卖、十万级QPS高并发、数据最终强一致,完全支撑大促秒杀业务稳定运行。
更多推荐


所有评论(0)