想象你开了一家网红奶茶店。后厨只有三个店员,一分钟最多能做十二杯。有一天品牌突然爆火,一秒钟涌进来两百条订单——后厨炸了,机器烧了,顾客等了四十分钟,差评淹没评论区。痛定思痛,你决定在门口装一道“流量阀门”:不管多少人想喝,进了店门就得守规矩。你先后试过几种制度:整点限号、滚动统计、发接单券、订单漏斗……这些制度,就是 API 世界里的“速率限制算法”。这篇文章,咱们就从这家奶茶店出发,把令牌桶(Token Bucket)、滑动窗口(Sliding Window)这些听起来高深的名字,讲得明明白白。

岗位关联

理解限流算法是设计可靠 API 服务的必修课——掌握令牌桶与滑动窗口,你就能为 AI 服务筑起第一道抗流量冲击的防线,这也是后端与 AI 工程化岗位的高频考点。

目录
  1. 可视化演示
  2. 一、限流基础认知
  3. 二、固定窗口算法
  4. 三、滑动窗口算法
  5. 四、令牌桶算法
  6. 五、漏桶算法
  7. 六、分布式限流
  8. 七、选型与响应设计
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Token Bucket令牌桶与Sliding Window滑动窗口两种限流算法的工作原理,通过令牌生成消耗与窗口滑动的动态过程对比突发流量处理能力。

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

令牌桶算法:以固定速率生成令牌 令牌:以固定速率填充桶 请求:消耗令牌通过 rate = tokens/sec, burst = bucket_size 支持突发流量,桶满时丢弃溢出令牌,空时拒绝请求。

一、限流基础认知

给奶茶店装流量阀门之前,你先要回答三个问题:限多少、在哪限、拿什么来算。限多少,是估算后厨产能;在哪限,是定在点单台入口;拿什么算,是数订单条数。API 限流世界也是一样:先把这三个基础想清楚,后面的算法才有意义。

API 限流的本质是“准入控制”。服务容量是有限的,在请求真正消耗计算资源之前,先按预定规则决定放行还是拒绝。它是 API 治理与安全的第一道防线,和认证、鉴权并列,共同保护后端不被流量冲垮。

限流最常用的度量指标是 QPS(Queries Per Second,每秒查询数)。它衡量请求到达的速率,所有限流配额都以它为单位。限流位置一般选在 API 网关或服务入口中间件,这样可以在请求进入业务逻辑之前就完成拦截。

就像奶茶店在点单台就把超载订单拦下,而不是等后厨炸了再补救,限流把灾难挡在系统入口之外。一个合理的 QPS 阈值,就是你对自己后厨产能的诚实估算。

QPS=NreqT\displaystyle QPS = \frac{N_{req}}{T}
QPS 就是把“请求总数”除以“秒数”,相当于按秒算平均客流,限流配额以它为基准。
  1. N_req 表示请求总数在统计窗口内进入 API 的请求条数,分子越大,速率越高。
    NreqN_{req}
  2. T 表示统计时长以秒为单位的时间长度,分母越大,平均速率越低。
    TT
  3. 除法得到每秒速率请求数除以秒数,就是每秒查询量,用来对齐配额上限。
    NreqT\frac{N_{req}}{T}

二、固定窗口算法

你最先想到的规则是“整点放号”:每个小时最多发 100 个号,9 点、10 点、11 点……每个整点计数器清零重来。这就是固定窗口算法的直觉。

固定窗口把时间轴切成等长的片段,每个片段配一个独立的计数器。请求到达时,先判断自己落在哪个窗口,再给对应计数器加一;当计数器达到配额上限 L,后续请求被拒绝,直到下一个窗口开始。

它最大的优点是简单:进程内一张哈希表,或者 Redis 里一个带过期时间的 key 就能实现,几乎零额外开销。但它有一个著名的缺陷——窗口边界突发。

想象 8:59 你接了 100 单,9:00 刚清零又接了 100 单。两分钟内 200 个订单涌进后厨,固定窗口的防线在边界处形同虚设。于是你意识到:必须改成“滚动”的统计方式。

