在南方深山里,有一座生意火爆的温泉度假村。度假村的心脏,是一口只能容纳一百人的露天温泉池——不是老板不想多接客,而是救生员反复强调:池子一旦超过一百人,水温会骤降、安全会失控、体验会崩盘。于是老板开了一场“客流管理大会”,让三位管家各出一套方案。 老陈的方案是一条传送带:不论更衣室排了多少人,传送带每隔五秒只送一位游客进池,速度永远恒定——这是“漏桶”。小周搬来一台温泉币兑换机:机器每秒往箱子里补一个币,箱子最多攒十个,游客必须投币才能进池,攒下的币让你在早高峰一口气冲进去——这是“令牌桶”。安全员小林则举起一块救生员记录板,盯住“过去三十分钟”的滚动入场人数,任何滚动时段都不许超过一百——这是“滑动窗口”。 三套方案各有性格,它们对应的正是计算机系统在高并发场景下的三大限流算法。限流,就是给系统装一道阀门:当请求像游客一样汹涌而来时,阀门决定放谁进、放多少、什么时候放,保住背后那口脆弱的“温泉池”——数据库、订单服务,或者任何一个扛不住突增的下游。接下来,我们把三道阀门逐一拆开,看看它们的脾气、数学账,以及什么时候该用哪一把。

岗位关联

对 AI 从业者来说,限流是“高可用架构”的第一课:看懂三种算法的取舍,你就能在系统设计评审时提出关键问题,在流量突增时快速止损——这也是架构师面试中几乎必考的一道题。

目录
  1. 可视化演示
  2. 限流概念:为什么池子必须设上限
  3. 漏桶算法:匀速传动的“刚性传送带”
  4. 令牌桶算法:能攒家底的“温泉币”
  5. 滑动窗口:救生员的“滚动记录板”
  6. 参数配置:阈值不是拍脑袋定出来的
  7. 工程对比:三把钥匙各开一把锁
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

模拟令牌桶、漏桶与滑动窗口三类限流算法的请求处理过程,对比突发流量接纳与匀速输出特性,观察拒绝与放行判定。

本节配有交互动效(进入视口后自动加载,约 3KB)。

模式1:令牌桶限流 令牌以速率 r 生成入桶 请求取令牌则放行 tokens = min(T_max, tokens + r·Δt) 支持突发流量,但可能瞬时压垮下游。

限流概念:为什么池子必须设上限

温泉池的安全线是“一百人”。老板发现,问题不在平时的三五成群,而在周末节假日的爆发:有人上午十点就在大门口排了两个小时队,一开门就演变成“百米冲刺”,救生员根本数不过来。如果不设限,池子瞬间超载,轻则体验崩盘,重则出安全事故。限流,就是提前在山路入口处立一道闸,让游客有序、限量地进入。

计算机系统的“温泉池”就是你的后端服务,它的容量由 CPU 核数、内存、数据库连接池共同决定。平时请求平稳,一切安好;一旦遇上秒杀、热点新闻、恶意爬虫,QPS 可以在几秒内翻几十倍。请求打进来本身不致命,致命的是每个请求背后都要查数据库、扣库存、调外部服务——每个请求都会消耗有限的资源。资源耗尽时,所有请求一起超时,系统像多米诺骨牌一样整体倒下,这就是雪崩。

限流(Rate Limiting)就是预先设置一条“安全水位线”:超过水位的请求被拒绝或排队,保证系统永远不超载。和限流并肩作战的还有两个兄弟:熔断(下游故障时快速断开,避免雪崩扩散)和降级(牺牲次要功能、保全核心链路)。评价一个限流方案,主要看三个维度:精度(放行量是否严格不超阈值)、突发容忍度(能否应对短时脉冲)、资源开销(内存与计算成本)。

老板拍板的规则就是阈值,救生员就是执行器。可阈值定多大、用什么规则定,决定了度假村能安稳多久——接下来,三位管家的方案轮流登场。

