Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第 9 章 高并发写与热点场景系统设计方法论:秒杀、社交互动与流量洪峰处理

当系统面对秒杀、社交点赞、评论洪峰、直播互动、抢票、短视频上传这类“短时间内大量写入同时打向少数热点资源”的场景时,设计重点不再只是数据模型是否优雅,而是如何在极端流量下保护系统、保护资源、保护用户体验,并让写入结果最终可收敛。

前两章里,我们分别讨论了两类常见方法论:

  • 高准确性与强一致性系统,重点是保护业务事实
  • 低延迟与复杂读系统,重点是保护读路径和延迟预算

这一章讨论的是第三类非常典型的问题域:

写入压力不是均匀分布的,而是会在极短时间内集中爆发,并且经常打在同一个资源、同一个分区、同一个库存窗口、同一个热门对象上。

这类系统的核心矛盾通常不是“单次写入怎么做”,而是:

  • 流量洪峰能不能被拦住
  • 热点资源会不会被打穿
  • 后端写链路会不会雪崩
  • 结果会不会因为重复、乱序、超卖、回压而失控

所以高并发写场景的关键,不是盲目“提 TPS”,而是:

先把洪峰削平,把热点隔离,把写链路分层,再决定哪些结果必须立即确认,哪些结果可以异步收敛。

9.1 什么叫高并发写与热点场景

先看这类系统的几个共同特征。

9.1.1 写流量会在短时间内陡增

很多常规写系统的压力是相对平滑的,但高并发写场景往往不是。

典型情况包括:

  • 秒杀活动开始的前几秒
  • 演唱会抢票开票瞬间
  • 直播间大促口令生效的几十秒
  • 热门内容发布后的评论、点赞洪峰
  • 短视频平台热点事件带来的上传或互动峰值

这里最大的风险不是平均流量,而是瞬时峰值。

9.1.2 写入集中打向少数热点资源

普通写流量的问题主要是总量大;
热点写流量的问题是大量请求同时写同一个对象。

例如:

  • 同一个秒杀库存
  • 同一场演唱会同一票档
  • 同一个直播商品
  • 同一条爆款内容的点赞计数
  • 同一个明星帖子的评论楼层

这意味着即使系统总吞吐看起来够,单个热点 key、单个分区、单行记录、单个队列仍然可能先被打爆。

9.1.3 业务常常要求“快反馈”,但后端不一定能同步处理

用户在这些场景下通常希望立即得到反馈:

  • 抢到了还是没抢到
  • 点赞是否成功
  • 评论是否提交
  • 直播下单是否进入受理

但后端真正的业务处理往往比前端反馈复杂得多:

  • 需要扣库存
  • 需要写订单
  • 需要生成异步任务
  • 需要做审核或风控
  • 需要做内容处理或媒体转码

所以系统必须学会把“用户反馈时点”和“后端真正完成时点”分开设计。

9.1.4 问题常常不是平均处理能力,而是极端不均匀

这类系统最容易误判的一点是:

平均 TPS 够,不等于热点场景能扛住。

因为真正的崩溃往往来自:

  • 单 key 热点
  • 单分区热点
  • 单机热点
  • 单库热点
  • 某个下游依赖在高峰时先超时

因此高并发写系统设计,本质上要解决的是“局部极端不均匀”。

9.2 为什么不能用普通 OLTP 写路径硬扛

很多系统在业务初期的写路径都很直接:

  1. 请求进来
  2. 鉴权校验
  3. 直接写数据库
  4. 同步返回成功

这套方式在日常流量下很有效,但到了秒杀、抢票、社交热点等场景,很快就会暴露问题。

9.2.1 数据库不是洪峰吸收器

如果海量请求直接落到数据库层,会立刻遇到:

  • 连接数打满
  • 行锁竞争
  • 热点索引竞争
  • 磁盘刷写压力暴涨
  • 主从延迟放大

也就是说,数据库适合承担“最终事实存储”,但不应该成为第一道承压面。

9.2.2 同步全链路处理会把高峰直接扩散到所有下游

如果请求必须同步走完:

  • 库存
  • 订单
  • 支付上下文
  • 风控
  • 内容审核
  • 推送通知

那么高峰会被一层层传递下去,最终变成级联超时和雪崩。

9.2.3 热点冲突会让吞吐在局部迅速塌陷

例如 10 万请求并发抢 100 件库存,问题不只是“请求多”,而是这些请求都争夺同一个库存事实。
这时单纯横向扩容应用实例,收益通常有限,因为瓶颈不在应用层总算力,而在共享热点资源。

9.2.4 用户体验不一定要求“同步完成全部动作”

很多场景里,用户真正需要的是:

  • 请求被接受
  • 请求结果有明确状态
  • 最终结果能查到

而不是必须在 50 ms 内把所有后续业务全部做完。

所以工程上常见的关键转变是:

不要把所有业务步骤都塞进同步写路径,而要把同步确认、异步处理和最终收敛拆开。