Ck=∑i1[kΔ≤ti<(k+1)Δ],Ck≥L⇒拒绝\displaystyle C_k = \sum_{i} \mathbb{1}\left[k\Delta \le t_i < (k+1)\Delta\right], \quad C_k \ge L \Rightarrow \text{拒绝}
固定窗口每过一个固定的时间格就清零重数,看似简单,但窗口交替的瞬间容易被钻空子。
  1. Δ 窗口长度时间格子的跨度,比如 60 秒,它决定了计数清零的频率。
    Δ\Delta
  2. kΔ 窗口起点第 k 个窗口的起始时刻,窗口编号从 0 开始递增。
    kΔk\Delta
  3. t_i 请求到达时间每个请求的时间戳,它决定请求被算进哪个窗口。
    tit_i
  4. L 配额上限单窗口最多放行的请求数,超过即拒绝。
    ∑i1[kΔ≤ti<(k+1)Δ]≥L\sum_{i} \mathbb{1}[k\Delta \le t_i < (k+1)\Delta] \ge L

三、滑动窗口算法

你改进了放号规则:不再看整点,而是挂一块“滚动看板”,永远显示“从现在往前推 1 小时”有多少单。10:00:30 看的是 9:00:30 到 10:00:30 的订单数。8:59 的 100 单和 9:00 的 100 单,终于不会被当成两个独立窗口了。

滑动窗口(Sliding Window)统计的是一个持续向前滚动的区间 $[t-\Delta, t)$。每来一个请求,窗口右端就推进到当前时刻,同时丢弃窗口左端滑出去的旧请求。计数永远反映“最近 Δ 秒”的真实负载。

最精确的实现是“滑动日志”:记录每个请求的时间戳,新请求到达时先删除窗口外的旧时间戳再计数,缺点是按请求量消耗内存。工程上常用分桶近似优化:把 Δ 切成 N 个小格,每个小格只保存计数,计算时按时间重叠比例加权求和,用少量精度换固定内存,这就是“滑动对数”的优化思路。

滑动窗口就是那块永远跟着现在走的看板,边界漏洞被彻底堵住。代价是:要么多花内存记日志,要么接受分桶近似的误差。精度和成本,永远是一对需要权衡的兄弟。

C(t)=∑ti∈[t−Δ, t)1,C(t)≥L⇒拒绝\displaystyle C(t) = \sum_{t_i \in [t - \Delta,\, t)} \mathbb{1}, \quad C(t) \ge L \Rightarrow \text{拒绝}
滑动窗口永远统计“从当前时刻往前推 Δ 秒”的请求数,窗口跟着当前时刻一起走,边界钻空子的机会被堵死了。
  1. t 当前时刻滑动窗口的右端点,随每个请求实时前进,窗口因此“滑”动。
    tt
  2. Δ 窗口长度向前回看的时长,决定统计范围的大小。
    [t−Δ, t)[t - \Delta,\, t)
  3. 滚动区间左闭右开区间内的所有请求时间戳都会被计入,滑出区间的自动失效。
    ∑ti∈[t−Δ, t)1\sum_{t_i \in [t - \Delta,\, t)} \mathbb{1}
  4. 配额判断计数达到 L 即拒绝,与固定窗口相同,但区间随时在移动。
    C(t)≥LC(t) \ge L

四、令牌桶算法

后来你换了更聪明的制度——“接单券”。收银台放一个券盒,每分钟自动掉进 5 张券,盒子最多装 10 张。顾客下单必须先交 1 张券。生意冷清时券攒满盒子,突然来一拨团购也能连做 10 杯;券用完了,就得等下一批券生成。

这就是令牌桶算法(Token Bucket)。它有两个核心参数:令牌生成速率 r 决定长期平均速率,桶容量 C 决定最大突发能力。请求到达时,桶里有令牌就消耗一个并放行,没有就拒绝或等待。