R实际=min⁡(R请求, R阈值)R_{\text{实际}} = \min(R_{\text{请求}},\ R_{\text{阈值}})
这句话是说:系统真正处理的速率,永远取“请求速率”和“限流阈值”中较小的那个——请求少时全放行,请求多时只放阈值那么多。
  1. 实际处理速率系统真正放行的请求数(QPS),它是整条公式的输出结果,永远不会超过阈值。
    R实际R_{\text{实际}}
  2. 请求速率上游打进来的原始流量,可能瞬间飙升到阈值的几十倍,它是“不听话”的输入。
    R请求R_{\text{请求}}
  3. 取最小值当请求少时全放行,请求多时只放阈值那么多——这就是“阀门”的含义。
    min⁡(R请求, R阈值)\min(R_{\text{请求}},\ R_{\text{阈值}})
  4. 限流阈值工程师根据压测设定的安全水位线,超过它的部分被拒绝或排队等待。
    R阈值R_{\text{阈值}}

漏桶算法:匀速传动的“刚性传送带”

老陈的传送带,在计算机里有个学名叫“漏桶”。想象一个底部带小孔的桶:不管你用消防泵往里面灌水,还是拿滴管一滴一滴地喂,桶底的水流速度都恒定。桶有大小,水灌满了就会溢出——溢出的水,就是被限流挡在门外的请求。

在漏桶算法里,每个请求先进入桶(相当于排队),桶底按固定速率放行请求;桶的容量为,请求超过容量后直接溢出丢弃。这里的“溢出”有两种处理策略:直接拒绝(返回 429 或错误码),或者静默丢弃。无论桶里积压了多少,出去的速率始终不变——这就是漏桶最核心的性质:输出绝对平滑。

老陈的传送带“稳定压倒一切”,但代价是死板:就算池子空无一人,传送带也不会加速。漏桶最适合削峰填谷——把突发的流量抹平成均匀的节奏,喂给那些“吃不了快饭”的下游,比如消息队列的消费者。

Q(t)=min⁡(C, Q(0)+λt−rt)Q(t) = \min(C,\ Q(0) + \lambda t - r t)
这个公式描述的是“ t 时刻桶里积压了多少请求”:初始积压,加上新到来的,减去匀速流出的,但最多不超过桶容量。
Q(t)=min⁡(C, Q(0)+λt−rt)Q(t) = \min(C,\ Q(0) + \lambda t - r t)
  1. 当前积压时刻桶里等待处理的请求数,它随时间动态变化,是公式的输出。
    Q(t)Q(t)
  2. 桶容量桶能装的最大请求数,水满再倒就会溢出,溢出的请求被丢弃。
    min⁡(C, ⋅)\min(C,\ \cdot)
  3. 初始积压与新增是开始时已有的积压,是请求到达速率,就是秒内新到的请求。
    Q(0)+λtQ(0) + \lambda t
  4. 匀速流出桶底每秒处理个请求,秒共处理个——这是“漏”出去的部分,速度恒定。
    rtr t

令牌桶算法:能攒家底的“温泉币”

小周看不上传送带的死板。他设计的温泉币兑换机每秒往箱子里吐一个币,箱子最多攒十个币;游客必须投一个币才能进池。平时没人来,币就攒着;早高峰一到,攒下的十个币可以让十个游客瞬间冲进去,而后面的游客只能等下一个币。这就是“闲时积攒、忙时爆发”。

令牌桶算法(Token Bucket)就是这个机制:系统按固定速率向桶中放入令牌,桶容量为;每个请求必须先拿走一个令牌才能被处理。有令牌立刻放行,没令牌直接拒绝(或短暂等待)。与漏桶最大的区别是:令牌桶没有请求队列,请求之间不会互相排队,只要桶里有令牌就放行。

小周的方案最懂人性:既保证平均速率不超过,又允许短时间爆发个请求。这也是为什么业界最常用令牌桶——Guava 的 RateLimiter、Nginx 的 limit_req、各大 API 网关的限流插件,基本都是令牌桶或它的变体。