9.3 设计原则:先削峰,再控速,再异步,再落库

9.3.1 第一原则:不要让洪峰直接打到最终存储

高并发写系统最重要的一件事,就是在真正写库之前先建立缓冲层和保护层。

这层保护可以是:

  • 接口限流
  • 队列排队
  • 缓冲池
  • Redis 预处理
  • 本地内存令牌

核心思想是一样的:

  • 前面吸收波峰
  • 后面按系统可承受速率处理

9.3.2 先控制进入量,再讨论处理量

很多团队一上来就想“怎么把后端撑大”,但真正成熟的系统会先问:

  • 多少请求值得进入系统
  • 多少请求应该直接拒绝
  • 多少请求应该排队等待
  • 多少请求应该只保留一次有效写入

因为在极端高峰下,“全部接住”往往不是能力,而是灾难。

9.3.3 把同步写和异步写分层

这类系统通常要把写路径分成三层:

  1. 入口受理层:校验、限流、幂等、快速返回
  2. 削峰缓冲层:队列、缓存、热点隔离、令牌分发
  3. 最终处理层:数据库写入、补偿、对账、状态推进

这样做的好处是:

  • 同步链路更短
  • 异步链路更可控
  • 各层可以独立扩缩容和降级

9.3.4 热点资源要被单独设计,而不是顺带处理

热点不是边角问题,而是这类系统的中心问题。

要优先回答:

  • 热点 key 怎么识别
  • 热点数据放在哪一层先拦截
  • 单热点是否要独立分片
  • 热点读写是否需要专门队列
  • 超热资源是否要隔离出单独集群

9.3.5 幂等、防重、补偿必须默认开启

高峰场景下,重试、超时、重复点击、客户端重发、消息重复投递都是常态。
所以系统必须默认:

  • 同一个请求可能来两次
  • 同一个消息可能消费两次
  • 同一个用户可能短时间连点十几次

这意味着:

  • 入口需要幂等键
  • 核心状态变更要有唯一约束
  • 异步消费必须幂等
  • 补偿逻辑也必须幂等

9.4 方案选型:从限流、排队到 Redis、MQ 与分片

9.4.1 限流:先决定谁能进来

限流是高并发写系统的第一道门槛。

常见算法包括:

  • 令牌桶
  • 漏桶
  • 固定窗口
  • 滑动窗口

适用场景:

  • API 接口保护
  • 用户级频控
  • 活动入口控流
  • 下游保护

重点不是“有没有限流”,而是:

  • 按用户限还是按资源限
  • 本地限还是全局限
  • 超限直接拒绝还是进入等待

9.4.2 排队与异步化:把尖峰变成平峰

消息队列最适合吸收瞬时洪峰,把写压力变成后端可消费的平滑流量。

适用场景:

  • 秒杀下单受理
  • 评论异步入库
  • 点赞异步聚合
  • 上传后异步转码

优点:

  • 削峰填谷
  • 降低同步链路时长
  • 便于失败重试

限制:

  • 不能无限排队
  • 排队时长需要可观测
  • 结果确认模式需要重新设计

9.4.3 Redis + Lua:热点资源的快速原子预处理层

对于库存预减、热点计数、资格判断、去重写入等场景,Redis + Lua 是非常常见的方案。

它适合做:

  • 秒杀库存预扣
  • 单用户去重购买校验
  • 点赞防重
  • 热门计数原子更新

优点:

  • 单线程原子执行
  • 延迟低
  • 能在真正写库前先完成快速判定

限制:

  • Redis 不是最终事实账本
  • 结果仍需和后端状态收敛
  • 热点 key 本身仍需治理

9.4.4 分片与一致性哈希:把热点拆散,但不是万能药

分片的价值在于把总量拆散,把负载摊平。
一致性哈希的价值在于减少扩缩容时的数据迁移成本。

适用场景:

  • 评论分区
  • 点赞计数分桶
  • 上传任务路由
  • 用户写流量打散

但要注意:

  • 如果所有请求都打同一个商品库存,单纯分片不一定解决根问题
  • 真正单热点资源,往往还需要资格预发放、分段库存、活动分仓等专门设计

9.4.5 数据库写缓冲与批量落库:适合“能稍后再写”的场景

对于评论、点赞、日志、行为流、媒体元数据这类不要求每次强同步落库的场景,可以考虑:

  • 批量聚合写
  • 写缓冲
  • 日志式追加
  • 周期性刷盘

优点:

  • 显著降低数据库写放大
  • 提高吞吐

限制:

  • 不适合所有强一致业务
  • 刷盘失败、缓冲丢失、顺序要求都要治理

9.4.6 一张务实选型表

场景特征推荐方案说明
流量瞬时暴涨,需要先挡住入口限流 / 频控先保护系统边界
洪峰大但允许异步处理MQ / 排队典型削峰填谷
热点资源需要快速原子判断Redis + Lua适合预扣、去重、计数
总写量大,负载可打散分片 / 一致性哈希适合评论、上传、用户流量
写后可延迟入库写缓冲 / 批量落库适合行为流和互动数据
需要同时解决库存与状态推进Redis 预处理 + 异步订单链路秒杀场景常见组合