令牌的补充不是靠定时任务,而是“懒更新”:等请求来了,才用当前时间与上次更新时间的时间差,一次性补齐该补的令牌。这既精确又高效。桶满时令牌不再累积,保证空闲再久,突发也不会超过 C。

冷清时攒券、高峰时连做十杯,这正是令牌桶在真实世界中最受欢迎的原因:它不但限速,还允许合理突发,把“平均保护”和“用户体验”都照顾到了。市面上绝大多数 API 网关的默认限流器,就是它。

N(t)=min⁡(C, N(t0)+r×(t−t0))\displaystyle N(t) = \min\left(C,\ N(t_0) + r \times (t - t_0)\right)
令牌桶相当于一个“会自动补充、有上限的令牌仓库”:有令牌就放行,没令牌就等或拒,平均速率受生成速率约束,突发幅度受容量约束。
  1. N(t) 当前令牌数t 时刻桶里剩余的令牌数量,请求消耗它,时间补充它。
    N(t)N(t)
  2. r 令牌生成速率每秒向桶中补充的令牌数,决定长期平均每秒放行多少请求。
    r×(t−t0)r \times (t - t_0)
  3. 时间差补令牌$t - t_0$ 是距离上次更新的真实流逝时间,乘上 r 就是这段时间应补的令牌。
    N(t0)+r×(t−t0)N(t_0) + r \times (t - t_0)
  4. min 容量约束补完也不能超过 C,令牌最多攒满一桶,避免无限累积。
    min⁡(C, N(t0)+r×(t−t0))\min\left(C,\ N(t_0) + r \times (t - t_0)\right)

五、漏桶算法

如果你追求的是“绝对平稳”,接单券还不够。你可能会想:所有订单先进一个等待队列,后厨不管外面多火爆,都按固定速度取单,比如每十秒做一杯;队列满了就拒收新单。这就是漏桶(Leaky Bucket)的直觉——一个订单漏斗。

漏桶有两个参数:队列容量 B 和恒定输出速率 r。请求先入队,后端按 r 的固定节奏消费队列。与令牌桶“有令牌就放、没令牌再看”的概率式放行不同,漏桶是确定性的整形器:输入再乱,输出都像涓涓细流。

工程上,漏桶适合对下游稳定性要求极高的场景,例如批量写数据库、消息推送、对接第三方受限接口。它的代价是引入了排队延迟:突发请求可能要在队列里等很久,甚至被直接丢弃。

令牌桶和漏桶,一个像“发券限流”,一个像“漏斗整形”。前者关心能不能放行,后者关心怎么平稳输出。挑哪个,取决于你是怕“平均超速”,还是怕“瞬间洪水”。

q←q+1  (if q<B),q←max⁡(0, q−r)\displaystyle q \leftarrow q + 1 \;(\text{if } q < B), \qquad q \leftarrow \max(0,\ q - r)
漏桶把请求先装进队列,再按恒定速度放行,输出像涓涓细流一样平稳,多余的请求要么排队、要么溢出丢弃。
  1. q 队列积压量等待区排队的订单数,它是漏桶的状态核心。
    qq
  2. B 队列上限漏斗能容纳的最大积压量,达到上限后新请求被丢弃。
    q<Bq < B
  3. r 恒定输出速率后端每单位时间处理的请求数,不随输入波动。
    q←q−rq \leftarrow q - r
  4. max 保证非负队列处理完不会变成负数,用 max(0, ·) 截断。
    max⁡(0, q−r)\max(0,\ q - r)

六、分布式限流

生意好到开了 5 家分店,共用一个小程序点单。问题来了:如果每家店各挂一块自己的滚动看板或券盒,5 家店一分钟最多接的单子就会变成单店的 5 倍,中央厨房照样被淹没。你需要一本所有分店共享的“中央账本”。

这就是分布式限流的本质:当服务以多个实例部署时,单机限流在每台机器上各自计数,总配额会按实例数放大。正确做法是让所有实例共用一个集中式计数器。