T(t)=min⁡(B, T(t−Δt)+r×Δt)T(t) = \min(B,\ T(t - \Delta t) + r \times \Delta t)
这个公式描述的是“ t 时刻桶里还剩多少令牌”:每个瞬间先补充新生成的令牌,但桶里最多存个,多余的作废——正是这个容量,决定了系统能承受多大的突发流量。
  1. 当前令牌数时刻桶里剩余的令牌个数,每个请求要拿走一个才能通过。
    T(t)T(t)
  2. 上轮余额上一个瞬间剩下的令牌,它们不会过期,会一直攒着。
    T(t−Δt)T(t - \Delta t)
  3. 新生成令牌在这段时间内,机器匀速吐出的新令牌,相当于系统持续补充的“额度”。
    r×Δtr \times \Delta t
  4. 容量上限令牌最多攒个,再多的白白浪费,防止积累出无限大的爆发力。
    min⁡(B, ⋅)\min(B,\ \cdot)

滑动窗口:救生员的“滚动记录板”

小林发现两位管家都漏了一个场景:如果只约定“每分钟最多一百人”,游客会在第 59 秒冲进一百人,第 61 秒(下一分钟的第一秒)再冲进一百人——前后不过 2 秒,池子涌进两百人。这就是“固定窗口”的临界突发问题,也叫“窗口边界穿透”。

滑动窗口的解决思路是:把时间切成更细的小格(比如 1 秒一格),维护一个“过去 60 秒”的滚动计数。每过一小格,最旧的一格滚出窗口,新的一格加入统计。无论请求卡在哪一秒,统计的都是“最近 60 秒”,边界漏洞被彻底封死。

精确实现可以用时间戳列表(每个请求记一个时间戳,滑动时剔除过期的),但内存随请求量增长;工程上更常用“加权滑动窗口”,只需两个计数器就能估算:

小林的方案最“较真”:精确但费资源。滑动窗口适合“用户维度精确配额”——比如每个 API Key 每分钟最多 30 次调用。它和令牌桶并不互斥:令牌桶管“总闸”,滑动窗口管“细账”,很多系统两者叠加使用。

N=Nprev×(1−ΔtW)+NcurrN = N_{\text{prev}} \times \left(1 - \frac{\Delta t}{W}\right) + N_{\text{curr}}
当前窗口的请求数,约等于“上一格计数按还没滚出去的比例折算”再加上“当前格已经累计的计数”。折算比例随时间的推移从 1 衰减到 0。
  1. 当前窗口计数估算出的“最近一个窗口”内的请求总数,用来和限流阈值比较。
    NN
  2. 上一格计数前一个时间格累计的请求数,它只有一部分还落在当前窗口里。
    NprevN_{\text{prev}}
  3. 折算比例上一格还“有效”的比例——当前格已流逝的时间越大,滚出去的越多,折算越小。
    1−ΔtW1 - \frac{\Delta t}{W}
  4. 当前格计数当前时间格已经累计的请求数,它完整地落在窗口内,全额计入。
    NcurrN_{\text{curr}}

参数配置:阈值不是拍脑袋定出来的

三套方案摆在桌上,老板问出了最关键的问题:阈值怎么定?传送带五秒一人还是一秒一人?温泉币箱子容量十个还是五十个?小林说:别猜,先做一次“压力测试”。

压测的做法是:不断加大并发请求,观察系统的吞吐量曲线。一开始吞吐量随并发上升,但到某个点后,再加并发,吞吐量不再增长、响应时间反而直线恶化——这个点就是系统的“拐点”,也就是真实承载上限。限流阈值应该设在拐点之下,预留安全余量:

参数不是定一次就完事,还要灰度调优:先放量观察,再逐步逼近阈值,同时盯着 CPU、响应时间和错误率三个仪表盘。老板听完,决定先试运营一周,再根据周末客流微调传送带速度——这套“压测找边界、余量保安全、数据验证”的流程,既适用于温泉池,也适用于任何高并发系统。

Rlimit=Rmax×(1−α),α∈[0.2, 0.3]R_{\text{limit}} = R_{\text{max}} \times (1 - \alpha),\qquad \alpha \in [0.2,\ 0.3]
通俗讲:压测发现系统极限 QPS 是 1000,那阈值就设 700~800,留出两三成缓冲,避免日常波动就触发雪崩。
  1. 限流阈值最终写入配置的限流值,系统不允许实际速率超过它。
    RlimitR_{\text{limit}}
  2. 极限吞吐压测得到的吞吐拐点,是系统的真实物理上限,超过它就会恶化。
    RmaxR_{\text{max}}
  3. 安全余量0.2~0.3 的缓冲系数,防止常态波动误伤正常用户。
    α∈[0.2, 0.3]\alpha \in [0.2,\ 0.3]
  4. 双旋钮与令牌桶的两个参数——对应平均 QPS,对应最大突发量。
    r, Br,\ B

