大促秒杀系统优化复盘:高并发架构与Redis库存扣减实战

发布时间:2026/10/11 17:51:44
大促秒杀系统优化复盘:高并发架构与Redis库存扣减实战
前几天刚面完一家内容社区平台的Java后端岗趁着记忆还热乎我把这场面试从头到尾复盘了一遍。结果是好的拿到了超出预期的offer但真正帮我拿下这个结果的不是临时刷了多少题而是简历上那段大促秒杀优化经历。面试官几乎整个二面都绕着它追问方案设计、压测数据、Redis挂掉怎么办、消息丢了怎么恢复一环扣一环。这篇复盘把我的完整准备过程、技术方案和踩坑记录都写出来希望能给准备面高并发相关岗位的朋友一点参考。先说明一下背景我面的是Java后端方向整体面试流程是技术一面、技术二面、技术三面加HR面。一面偏基础补了一些Java并发和JVM的底子二面和三面几乎全程在聊项目和系统设计重点就是秒杀系统优化HR面反而聊了很多关于项目价值和稳定性保障的内容。如果你简历上有类似的电商大促经历这篇应该对你有用。1. 面试前的准备那段大促经历是怎么变成简历亮点的1.1 项目经历从一次“爆库”事故说起我之前在的某电商业务平时QPS不高最多也就几百结果一到节假日大促就出问题。印象最深的一次活动刚开始十分钟商品详情页和下单接口的QPS直接飙到日常的几十倍。数据库连接池被打满订单表写入超时用户端不断重试最后出现了两个严重问题一是很多用户看到“购买失败”但实际库存扣了二是部分用户因为前端重试导致重复下单。复盘时发现问题根本不在某一个接口而是整条链路都没有做容量规划。详情页每次请求都查数据库下单接口直接对库存字段做update而且没有限流所有流量一路穿透到数据库不挂才怪。那次事故之后我参与了大促链路的整体改造从限流、缓存、异步化到库存扣减方式一个版本一个版本迭代。后续的大促没有再出现过同类问题。这段经历后来几乎原封不动地变成了我简历上最核心的一段项目描述也是面试中所有深挖问题的源头。1.2 简历写法的三次迭代从“参与”到“主导”再到“量化”第一次写简历时我的描述很平淡“参与秒杀系统开发负责订单模块使用Redis和RabbitMQ”。这种写法的问题很明显看不出你具体干了什么更看不出结果面试官根本不知道怎么问自己也错失了展示的机会。第二次改成了“负责秒杀系统优化通过Redis缓存、消息队列异步处理、接口限流等手段提升系统性能”。比初版好一些但还是在罗列技术名词缺少业务场景和量化结果。第三次写简历的时候我重点强调了业务问题、方案结果和数字某电商大促秒杀链路优化负责人。针对瞬时流量高峰导致数据库过载、库存超卖的问题设计并落地“网关限流 多级缓存 Redis预扣库存 MQ异步下单”方案。活动期间核心接口QPS由800提升至5000P99响应时间从1200ms降至150ms全程0超卖0库存负数。这版简历的问题意识很强有结果有数字后续每一轮面试官都会围绕它追问。但这里要提醒一句简历上写的每个技术点面试时都得扛得住追问。不要为了好看把“参与”写成“主导”除非你真的能讲清楚每一个决策和权衡。版本描述方式问题初版参与秒杀系统开发负责订单模块看不出个人贡献和结果二版负责秒杀系统优化使用Redis、MQ、限流停留在技术名词罗列三版主导优化有场景、方案、量化结果能支撑面试深挖但需要充分准备2. 秒杀系统优化的核心方案面试官到底想听什么2.1 先看清秒杀的本质高并发读、极端写、无效流量秒杀场景和普通业务最大的区别是流量在活动开始的一瞬间集中涌入但真正能成交的量极其有限。比如库存一万件可能来了百万级请求其中99%的请求注定买不到。设计秒杀系统的核心思路不是让所有请求都成功而是让有效请求快速成功无效请求快速失败并且保证系统不能被打挂。所以整个优化方向围绕两件事展开一是尽量把流量挡在前面越早拦截越好二是把核心写路径上的压力降到最低尤其是数据库的压力。基于这个思路整个方案拆成读链路和写链路两个方向去落地。面试官如果听到你说“我们要让99%的流量在门前就失败”他会知道你真的理解秒杀场景。2.2 读链路多级缓存把热点数据放在离用户最近的地方第一个压力点是商品详情页。传统做法是后端查数据库组装页面数据大促流量一来数据库先扛不住。我当时的改造从三层缓存开始第一层是页面静态化。商品名称、图片、规格这些不常变的信息提前渲染成静态页面推到CDN用户请求根本到不了后端第二层是应用本地缓存。把热点商品的库存余量、价格、活动状态放到JVM本地缓存减少对Redis的依赖第三层才是Redis存库存余量、限购标记这类需要实时一致性的动态数据。缓存分层之后真实到达数据库的请求量会低好几个数量级。不过缓存最怕一致性问题。我踩过一个坑上架活动时改了数据库价格没及时更新本地缓存导致用户看到的价格和结算价格不一致。后来我做了两个补偿措施价格这类关键字段在本地缓存只放很短时间比如3到5秒同时把变更事件通过MQ广播出去各实例收到后主动失效本地缓存。这样即使偶尔有几秒延迟也不会酿成大问题。面试时主动讲出这种一致性权衡是很加分的。2.3 写链路Redis预扣库存加MQ异步下单怎么做到不超卖写链路是秒杀的核心也是最容易被追问的部分。如果直接到数据库执行库存扣减数据库连接和行锁会变成严重的瓶颈。我当时采用的是Redis预扣库存加异步下单用户点击秒杀按钮后请求先经过网关限流和风控同一用户限制购买一件多设备同时下单会被拦截请求到达应用层执行Redis Lua脚本判断库存是否大于0如果是则库存减一同时记录用户限购标记Lua执行成功说明用户抢到资格接口立即返回“已抢到请支付”此时不会创建订单应用层把创建订单的消息发到MQ消费者异步生成订单数据用户支付成功后支付回调里再对数据库库存做最终扣减并触发后续发货流程。这里最关键的是第2步为什么必须用Lua脚本。判断库存和扣减库存必须是一个原子操作。如果先用Java代码查Redis再扣减两个请求同时读到库存为1同时执行扣减就超卖了。Lua脚本在Redis中是原子执行的判断和扣减之间没有间隙。local stock redis.call(get, KEYS[1]) if tonumber(stock) 0 then return 0 end redis.call(decr, KEYS[1]) redis.call(set, KEYS[2], ARGV[1]) return 1面试时还可以补充为什么不直接decr再判断因为要先判断再扣减避免库存减成负数。为什么不放在Redis事务里因为Lua脚本更轻量不需要WATCH、MULTI、EXEC这一整套机制。能讲出这种细节面试官会认为你真的在线下扣减逻辑里折腾过。2.4 兜底与降级缓存失效、数据库限流、对账修复系统设计如果只讲正常流程面试大概率过不了因为面试官一定会问异常场景。我当时准备的兜底方案主要覆盖三个方向如果Redis集群不可用秒杀接口不能直接报错。我会让库存服务降级到数据库扣减但此时同步开启更严格的限流只放行预估成交量的3倍流量。数据库扣减虽然扛不住秒杀量级但在降级模式下能保证核心用户仍然可以下单如果下单消息长时间消费失败消息进入死信队列由对账任务定时扫描把“已扣库存但订单未创建”的数据捞出来回补库存或者补发订单每天跑一次库存对账核对Redis库存、已售数量、待支付数量三者关系如果发现差异以数据库流水为准做修正。这些异常场景的预案讲清楚之后面试官对你的项目判断会明显上一个台阶。因为他知道你不只是写了功能代码而是真的为线上稳定性想过办法。3. 面试官连环追问实录回答思路和踩坑记录这一部分是整场面试最精彩的地方也是我收获最大的地方。我把真实遇到的追问整理成四个问题附上回答思路希望对你有帮助。3.1 追问一如果Redis挂了你的方案怎么办面试官问这个问题时千万别急着说“Redis有主从和哨兵”他其实是想知道完全不可用时系统怎么保住底线。我当时分了三层回答第一Redis本身做了高可用核心节点部署了主从加哨兵集群模式下就算单节点故障也能自动切换这是第一道防线。第二即使整个Redis不可用接口也不会直接报错库存服务会降级到数据库扣减同时把放行流量压缩到数据库能承受的范围。第三由于Redis只是预扣阶段真正下单时数据库还会做最终扣减所以哪怕Redis数据丢失对账任务也能通过数据库流水恢复库存。回答的关键是有层次高可用防单点降级保可用对账保最终一致。单讲任何一块都不完整。3.2 追问二MQ消息丢了怎么办这个问题几乎是高并发岗位必问但它实际是个组合问题至少要覆盖三个层面生产者发送失败怎么办Broker端消息丢失怎么办消费者处理失败怎么办。我的回答是生产者开启确认机制发送失败后重试或者配合本地消息表保证消息最终能发出去Broker端开启队列持久化和磁盘刷盘重启后消息不丢消费者关闭自动ack改为手动ack业务处理成功后才确认处理失败就重试多次重试进入死信队列。这里有个很容易被追问的点我自己在面试时也栽过就是消息重复消费。比如消费者处理成功后还没ack进程重启了消息会被重新投递消费者就会重复创建订单。所以消费者必须做幂等。我当时用一张幂等表以用户ID加活动ID加商品ID作为唯一键插入失败说明已经处理过直接返回成功不重复创建订单。这个方案几乎是标准答案但能结合自己的项目讲出来说服力会强很多。3.3 追问三为什么不直接用数据库扣库存这个问题考验的是对数据库锁机制的理解。使用数据库update确实能保证不超卖update stock set quantity quantity - 1 where product_id ? and quantity 0;但问题在于秒杀场景下大量请求同时更新同一行库存行锁竞争非常剧烈数据库连接会很快被占满整体吞吐量上不去。这里要说清楚不是技术不行而是数量级不匹配。Redis单线程执行Lua脚本扣减天然串行化百万级请求也能在毫秒级别完成判断和扣减单节点几万QPS的扣减能力远超数据库行锁场景。另外要补充数据库乐观锁兜底。订单创建后数据库执行update时加上version条件如果version变化就更新失败说明Redis和数据库状态不一致走对账修复。这样Redis层和数据库层各有一道防线超卖概率理论上降到了零。3.4 追问四你的压测数据怎么来的容量评估怎么做这个问题是在验证简历上的数字是不是编的。如果回答得含糊前面建立的可信度会直接崩塌。我当时做压测的完整流程是用wrk压单接口用JMeter压完整业务链路压测环境单独申请测试集群与生产环境隔离压测时先单接口找瓶颈再混合场景模拟真实流量监控指标重点看QPS、TPS、P99响应时间、错误率、CPU、内存和数据库连接池占用。容量评估的做法是预估大促峰值流量是日常的20倍日常QPS是1000峰值就是2万。如果单机接口能扛1000 QPS那么理论上20台机器就够但考虑到网络、依赖服务、数据库等不稳定因素我按2到3倍余量扩容最终准备了50到60台机器。讲到这里还可以补一句预估不可能完全准所以线上必须有实时监控、限流和熔断一旦流量超过阈值自动触发保护。这句话通常能让面试官点头。4. 复盘后的涨薪逻辑面试时如何把项目讲出高价值4.1 项目讲述的节奏场景、方案、权衡、结果面试复盘之后我发现面试官欣赏的不是“我用了Redis用了MQ”而是你讲述项目的节奏感。最有效的结构是第一场景当时的业务背景是什么流量有多大出了什么问题第二方案你提出了什么方案为什么选这个方案为什么不用另一个方案第三权衡这个方案付出了什么代价怎么兜底第四结果量化指标变化了多少系统稳定性表现怎么样。这套结构我后来反复练每次控制在5到8分钟不背稿按逻辑推演。面试官问任何一个细节我都能顺着这条链路往下延伸。比如他问“缓存怎么更新”我就从本地缓存讲到MQ广播再讲短时间过期兜底再回到一致性问题。整个故事是自洽的。4.2 面试中容易踩的坑说几个我真实踩过的坑。第一只讲技术不讲业务。面试官问“为什么要做秒杀系统”如果回答“因为要支撑高并发”等于没讲。应该回答“业务上需要在短时间内集中售卖爆款商品带来瞬时流量高峰技术目标是在保证数据一致性的前提下稳住系统”。第二不懂硬答。有一轮面试被问到分库分表的具体规则我当时只是参与过方案评审没有亲手做过迁移硬答了两句就被识破了。后来我的策略是遇到不确定的细节先坦诚说明参与程度再讲自己知道的部分面试官一般不会为难你。第三忽略非功能性指标。秒杀方案除了QPS还要讲数据一致性和可用性。面试官问你的方案好在哪不要只说快要说吞吐量上去了超卖没了挂了还能自动恢复。4.3 谈薪策略用项目价值锚定薪资区间谈薪这块我个人觉得核心不是“我要多少”而是“我值多少”。你需要让面试官和HR相信你来了之后能解决他们正在头疼的问题。如果岗位正好在建设大促或秒杀链路你的实战经验就是稀缺技能。谈薪时就不用只拿当前薪资说事可以表达我能带的不只是编码能力还有一套经过线上验证的架构思路和踩坑经验。几个实操建议先了解目标岗位的薪资带宽不要凭空报价手里有其他offer时谈薪底气会完全不一样谈薪以年包为单位月薪、年终奖、绩效、股票、签字费都要算进总包。我当时是拿了两家offer做对比用其中一家的年包跟另一家谈最终拿到了预期之上的涨幅。最后分享一点个人体会。面完这家公司之后我最大的感受是大促实战经验本身不值钱值钱的是你从实战里提炼出来的判断逻辑。同样是做秒杀有人只会背“Redis预扣库存”但有人能讲清楚为什么预扣、预扣失败的兜底是什么、消息丢失怎么恢复、压测数据怎么来。面试官要的就是后面这种能扛事的人。如果你也在准备高并发方向的面试建议先把自己的项目复盘成“场景、方案、权衡、结果”这套结构然后对着录音多讲几遍。你讲得越顺面试官就越相信那确实是你的项目。