最经典的实现基于 Redis:所有实例对同一个 key 执行 INCR 自增,第一次计数时设置 EXPIRE 过期时间,实现窗口自动翻转。为了在并发下保证“读计数-判断-自增”不被插队,通常把这段逻辑写成 Lua 脚本,由 Redis 原子执行。

中央账本的代价是多一次网络往返,以及 Redis 故障时的可用性风险——所以很多团队会再加一层本地限流兜底。分布式限流不是银弹,但它让“5 家分店共守一套配额”成为可能。

N=INCR(limitKey),N=1⇒EXPIRE(limitKey,Δ),N>L⇒拒绝\displaystyle N = \text{INCR}(\text{limitKey}), \quad N = 1 \Rightarrow \text{EXPIRE}(\text{limitKey}, \Delta), \quad N > L \Rightarrow \text{拒绝}
分布式限流用 Redis 这本“中央账本”计数,所有实例都来同一个键上记账,谁超了账本额度谁就不能再放行。
  1. INCR 原子自增Redis 的 INCR 命令把 limitKey 的值加 1 并返回新值,所有实例操作同一个键。
    N=INCR(limitKey)N = \text{INCR}(\text{limitKey})
  2. 首次设置过期当计数等于 1 时,说明这是新窗口的第一个请求,用 EXPIRE 给键设置 Δ 秒过期时间,窗口自动翻转。
    N=1⇒EXPIRE(limitKey,Δ)N = 1 \Rightarrow \text{EXPIRE}(\text{limitKey}, \Delta)
  3. 配额判断自增后的计数 N 超过配额 L,说明窗口已满,拒绝请求。
    N>L⇒拒绝N > L \Rightarrow \text{拒绝}
  4. Lua 原子执行把“读计数、判断、自增”写进一个 Lua 脚本整体执行,避免并发请求同时读到相同计数导致超额放行。
    EVAL(luaScript, limitKey, L, Δ)\text{EVAL}(\text{luaScript},\ \text{limitKey},\ L,\ \Delta)

七、选型与响应设计

阀门装好了,还得教顾客怎么配合。门口贴三句告示:今天已约满(对应 HTTP 429)、下午三点放号(对应 Retry-After 头)、别一直刷手机(对应退避重试)。这三件事,就是限流响应设计的全部。

先说选型。需要精确统计最近时间窗内请求量,选滑动窗口;容忍短期突发、想要体验平滑,选令牌桶;要求输出绝对恒定,选漏桶;服务多实例部署,就叠加 Redis 分布式限流。没有最好的算法,只有最合适的场景。

再看响应。被限流的请求,API 应统一返回 429 Too Many Requests,并在响应头中给出 Retry-After 指示重试时间。客户端收到 429 后,应采用指数退避策略重试,否则大量客户端同时重试会引发“重试风暴”,把刚缓过来的服务再次打垮。

好的流量阀门,不仅要会“关”,还要告诉别人“什么时候能再来”。限流不是把用户挡在门外,而是让服务在承受范围内持续可用——这正是 API 治理与安全的核心素养。

twait=min⁡(tmax, 2k×tbase)\displaystyle t_{wait} = \min\left(t_{max},\ 2^{k} \times t_{base}\right)
指数退避让客户端每次失败后等待时间翻倍,用越来越缓的重试节奏,换取服务恢复的时间。
  1. k 重试次数客户端连续收到 429 的次数,从 0 开始计数。
    kk
  2. t_base 基础等待时间第一次重试前的基础等待时长,例如 500 毫秒。
    tbaset_{base}
  3. 2^k 指数放大每次失败等待时间翻倍,第 1 次等 2×,第 2 次等 4×……
    2k×tbase2^{k} \times t_{base}
  4. t_max 上限保护防止等待时间无限增长,超过上限后保持固定等待。
    min⁡(tmax, 2k×tbase)\min\left(t_{max},\ 2^{k} \times t_{base}\right)

AI 应用场景

场景一:AI 大模型 API 网关的令牌桶限流

阶段:模型服务接入与流量治理