工程对比:三把钥匙各开一把锁

最后老板拍板:不同入口用不同方案——更衣室用传送带(要平滑),温泉池用温泉币(要允许早高峰),园区大门用记录板(要防踩点)。工程上的选型也是同理:先看你的核心诉求,再决定用哪把锁。

三个关键维度对比:

  • 突发流量应对:漏桶完全没有突发能力,秒内最多放行个;令牌桶允许先爆发个再匀速;滑动窗口限制的是窗口总量,剩余额度决定了突发空间。
  • 时钟漂移容忍:漏桶和令牌桶都依赖“本地时间差”来补令牌或漏水,对时钟漂移不敏感,分布式部署更稳;滑动窗口依赖每个请求的时间戳,各机器时钟不一致会导致统计偏差。
  • 分布式实现:令牌桶和漏桶的核心状态只是一个计数器和时间戳,用 Redis + Lua 就能原子完成;滑动窗口需要 ZSET 或分段计数,成本和复杂度更高。

用一条公式总结三种算法在时间内的“最大放行量”:

选型建议一句话:求稳选漏桶,求活选令牌桶,求准选滑动窗口。大型系统从来不只锁一把锁——网关层用令牌桶挡总量,业务层用滑动窗口做用户配额,消息管道用漏桶做削峰填谷,再叠加“本地限流 + 集中式兜底”的双层结构,才能在突发流量面前既稳又准。

S漏桶(T)=rT,S令牌桶(T)=B+rT,S滑窗(T)=LS_{\text{漏桶}}(T) = rT,\qquad S_{\text{令牌桶}}(T) = B + rT,\qquad S_{\text{滑窗}}(T) = L
  1. 漏桶匀速传送带,秒最多送个,雷打不动,曲线最平。
    rTrT
  2. 令牌桶先花攒下的个家底,再按匀速续上,上限最高。
    B+rTB + rT
  3. 滑动窗口滚动限额,窗口内总量不许超,规则最严。
    LL

AI 应用场景

电商大促秒杀系统的入口限流

阶段:高可用架构设计 / 流量入口防护

解决的问题:秒杀开场瞬间流量可达平时的几十倍,直接打到订单服务会导致数据库连接池耗尽、系统雪崩。传统做法是“硬扛”,结果所有用户一起超时;用令牌桶在网关设一道“总闸”,允许短时爆发但限制平均速率,超出的请求引导到排队页或直接返回“已抢光”,保住核心下单链路。

具体案例:某电商平台在双 11 秒杀中,网关层部署令牌桶(rate=8000 QPS,capacity=24000),配合 Redis + Lua 在集群内共享令牌状态。压测对比显示:无限制时订单服务在 2 万 QPS 下 RT 从 40ms 恶化到 3s;加上限流后 RT 稳定在 45ms,错误率从 12% 降到 0.1%。

消息队列消费端的削峰填谷

阶段:数据管道 / 消息消费速率控制

解决的问题:上游产生消息的速率忽高忽低,而下游消费者(如批量写库、调用第三方 AI 模型 API)有严格的速率上限。用漏桶思想把消费速率固定在一个安全值,避免下游被突发的消息冲垮——就像传送带不管上游多急,都匀速把消息喂给消费者。

具体案例:一个内容审核系统需要调用外部 AI 审核 API,该 API 限速 100 QPS。团队在消费端用漏桶(rate=80 QPS,capacity=1000)匀速拉取消息,把瞬时突发的 2000 条消息排队慢慢消费,既保证了吞吐稳定,又避免了 API 配额被封禁。

分布式 API 网关的用户维度限流

阶段:网关层 / 多租户配额管理

解决的问题:某个用户的脚本疯狂调用接口,影响其他用户。单机限流无法约束集群整体用量,需要滑动窗口精确统计“每个用户过去 1 分钟的请求数”,超限即返回 429——就像救生员盯着每个游客过去 30 分钟的进场次数,谁超了谁就被请出去。