9.5 以秒杀、社交互动、直播与抢票为例

9.5.1 秒杀:核心矛盾是少量库存对抗海量请求

秒杀的关键不是“把所有请求都处理掉”,而是:

  • 谁有资格进入下一轮
  • 库存在哪一层先被拦住
  • 结果如何最终收敛成真实订单

比较务实的路径通常是:

  1. 接口限流和资格校验
  2. Redis 预扣库存或发放资格令牌
  3. 成功者进入异步下单链路
  4. 订单写库后最终确认
  5. 失败订单触发补偿回补

9.5.2 社交点赞 / 评论:核心矛盾是热点对象与低成本反馈

点赞和评论看似简单,但热点内容上可能同时出现:

  • 海量并发写
  • 重复点击
  • 计数放大
  • 排序和楼层竞争

工程上常见做法是:

  • 点赞先做防重和异步聚合
  • 评论先受理再异步落库
  • 热门计数独立缓存
  • 长尾数据按普通路径处理

9.5.3 直播互动:核心矛盾是洪峰极短、反馈要快

直播弹幕、抽奖、口令红包、互动投票等场景通常要求:

  • 几乎实时反馈
  • 能扛瞬时爆发
  • 不因单次活动拖垮全站

因此会特别依赖:

  • 活动隔离
  • 房间级限流
  • 房间级队列
  • 热房间单独扩容

9.5.4 抢票:核心矛盾是公平性、库存一致性与热点保护

抢票和秒杀很像,但通常更强调:

  • 座位或票档资源的唯一性
  • 顺序和公平性
  • 超时释放
  • 防刷和黄牛控制

这类场景除了高并发写治理,还常常叠加强一致要求,因此经常会结合:

  • 限流
  • 排队
  • 预占库存
  • 状态机推进
  • 取消回补

9.6 热点治理、库存一致性、幂等与补偿

9.6.1 热点治理首先是识别热点

很多系统做不好热点治理,不是不会分片,而是根本不知道热点在哪里。

要重点观测:

  • 热点 key 分布
  • 单分区写入量
  • 热门活动资源命中率
  • 单对象写失败率
  • 单下游超时率

9.6.2 库存一致性不能只靠前置预扣

Redis 预扣很快,但它不是订单事实本身。
真正成熟的库存方案通常是:

  • 前面快速预扣或资格判断
  • 后面真实订单和库存事实落库
  • 最后通过补偿和对账收敛

也就是说:

预扣解决的是峰值入口,最终事实仍然要靠后端状态闭环。

9.6.3 幂等既要挡住入口,也要挡住异步链路

入口幂等防的是重复提交;
异步幂等防的是重复消费和重复执行。

两者都缺一不可。

9.6.4 补偿不是失败兜底,而是主设计的一部分

在高并发写场景里,失败不是偶发,而是常态之一。
所以必须提前设计:

  • 订单创建失败如何回补
  • 消费超时如何重试
  • 热点状态不一致如何对账
  • 预扣成功但最终失败如何释放

9.7 常见误区

9.7.1 只想着扩容,不想着控流

扩容能提升总量,但拦不住无上限洪峰。

9.7.2 让数据库承担第一波冲击

这样最容易把最终存储层变成第一批牺牲者。

9.7.3 只做异步化,不做结果确认设计

异步不是把问题藏起来。
用户成功、受理中、失败、待确认这些状态都必须被明确定义。

9.7.4 只做 Redis 预扣,不做后端事实闭环

这样系统会很快陷入“缓存看起来对,数据库事实说不清”的状态。

9.7.5 把所有热点都当成同一种问题

单库存热点、热门评论热点、直播房间热点、上传带宽热点,其实是不同层次的问题,方案不能一刀切。

9.8 方法论沉淀:如何做出务实选型

9.8.1 五个判断句

  1. 高并发写系统的首要目标不是把所有请求都处理完,而是让系统在洪峰下仍然可控。
  2. 先控进入量,再谈处理量;先保护热点,再谈整体吞吐。
  3. 能异步的不要强同步,能削峰的不要直接落库,能分层的不要单链路硬扛。
  4. Redis、MQ、分片、批量写都只是局部手段,真正关键的是入口保护、状态设计和最终收敛。
  5. 秒杀、抢票、社交互动看起来不同,但底层都在解决洪峰、热点、幂等、补偿和资源保护问题。

9.8.2 一句话表达

如果把这一章压缩成一句可直接复用的话,可以这样讲:

高并发写与热点场景系统设计的核心,不是把后端数据库做得足够抗打,而是先用限流、排队、预处理和热点隔离把洪峰削平,再用异步链路、幂等、补偿和状态闭环把最终结果收敛出来。