school
前端Q&A
架构与设计模式 chevron_right High Concurrency

大促等高并发场景下的前端削峰设计

在秒杀、大促或整点抢券等瞬时高并发场景下,流量并不是平滑的,而是一瞬间的“尖峰”。直接让所有流量冲击后端会导致数据库连接池耗尽、缓存击穿甚至服务宕机。**“削峰填谷”**的核心思想就是通过一系列手段将瞬时的洪峰流量错开、拦截或转化。


1. 前端削峰的 5 种核心策略

前端是流量的第一道防线,通过有效的拦截和分散,可以过滤掉 90% 以上的无效压力。

A. 请求随机打散 (Random Scattering)

  • 设计:不要在整点时刻(如 10:00:00.000)立即发起请求。
  • 代码实现:在计时结束前,为不同的客户端生成一个随机的偏移量(Jitter)。
    // 抢购开始瞬间,随机延迟 0-500ms 发起请求
    const randomDelay = Math.floor(Math.random() * 500);
    setTimeout(() => {
        sendRequest();
    }, randomDelay);

B. 令牌桶/漏桶本地模拟 (Client-side Throttling)

  • 设计:在前端设置点击间隔或全局请求速率限制。
  • 效果:通过 throttle 拦截高频点击,防止用户因焦虑多次点击产生的重复流量。

C. 交互反馈与真实请求解耦

  • 设计:当用户点击“参与”时,UI 立即反馈“排队中…”,但真实的网络请求根据权重、排队计数器在后台缓缓发出。
  • 目的:让用户在心理上感知到“已请求”,而系统在物理上分散了压力。

D. 静态资源 CDN 隔离与“验证码”前置

  • 设计:将活动详情页全量静态化并推送到 CDN。
  • 拦截:在发起下单请求前,强制弹出简单的验证码(图形、滑块)。
  • 逻辑:验证码的交互过程不仅拦截了机器人,还利用人类输入的时间差(平均 2-3 秒)自然地完成了流量的削峰。

E. 乐观排队机制 (Optimistic Queue)

  • 设计:前端维护一个本地队列,仅在网络空闲或获得上一个 Response 后再发下一个。

2. 后端削峰配套方案 (分布式一致性深度)

后端通过 “预处理 + 异步化” 来消化前端漏下的流量:

A. Redis + Lua 脚本:原子预减库存

  • 核心逻辑:不能直接 getset,那会导致超卖。必须使用 Lua 脚本将“查询、判断、扣减”打包成一个原子操作。
  • 代码示例
    -- 只有 Redis 返回 1,前端才认为“抢购成功”
    local stock = tonumber(redis.call('get', KEYS[1]))
    if stock > 0 then
        redis.call('decr', KEYS[1])
        return 1 
    else
        return 0
    end

B. “预成功”状态机设计

  • 设计思想:一旦 Redis 预扣成功,系统即刻给用户返回“抢购成功,订单处理中”。
  • 逻辑:此时请求进入 MQ。由于 Redis 已经过滤了 99.9% 的无效流量,剩下的请求对于下游来说是平滑且可控的。
  • 异常处理
    • 99% 情况:MQ 消费成功,DB 写入订单,用户收到最终确认。
    • 极少数情况 (回滚):若 DB 写入失败(如风控触发),通过 MQ 异步将 Redis 库存 Incr 加回去,并通知用户。

C. 消息队列 (MQ) 削峰填谷

  • 作用:作为“蓄水池”。后端消费者根据 DB 的承载能力(如 2000 TPS)匀速消费。

D. 数据库最后的守门员 (CAS/行锁)

  • 即便上游失守,DB 层必须带上物理检查:Update stock set count = count - 1 where id = 1 and count > 0

3. 架构总结

削峰是一场**“空间换时间”“拦截换稳定”**的权衡。前端通过随机离散和交互延迟分散压力,后端通过 Redis Lua 原子拦截 提供“预成功”体验,再通过 MQ 异步化 确保数据的最终一致性。

tips_and_updates

AI 深度解析

需要更详细的解释或代码示例?让 AI 助教为你深度分析。