具体案例:某 SaaS 平台用 Redis + Lua 实现滑动窗口,对每个 API Key 维护一个 ZSET,记录最近 60 秒内每次请求的时间戳,窗口内超过 30 次则拒绝。上线后单个恶意用户对整体资源的占用下降了 90%,正常用户调用完全不受影响。

Python 演示

python95 行
import timeimport threading  class TokenBucket:    """令牌桶限流器: rate 为每秒令牌数, capacity 为桶容量"""     def __init__(self, rate, capacity):        self.rate = rate        self.capacity = capacity        self.tokens = capacity        self._lock = threading.Lock()        self._last = time.monotonic()     def acquire(self):        """尝试获取一个令牌, 成功返回 True, 失败返回 False"""        with self._lock:            now = time.monotonic()            self.tokens = min(self.capacity,                              self.tokens + (now - self._last) * self.rate)            self._last = now            if self.tokens >= 1:                self.tokens -= 1                return True            return False  class LeakyBucket:    """漏桶限流器: 固定速率匀速放行, capacity 为桶容量"""     def __init__(self, rate, capacity):        self.rate = rate        self.capacity = capacity        self.queue = 0        self._lock = threading.Lock()        self._last = time.monotonic()     def acquire(self):        """尝试进入队列, 队列未满则接收, 满了则溢出拒绝"""        with self._lock:            now = time.monotonic()            self.queue = max(0.0,                             self.queue - (now - self._last) * self.rate)            self._last = now            if self.queue < self.capacity:                self.queue += 1                return True            return False  class SlidingWindow:    """滑动窗口限流器: 记录时间戳列表, 窗口内总数不超过 limit"""     def __init__(self, window, limit):        self.window = window        self.limit = limit        self._stamps = []        self._lock = threading.Lock()     def acquire(self):        """剔除过期时间戳后, 判断窗口内计数是否超限"""        with self._lock:            now = time.monotonic()            self._stamps = [t for t in self._stamps if now - t < self.window]            if len(self._stamps) < self.limit:                self._stamps.append(now)                return True            return False  def run(name, limiter, seconds, qps):    interval = 1.0 / qps    allowed = 0    total = 0    for _ in range(int(seconds * qps)):        total += 1        if limiter.acquire():            allowed += 1        time.sleep(interval)    print(f"{name}: 总请求 {total}, 放行 {allowed}, "          f"拒绝 {total - allowed}, 放行率 {allowed / total:.1%}")  def main():    print("=== 三种限流算法对比: 模拟 1 秒内突发 100 个请求 ===")    run("令牌桶(rate=50, cap=20)",        TokenBucket(rate=50, capacity=20), 1, 100)    run("漏桶(rate=50, cap=20)",        LeakyBucket(rate=50, capacity=20), 1, 100)    run("滑窗(window=1, limit=50)",        SlidingWindow(window=1, limit=50), 1, 100)  if __name__ == "__main__":    main()
目的:用纯 Python 实现令牌桶、漏桶、滑动窗口三种限流器,模拟 1 秒内 100 个突发请求,直观对比三种算法的放行行为。
  1. self.tokens = min(self.capacity, self.tokens + (now - self._last) * self.rate)令牌桶先按流逝时间补发令牌,再封顶到容量,这是令牌桶的核心公式。
  2. self.queue = max(0.0, self.queue - (now - self._last) * self.rate)漏桶先把队列按固定速率“漏”掉一部分,模拟匀速出水。
  3. if self.queue < self.capacity:漏桶队列没满就接收请求,满了就溢出拒绝,多余的游客只能走人。
  4. self._stamps = [t for t in self._stamps if now - t < self.window]滑动窗口先剔除过期时间戳,只保留窗口内的记录。
  5. if len(self._stamps) < self.limit:窗口内计数未超限才放行,精确但不允许任何突发。
  6. with self._lock:加锁保证多线程并发请求时计数不超卖,相当于救生员“一次只核一个账”。
  7. time.monotonic()使用单调时钟,不受系统改时间影响,避免时钟回拨导致误放行。

常见误区

误区“漏桶和令牌桶不是一样吗?都是往桶里放东西、满了就丢,区别只是叫法不同吧?”