解决的问题:大模型推理成本高,单个失控脚本能在几秒内打满 GPU 配额。传统按 IP 封禁误杀率高,且无法应对“平时不用、高峰猛刷”的流量。用令牌桶按 API Key 限流:一个 Key 每分钟生成 60 个令牌、桶容量 120,就像给每个顾客发“接单券”,既限制平均调用速率,又允许合理的短时脉冲。

具体案例:某 AI 公司开放 chat/completions 接口,为免费用户设 60 req/min、付费用户 600 req/min。一个免费用户跑了个死循环脚本,令牌耗尽后被接连拒绝并返回 429,GPU 集群稳定运行,其他用户的请求完全不受影响。

场景二:电商大促的滑动窗口限流

阶段:业务后端流量保护

解决的问题:秒杀场景下,固定窗口在整点边界会出现双倍突发。10:00:59 放行 100 单、10:01:00 又放行 100 单,订单服务照样被打垮。滑动窗口统计“最近 60 秒”的真实订单量,边界漏洞被消除,像滚动看板一样时刻反映真实负载。

具体案例:某电商平台大促期间,用滑动窗口分桶近似实现网关限流,把下单接口稳定控制在 5000 QPS 以内。即使瞬间涌入 10 万用户,后端订单服务也始终保持健康,限流期间用户看到友好的“排队中”提示。

场景三:开放平台的分布式限流

阶段:多实例服务的统一配额管理

解决的问题:服务部署 10 个实例,每台各自限流会导致总配额放大 10 倍。用 Redis 中央账本 + Lua 原子脚本,让所有实例共享同一份配额,响应统一返回 429 并携带 Retry-After 时间。

具体案例:某开放平台为第三方应用分配每天 100 万次调用额度,基于 Redis 的分布式限流保证跨实例总量一致;超限时返回 429 + Retry-After,SDK 自动按指数退避重试,既保护平台,也让开发者获得清晰的重试指引。

Python 演示

python57 行
import timeimport threading  class TokenBucket:    """令牌桶限流器: 按速率补充令牌, 桶容量决定最大突发."""    def __init__(self, rate, capacity):        self.rate = rate           # 令牌生成速率(个/秒)        self.capacity = capacity   # 桶容量(最大令牌数)        self.tokens = capacity     # 初始满桶        self.last = time.monotonic()        self.lock = threading.Lock()     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 SlidingWindow:    """滑动窗口限流器: 统计最近window秒内的请求数."""    def __init__(self, limit, window):        self.limit = limit      # 窗口内最大请求数        self.window = window    # 窗口长度(秒)        self.requests = []      # 请求时间戳列表     def acquire(self):        now = time.monotonic()        self.requests = [t for t in self.requests if now - t < self.window]        if len(self.requests) < self.limit:            self.requests.append(now)            return True        return False  def simulate(name, limiter, total=12, interval=0.05):    print("===", name, "===", end="")    ok_cnt = 0    for _ in range(total):        time.sleep(interval)        if limiter.acquire():            ok_cnt += 1            print(" A", end="")        else:            print(" R", end="")    print("  -> 放行 %d/%d" % (ok_cnt, total))  if __name__ == "__main__":    simulate("TokenBucket rate=10 cap=5", TokenBucket(rate=10, capacity=5))    simulate("SlidingWindow limit=3 window=0.3", SlidingWindow(limit=3, window=0.3))
目的:用令牌桶和滑动窗口两个类分别实现限流器,并通过模拟请求序列观察“初始突发后开始拒绝”以及“窗口内超限被拒”的行为。
  1. self.tokens = min(self.capacity, self.tokens + (now - self.last) * self.rate)按时间差补齐令牌且不超过桶容量,这是令牌桶“懒更新”的核心。
  2. if self.tokens >= 1:判断桶里还有没有令牌,决定这次请求放行还是拒绝。
  3. self.tokens -= 1每放行一个请求,就从桶里拿走一个令牌。
  4. now = time.monotonic()使用单调时钟计时,不受系统改时间影响。
  5. self.requests = [t for t in self.requests if now - t < self.window]滑动窗口的核心:把窗口外的旧时间戳全部剔除。
  6. if len(self.requests) < self.limit:窗口内计数小于配额才允许新请求进入窗口。
  7. with self.lock:用互斥锁保护令牌读写,保证多线程环境下计数准确。

