场景一:秒杀 / 抢票
整点开抢的那一秒,流量瞬间放大几百上千倍。这是高并发领域最"暴力"也最经典的场景:百万人抢一万件货,系统不能崩,货还一件都不能多卖。
场景描述与业务特征
双11 零点抢半价 iPhone、周杰伦演唱会开票、12306 春运抢票——它们的共同剧本是:货极少、人极多、时间极短。就像演唱会场馆只有一个小门,开门瞬间上万人一起往里挤。
⚡ 瞬时脉冲
流量集中在开抢后的几秒到几十秒,之前风平浪静,之后迅速退潮。为峰值买一整年的服务器,纯属浪费。
📉 请求极度"无效"
1 万件库存、100 万人抢,注定 99% 的请求拿不到货。设计核心思路:让无效请求死得越早、越便宜越好。
🔒 强一致扣减
库存是不能吹牛的数字:多卖一件就是资损和客诉。允许少卖(回滚),绝不允许超卖。
🤖 真人混着机器人
黄牛脚本、刷子军团往往占流量大头,风控与验证码是隐形的第一道闸门。
🧊 读写倒挂
开抢前全是读(刷新页面看倒计时),开抢瞬间转为写(下单扣库存),读写洪峰前后脚到达。
💥 连坐风险
秒杀流量若与正常业务共用数据库,一旦打挂,整个站点跟着陪葬——所以要隔离部署。
整体运作流程
成熟的秒杀架构是一个层层过滤的漏斗:100 万请求进来,每一层都拦掉一大批,真正摸到数据库的只剩几千。就像演唱会安检:先看有没有票(静态页/CDN)、再验身份(网关限流)、再安检(Redis 预扣减)、最后才进场(数据库落单)。
核心瓶颈与数据一致性挑战
🔥 单行热点:所有人挤同一行库存
百万请求最终都要更新 同一行库存记录。MySQL 行锁让请求排成串行长队,锁等待雪崩,连接池瞬间打满——这是秒杀的"物理极限"所在。
💸 超卖:最经典的一致性事故
朴素写法"先查库存再扣减",两个请求同时查到"还剩 1 件",双双下单成功——卖出 2 件。检查与扣减不是原子操作,就是事故的温床。
👻 少卖:预扣减的副作用
Redis 扣了库存,用户却没付款/下单失败,如果不把额度还回去,商品明明有货却显示售罄——需要超时回补机制。
🎭 缓存与数据库的双账本
库存同时存在于 Redis 和 MySQL 两本账上,宕机、消息丢失都会导致两本账对不上——必须以谁为准、如何对账,要提前想清楚。
UPDATE stock SET n = n - 1 WHERE id = 1 但忘了加 AND n > 0,库存被扣成 -4327,多卖四千余单,只能一单一单打电话道歉赔券。一致性挑战不是玄学,是真金白银。优化手段、获益与代价
① 页面静态化 + CDN 拦截层
活动页做成纯静态推到 CDN 边缘节点,开抢前按钮置灰,倒计时由前端本地计算。
② 网关限流 + 验证码/答题 拦截层
令牌桶/漏桶限流按容量放行;滑块、答题打乱脚本节奏,把机器人挡在门外。
③ Redis + Lua 原子预扣减 核心
库存提前加载进 Redis,用 Lua 脚本把"判断+扣减"合成一个原子操作,扣到即获得下单资格。
④ 消息队列异步下单 削峰
拿到资格后请求进 MQ(RocketMQ/Kafka),订单服务按自身消费能力匀速处理,前端轮询/推送结果。
⑤ 库存分片(热点打散) 进阶
把 1 万件库存拆成 100 个桶(每桶 100 件)分散到多个 Redis 分片/多行记录,请求随机路由到桶。
多种解决方案对比与选型建议
| 方案 | 防超卖能力 | 吞吐上限 | 实现复杂度 | 适用规模 |
|---|---|---|---|---|
| 数据库悲观锁 SELECT ... FOR UPDATE | 强 | 低 · 数百QPS | 极简 | 内部系统、小型活动 |
| 数据库乐观锁 UPDATE ... WHERE stock>0 | 强 | 中 · 数千QPS | 简单 | 万级并发的中小促销 |
| Redis+Lua 预扣减 + MQ 本页主推架构 | 强(需对账兜底) | 高 · 10万+QPS | 中等 | 大型电商秒杀主流方案 |
| 本地缓存分桶+分片 JVM内存扣减+异步汇总 | 最终一致 | 极高 · 百万QPS | 复杂 | 12306/头部大厂级别 |
| 答题/抽签制 先收集意向再摇号 | 强 | 不限(无瞬时峰) | 简单 | 公平性优先(摇号购车/购房) |
推演检验:换个条件还成立吗?
背下架构图不算学会,能判断"条件变了方案还成不成立"才算。先自己推,再展开参考。
推演 1:如果库存从 1 万件变成 1000 万件(基本人人有份),这套漏斗架构还需要吗?
💡 参考推演
拆成两个维度看:流量脉冲仍在(开售瞬间大家还是一起点),所以 CDN 静态化、网关限流、MQ 削峰这些"为流量服务"的层仍然需要;但稀缺性消失了,防超卖压力骤降——Redis 预扣减可以退化成 DB 乐观锁甚至不预扣。结论:拦截层为流量服务,一致性层为稀缺服务,两者要分开评估,不是打包取舍。
推演 2:如果开抢进行到一半 Redis 集群整体宕机,降级成数据库悲观锁硬顶,会发生什么?
💡 参考推演
行锁把并发捣成串行,吞吐从 10 万级跌到几百 QPS,连接池瞬间占满,若与正常业务共库还会拖垮全站——这就是"连坐"。正确姿势不是硬顶,而是同步把限流阀值降到 DB 容量以内(容量骤降但不超卖),或干脆熔断秒杀入口保主站。降级从来不是"换个组件继续扯",而是"换个组件 + 同比例收缩流量"。
推演 3:如果业务方新增硬性要求"绝不能少卖"(预扣额度不回补就是亏损),架构要加什么?
💡 参考推演
回补机制从"可选"变"必修":订单超时未支付 → 取消订单 → 额度原子归还 Redis,再加定时对账(Redis 余额 + 已成交订单 应等于 总库存)兑底修正。代价:复杂度上升,而且会出现"显示售罄又复活"的体验问题——技术指标(不少卖)和体验指标(不诈尸)本身也在博弈,需要产品一起拍板。