正解两者的“输出逻辑”截然相反。漏桶是“桶底漏水”——处理速率恒定,你来多少我都匀速处理,不允许突发;令牌桶是“先领币再入场”——处理速率只受令牌供给约束,攒下的令牌可以让请求瞬间爆发。可以这样记:漏桶限制的是“流出的速度”,令牌桶限制的是“流入的平均速度”。

为何常见:两者都叫“桶”,都有容量上限和溢出丢弃,初学者容易被表面结构迷惑,忽略了输出节奏的本质差异。

误区“每分钟限 100 个请求,那一分钟内最多就 100 个,怎么可能超?”

正解固定窗口的计数会在窗口边界瞬间清零。第 1 分钟的最后 1 秒放走 100 个,第 2 分钟的第 1 秒又放走 100 个——两个窗口交界处实际上 2 秒内放行了 200 个,这就是“临界突发”。滑动窗口把“整点清零”变成“滚动淘汰”,才真正封死了这个漏洞。

为何常见:人们习惯于“整点清零”的直觉(比如每分钟报表),没有意识到流量不会恰好停在整点边界。

误区“限流就是挡用户的,阈值设大一点,宁可系统扛一扛也别误伤用户。”

正解阈值一旦超过系统真实承载能力,限流就失去了意义——请求照样打进来,把数据库拖垮,最终所有用户一起超时。正确的做法是先压测找到吞吐拐点,再乘以 0.7~0.8 的安全系数。限流的本质是“牺牲部分请求,保全整体可用性”,而不是“能放多少放多少”。

为何常见:产品视角天然反感“拒绝用户”,容易把限流误解为业务损失,而忽视了系统雪崩造成的更大损失。

误区“集群有 10 个节点,每个节点本地限流 100 QPS,总容量就是 1000 QPS。”

正解流量不会均匀地打到每台机器上。负载均衡可能把 80% 的流量导到某一台机器(比如 Hash 策略下热点用户集中),单机先被击穿,而其他机器还在空转。分布式限流需要集中式协调(Redis + Lua 原子操作),或者至少“本地限流 + 集中式兜底”的双层结构。

为何常见:直觉上“总容量 = 单机容量 × 机器数”,忽略了流量分布的偏斜和单点退化风险。

误区“滑动窗口要记录每个请求的时间戳,内存会爆炸,所以只能用在玩具项目里。”

正解精确滑动窗口(ZSET 存每个时间戳)确实随请求量增长内存,但工程上常用“加权滑动窗口”——把时间切成若干固定小格,每格只存一个计数器,内存与请求量无关,只与格子数有关(例如 60 格只需 60 个整数)。Redis 官方推荐的很多限流方案就是这种近似滑窗。

为何常见:初学者只在教科书上看过“精确时间戳列表”的实现,不了解计数分桶的近似优化,于是以偏概全。

面试题

Q1. 关于漏桶算法的描述,正确的是哪一项?

单选题 · easy

  • A.输出速率可以突发
  • B.输出速率恒定平滑← 正确答案
  • C.桶容量越大输出越快
  • D.请求永远不会被丢弃

答案:B。漏桶的核心是“桶底匀速漏水”,无论请求多汹涌,处理速率恒定为 r,所以 B 正确。A 是令牌桶的特征;C 中桶容量只决定能排队多少、不决定流速;D 错误,桶满后溢出的请求会被丢弃。

Q2. 在令牌桶算法中,“桶容量 B”的含义是什么?

单选题 · easy

  • A.每秒发放令牌的速度
  • B.最多能积攒的令牌数量← 正确答案
  • C.每秒允许处理的请求数
  • D.队列中等待请求的数量

答案:B。令牌桶的“桶”是用来积攒令牌的,B 表示令牌最多能攒多少个,它决定了系统的最大突发能力。A 对应令牌生成速率 r;C 对应平均水平 r,不是 B;D 描述的是漏桶的队列,令牌桶并没有排队队列。

Q3. 固定窗口限流的主要缺陷是什么?

单选题 · mid

  • A.实现起来复杂度较高
  • B.窗口边界的突发漏洞← 正确答案
  • C.无法设置合理的阈值
  • D.每秒都需要重置计数器