常见误区

误区初学者以为限流就是对超配额请求一律返回错误,收到 429 就像被判了死刑,用户体验一定很差。

正解限流是“准入控制”,拒绝只是策略之一。令牌桶允许积累突发,漏桶用队列缓冲,很多系统对超限请求先排队等待,只有队列满才拒绝。就像奶茶店客流大时可以请顾客排队,而不是直接赶人。

为何常见:限流名字里带“限”,容易让人联想到“禁止”,忽略了“排队、等待、降级”也都是限流的兄弟策略。

误区都是数一分钟内的请求,选实现更简单的固定窗口不就行了?反正长期平均速率差不多。

正解固定窗口在窗口边界存在双倍突发漏洞:8:59 的 100 单加 9:00 的 100 单,两分钟内放行 200 单。滑动窗口统计的是 [t−Δ, t) 滚动区间,边界两侧的请求被同一个窗口统计,漏洞被消除。对时延敏感、配额严格的服务,两者差异巨大。

为何常见:只看长期平均值两者确实接近,但压垮系统的往往是“瞬间峰值”,只看平均便看不到边界风险。

误区服务有 3 个实例,每个实例限 100 QPS,加起来就是 300 QPS,完全没问题。

正解每个实例独立计数时,集群总量会放大到单机配额乘实例数,而且负载不均时某个实例可能先被打爆。分布式限流用 Redis 等中央存储共享同一个计数键,才能保证集群总配额一致,就像多家分店共用一本账本。

为何常见:单机限流实现简单、直觉清晰,把“单机正确”误当成“集群正确”,是很自然但危险的思维惯性。

误区容量大意味着能存更多令牌、更不容易触发限流,正常用户不会被误伤,何乐而不为?

正解桶容量决定一次性突发量。容量设为 100,等于允许一瞬间连放 100 个请求进入后端。如果下游数据库或模型推理扛不住 100 并发,照样被打垮。容量必须结合下游容量评估来确定,而不是越大越好。

为何常见:只盯着“别误伤正常用户”,却忘了限流的第一目标是“保护下游”。

误区都是控制速率,名字里都带桶,反正都能限速,没必要区分。

正解令牌桶看的是“有没有令牌”:有就放、没有就拒或等,输出是突发加平滑的混合;漏桶看的是“队列满没满”:请求先进队列,后端按恒定速率处理,输出完全平滑。选令牌桶通常意味着接受突发,选漏桶则要求绝对恒定输出。

为何常见:两个算法都叫“桶”,图示都是容器装水,视觉上太像,容易让人忽略一个“发券”、一个“漏斗”的本质差异。

面试题

Q1. 令牌桶中“令牌生成速率”这一参数的主要作用是?

单选题 · easy

  • A.决定长期平均放行速率← 正确答案
  • B.控制桶最大容量大小
  • C.决定每次请求超时时间
  • D.管理服务器并发连接数

答案:A。令牌生成速率 r 决定长期平均放行速率:每秒补 r 个令牌,意味着长期每秒最多放行 r 个请求。桶容量 C 管的才是突发上限;超时和并发连接数与令牌桶参数无关。

Q2. 固定窗口算法最大的缺陷是什么?

单选题 · easy

  • A.窗口边界双倍流量突发← 正确答案
  • B.实现复杂且维护困难
  • C.需要大量额外存储开销
  • D.无法配置每秒请求配额

答案:A。固定窗口把时间切成 [kΔ, (k+1)Δ),窗口边界两侧的请求在时间上相邻,却分属两个窗口、各自能满配额放行,造成两倍突发。固定窗口实现其实很简单,存储开销也小,配额也可配置。