答案:B。固定窗口在窗口切换瞬间计数清零,请求可以卡在边界前后各涌入一批,造成“临界突发”。这正是滑动窗口要解决的问题。A/C/D 都不是固定窗口的典型缺陷——它恰恰以简单著称,阈值也完全可以配置。

Q4. 压测得到某系统极限吞吐 2000 QPS,较合理的限流阈值是?

单选题 · mid

  • A.1000 左右
  • B.1500 左右
  • C.1600 左右← 正确答案
  • D.2500 左右

答案:C。工程惯例是在压测拐点上预留 20%~30% 安全余量,2000 × 0.8 = 1600。1500 偏保守但也可接受,1000 过于浪费资源,2500 超过极限等于没有限流,会让系统在过载中雪崩。

Q5. 集中式 Redis 令牌桶相比本地令牌桶,最典型的额外开销是?

单选题 · hard

  • A.需要额外存储令牌状态
  • B.每次请求都有网络开销← 正确答案
  • C.无法支持突发流量
  • D.令牌生成速率不稳定

答案:B。集中式方案把令牌状态放在 Redis,每次请求都要走一次网络往返去原子地拿令牌,比本地内存多一次网络开销,这是它的主要代价,换来的是集群内全局一致。A 中本地方案也要存状态;C/D 与集中式无关。

Q6. 加权滑动窗口公式 N_prev × (1 − Δt/W) + N_curr 中,Δt 表示?

单选题 · mid

  • A.整个时间窗口的总长度
  • B.当前小窗口中已过去的时间← 正确答案
  • C.两个请求之间的间隔
  • D.上一次重置的具体时刻

答案:B。Δt 是当前小窗口已经流逝的时间,1 − Δt/W 就是上一格计数中仍应算进当前窗口的比例——Δt 越大,上一格被折算得越少。A 是窗口长度 W;C/D 描述的是别的概念。

Q7. 下列场景中,最适合使用漏桶算法的是?

单选题 · hard

  • A.秒杀抢购的流量放行
  • B.消息队列消费的削峰填谷← 正确答案
  • C.用户维度的精确配额
  • D.应对不可预知的流量尖峰

答案:B。漏桶的价值是输出绝对平滑,非常适合把突发的消息积压在桶里、匀速喂给消费者,保护下游。A 秒杀需要突发放行,应选令牌桶;C 精确配额应选滑动窗口;D 漏桶并不能“应对尖峰”,它只是把尖峰抹平成匀速。

Q8. 关于令牌桶与漏桶的区别,下列正确的有?

多选题 · mid

  • A.令牌桶允许一定程度的突发← 正确答案
  • B.漏桶保证输出速率恒定← 正确答案
  • C.令牌桶也需要维护请求队列
  • D.漏桶对外表现为匀速排队← 正确答案

答案:ABD。令牌桶用“攒币”天然支持突发,A 正确;漏桶匀速出水,B 正确;漏桶把请求放进桶里排队、匀速放出,D 正确。C 错误——令牌桶只需要维护令牌计数器,请求来了有币就放、没币就拒,不需要排队。

Q9. 关于滑动窗口限流,下列说法正确的有?

多选题 · extreme

  • A.能消除固定窗口的临界突发问题← 正确答案
  • B.ZSET 实现时内存随请求量增长← 正确答案
  • C.滑动窗口会自动动态调整阈值
  • D.加权滑动窗口只需双计数器← 正确答案

答案:ABD。滑窗滚动淘汰旧记录,彻底封住边界漏洞,A 正确;精确实现用 ZSET 记录每个请求的时间戳,请求越多内存越大,B 正确;加权方案把窗口切成小格、每格一个计数器,所以只需少数计数器,D 正确。C 错误——滑窗只是统计手段,阈值仍需人工配置。

Q10. 下列哪些是常用的限流实现算法?

多选题 · easy

  • A.固定窗口计数器← 正确答案
  • B.滑动窗口计数器← 正确答案
  • C.令牌桶与漏桶← 正确答案
  • D.冒泡排序算法

答案:ABC。固定窗口计数、滑动窗口计数、令牌桶和漏桶都是经典限流算法,分别对应“整点盘点”“滚动盘点”“攒币入场”和“匀速流水线”。冒泡排序是排序算法,与限流无关,属于干扰项。