Q3. 滑动窗口相比固定窗口的核心改进是什么?

单选题 · mid

  • A.采用更长的时间窗口
  • B.滚动统计替代固定边界← 正确答案
  • C.增加请求排队缓冲机制
  • D.改用令牌桶算法控制

答案:B。滑动窗口把计数区间改为 [t−Δ, t),窗口随当前时刻滚动,边界两侧的请求会被同一个窗口统计,从而消除双倍突发。A 是调整窗口长度而非改进;C 是漏桶特征;D 是换算法而非改进。

Q4. 令牌桶允许突发流量的根本原因是什么?

单选题 · mid

  • A.令牌可以在桶中累积← 正确答案
  • B.请求可以一直排队等待
  • C.令牌生成速率非常高
  • D.桶的容量设置为零

答案:A。令牌桶允许突发是因为令牌可累积:空闲时令牌持续补充、桶满停止,高峰期可一次性消耗积累的令牌。排队是漏桶的特征;生成速率高不等于允许突发;容量为零反而完全不允许突发。

Q5. 漏桶算法输出速率的特点是什么?

单选题 · mid

  • A.随输入流量大小波动
  • B.输出速率恒定不变← 正确答案
  • C.与桶容量大小成正比
  • D.完全由突发流量决定

答案:B。漏桶的核心特性就是恒定输出:后端按固定速率处理队列,与输入流量无关。A 是普通队列的特征;输出速率与桶容量无关;突发被队列吸收,不会直接体现在输出上。

Q6. 分布式限流使用 Redis 时,为什么用 Lua 脚本实现计数?

单选题 · hard

  • A.提升网络数据传输速度
  • B.保证判断与递增原子执行← 正确答案
  • C.减少 Redis 的内存占用
  • D.便于跨机房的数据同步

答案:B。分布式限流的关键是“读计数、判断、自增”必须原子完成,否则并发请求会同时读到相同计数、一起放行导致超额。Lua 脚本在 Redis 中原子执行,避免竞态。A、C、D 都不是使用 Lua 的核心原因。

Q7. 收到 HTTP 429 响应后,客户端最合理的做法是?

单选题 · mid

  • A.立即无限重试直至成功
  • B.按响应重试时间等待重试← 正确答案
  • C.直接放弃所有后续请求
  • D.立刻提高请求的并发数

答案:B。429 响应通常携带 Retry-After 头或 JSON 字段,告知客户端等待多久再重试。客户端应遵守该时间重试,避免重试风暴。无限重试会加剧服务压力;直接放弃不合理;提高并发恰恰是限流要防御的行为。

Q8. 以下哪些属于常见的限流算法?

多选题 · easy

  • A.固定窗口算法← 正确答案
  • B.滑动窗口算法← 正确答案
  • C.令牌桶算法← 正确答案
  • D.快速排序算法

答案:ABC。固定窗口、滑动窗口、令牌桶都是经典限流算法;快速排序是排序算法,与限流无关。本题考察对限流算法家族的整体认知。

Q9. 关于令牌桶与漏桶的区别,以下说法正确的有哪些?

多选题 · hard

  • A.令牌桶允许短期突发← 正确答案
  • B.漏桶输出速率恒定← 正确答案
  • C.两者原理完全一致
  • D.漏桶需要队列缓冲← 正确答案

答案:ABD。令牌桶允许短期突发(桶内令牌累积),漏桶以恒定速率输出(需要队列缓冲),两者原理完全不同。C 说“完全一致”是错的——一个发券、一个漏斗,本质一放一整形。

Q10. 设计限流响应时,需要考虑哪些要素?

多选题 · mid

  • A.统一的 429 状态码← 正确答案
  • B.Retry-After 重试时间← 正确答案
  • C.清晰的错误提示信息← 正确答案
  • D.完整的服务器运行日志

答案:ABC。好的限流响应应统一返回 429 状态码,携带 Retry-After 指示重试时间,并给出清晰的错误信息让调用方理解原因。把服务器运行日志直接暴露给用户既不安全也不合理。