“青云游乐园”开园第三年,经理老周焦头烂额。每逢周末,园门口乌泱泱挤着两三万人,检票闸机被蜂拥而至的游客堵死,过山车排队排到园外百米,连园内的小卖部都因为瞬时订单过载而频繁罢工。游客骂声一片,设备频频告警,老周意识到:问题不是“游客太多”,而是“游客在同一个时刻涌进来”。 于是他想明白了一个道理——与其让所有人挤在门口谁也进不去,不如设计一套“入园管理系统”:有人排队等候,有人快速通过,让每一秒入园的人数都控制在设备能承受的范围内。这套系统,就是计算机世界里的限流算法(Rate Limiting)。 本文要聊的令牌桶、漏桶与滑动窗口,正是老周设计“闸门”时用到的三种核心方案。它们解决同一个问题:如何在洪峰式的流量冲击下,保护后端的服务器、数据库和第三方 API 不被击垮。读完这篇文章,你不仅会理解这三种闸门的原理与代码实现,还会懂得在真实的高并发系统里,该在哪个位置安放它们。

岗位关联

对 AI 从业者而言,限流是模型服务和数据管道必备的“安全阀”。掌握令牌桶与滑动窗口,意味着你能在设计 API 网关、模型推理服务和实时数据流时,从容应对流量尖峰,守住系统稳定的底线。

目录
  1. 可视化演示
  2. 限流基础目标
  3. 令牌桶算法
  4. 漏桶算法
  5. 滑动窗口算法
  6. 算法选型对比
  7. 代码实现要点
  8. 分布式限流实战
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示三大限流算法:令牌桶按速率生成令牌、漏桶匀速流出请求、滑动窗口统计时间窗内请求数。切换模式对比各算法原理,拖动滑块调节请求速率。

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

令牌桶算法原理 令牌桶以固定速率生成令牌放入桶中,桶有最大容量限制;每个请求到达时需获取一个令牌,有令牌则通过无令牌则拒绝 允许突发流量:桶满时积累的令牌可应对短时突发,突发上限=桶容量;稳态吞吐=令牌生成速率 pass = tokens > 0; tokens = min(capacity, tokens + rate * dt) 令牌桶适合允许突发的场景

限流基础目标

老周被周末的游客吓怕了,他翻出园区的设备手册:每台过山车每小时最多接待 600 人,小卖部每分钟最多处理 20 笔订单。一旦超过这个数字,系统就会过热、卡顿甚至宕机。老周意识到,他需要的不是“不让游客来”,而是把每一秒进来的游客数量,控制在设备的额定容量之内。这就是限流的第一性原理:保护系统的可用性。

在计算机世界里,这个道理一模一样。你的服务器、数据库、第三方 API 都有各自的“额定容量”——吞吐量上限。平时流量平稳时一切正常,可一旦遇到秒杀、热点事件或恶意爬虫,请求量可能在几秒内飙升十倍。如果不加限制,数据库连接池会耗尽、CPU 会打满、整个服务雪崩式宕机。

限流的核心目标是削峰填谷:把瞬时洪峰削成平缓的长流水,让系统始终工作在安全区间。值得注意的是,限流与“拒绝用户”有本质区别——它更像游乐园门口的“排队缓冲区”,不是把游客赶走,而是让他们有序地等待。理解了这一点,后面所有算法的设计逻辑就都串起来了。

text1 行
请求 → [限流器:每秒放行 N 个] → 后端服务

令牌桶算法

老周的第一版方案很像“售票积分”:他安排了一台自动售票机,每秒往桶里放入一张新票,桶最多存放 5 张。游客来了,先从桶里取一张票,取到就入园,取不到就过会儿再来。周末人多的时候,机器持续不断地产票;人少的时候,票就攒在桶里,等到人流高峰可以“预支”一部分。这套机制,就是令牌桶算法(Token Bucket)。

令牌桶的两个核心参数是令牌生成速率 \(R\) 和桶容量 \(B\)。每个请求必须消耗一个令牌才能通过;令牌由后台进程以速率 \(R\) 持续补充;桶的容量 \(B\) 限定了最多能积攒多少令牌——也就决定了系统能容忍的瞬时突发上限。

有趣的是,令牌桶巧妙地将“允许突发”和“长期平滑”统一了起来。因为桶里可以预先积攒令牌,所以当流量洪峰来临时,系统能迅速“烧掉”积累的令牌,应对短暂的流量尖峰;只要持续时间超过令牌补充的节奏,速率就会被 \(R\) 牢牢锁住。这就像游乐园里平时没人来的时候攒了一叠票,周末高峰可以快速消化一阵子,但票用完后就只能按每秒一张的速度慢慢进了。

tokensnow=min⁡(B, tokenslast+(tnow−tlast)×R)\displaystyle \text{tokens}_{now} = \min\left(B,\ \text{tokens}_{last} + (t_{now} - t_{last}) \times R\right)
通俗地说:桶里现在的令牌数 = 上次剩余的 + 这一小段时间新生成的,但最多不能超过桶的容量。
  1. 上次剩余令牌tokens_last 是上一次请求后桶里剩下的令牌数,它是计算的起点。
    tokenslast\text{tokens}_{last}
  2. 经过的时长(t_now - t_last) 是从上一次补充到现在的时间差,单位是秒;时间差越大,新生成的令牌越多。
    tnow−tlastt_{now} - t_{last}
  3. 新生成令牌将时间差乘以生成速率 \(R\),得到这段时间内新印出的令牌数。
    (tnow−tlast)×R(t_{now} - t_{last}) \times R
  4. 桶容量截断用 \(\min\) 函数把结果限制在桶容量 \(B\) 以内,多出来的令牌直接丢弃,防止积攒无限多。
    min⁡(B, tokenslast+(tnow−tlast)×R)\min\left(B,\ \text{tokens}_{last} + (t_{now} - t_{last}) \times R\right)

漏桶算法

老周试了几天令牌桶,发现一个问题:令牌桶允许突发,可小卖部的收银员吃不消——一次性涌来 20 个人,虽然系统没崩,但收银员手忙脚乱。老周换了个思路:不搞“攒票”了,直接改成匀速放行。他在入园通道里装了一个“旋转闸机”,无论外面挤了多少人,闸机固定每 2 秒转一圈、放一个人进去。这就是漏桶算法(Leaky Bucket)。

漏桶算法的模型非常直观:把流量想象成倒入漏斗的水,水从漏斗底部的小孔匀速滴出。不管上方一次性倒入多少水,底下滴出的速度永远是恒定的 \(R\)。如果桶里已经积满了水,新来的请求(水)就会溢出被丢弃——这个“溢出”就是限流中的拒绝。

漏桶和令牌桶最大的区别,在于漏桶不允许任何突发。它的输出曲线是一条几乎水平的直线,流量被彻底“抹平”。代价是:当洪峰到来时,来不及处理的请求会被直接拒绝(或者进入一个队列慢慢等)。因此漏桶特别适合那些对流量平滑度要求极高、无法接受突发冲击的下游系统,比如真实世界里的数据库写入、短信发送通道。

next_pass_time=max⁡(tnow, last_pass_time)+1R\displaystyle \text{next\_pass\_time} = \max(t_{now},\ \text{last\_pass\_time}) + \frac{1}{R}
这个公式的含义是:下一位游客最早通过闸门的时间,等于“当前时间和上一位通过时间中的较晚者”加上“每人所需的固定间隔”。
  1. 上一人的通过时间last_pass_time 记录了上一个请求被放行的时刻。
    last_pass_time\text{last\_pass\_time}
  2. 取较晚者\(\max(t_{now}, \text{last\_pass\_time})\) 保证即使请求在短时间内大量到达,也必须等待上一人处理完。
    max⁡(tnow, last_pass_time)\max(t_{now},\ \text{last\_pass\_time})
  3. 加入服务间隔加上 \(\frac{1}{R}\),即每个请求享受的固定时间窗,\(R\) 越大,间隔越短。
    1R\frac{1}{R}

滑动窗口算法

老周后来又遇到一个刁钻的漏洞。他规定“每分钟最多入园 100 人”,但发现有人在第 59 秒的最后一刻冲进来 90 人,第 61 秒又冲进 90 人——在短短 3 秒内,园内多了 180 人,完全失控。问题出在哪?因为他用的是“固定窗口”:每分钟重置一次计数器,窗口交界处的请求可以绕过限制。于是老周想到了滑动窗口(Sliding Window):把一分钟切成 6 个 10 秒的小格子,每过 10 秒整个窗口就往前挪一格,始终统计“最近 60 秒”的总人数。

滑动窗口的核心思想是用细粒度的时间切片来逼近“任意连续窗口内不超过限制”这一理想目标。格子越细,统计越精确,但内存开销也越大。一个常用的简化实现是“滑动日志”:记录每个请求到达的时间戳,清理掉超出窗口范围的时间戳,剩下的计数就是当前窗口的请求数。

滑动窗口的优势在于精确且不会出现边界突刺,特别适合登录失败次数限制、验证码发送频率这类“固定周期内次数敏感”的场景。它的短板在于需要维护连续时间段的计数状态,内存开销比前两种算法略高。对老周来说,滑动窗口就像一本“滚动记账本”——每时每刻都只看最近十分钟入园了谁,超了就在门口拦一下。

N^=Cprev×wremainwslot+Ccurrent\displaystyle \hat{N} = C_{prev} \times \frac{w_{remain}}{w_{slot}} + C_{current}
这是滑动窗口的“估算”公式:当前窗口总请求数 ≈ 上一个窗口中仍处于当前窗口部分的占比,加上本窗口已有的请求数。
  1. 上一格与当前格计数\(C_{prev}\) 是上一个时间格子的计数,\(C_{current}\) 是本格子的计数。
    Cprev, CcurrentC_{prev},\ C_{current}
  2. 剩余占比\(\frac{w_{remain}}{w_{slot}}\) 表示上一个格子的数据还有多少比例落在当前滑动窗口内;越靠近期末,占比越小。
    wremainwslot\frac{w_{remain}}{w_{slot}}
  3. 合并估算把按比例折算的上格计数与本格计数相加,得到对当前窗口总量的平滑估计,避免固定窗口的“边界突刺”。
    N^=Cprev×wremainwslot+Ccurrent\hat{N} = C_{prev} \times \frac{w_{remain}}{w_{slot}} + C_{current}

算法选型对比

集齐了三种闸门方案,老周把它们摆在一起对比,发现各有各的脾气。他总结出一张简单的“选型口诀”:要容忍瞬时爆发,选令牌桶;要强制匀速稳定,选漏桶;要精确控制周期内次数,选滑动窗口。

从设计目标看,令牌桶有“两个旋钮”(生成速率 \(R\) 和桶容量 \(B\)),灵活性最高。桶容量越大,对突发流量的容忍度越高;比如电商大促开场前可以预热令牌,让商品页扛住第一波抢购。漏桶只有“一个旋钮”——流出速率 \(R\),一切突发都被队列吸收或丢弃,适合对接无法抗峰的下游依赖,比如调用银行接口或发送短信。而滑动窗口提供的是“精确到秒级”的计数能力,不关心速率快慢,只关心“在任意一个窗口里别超过 N 次”。

具体到工程实践,选型的判断依据可以归纳为三点:业务对突发是否敏感、系统能接受多大的状态存储开销、统计口径是速率还是次数。Nginx 的 limit_req 模块用的是漏桶变体,它强调平滑地处理请求;Google Guava 的 RateLimiter 是经典的令牌桶实现;而社交平台的“每分钟最多发送 5 条私信”这类限制,用的则是滑动窗口。没有绝对最好的算法,只有当前业务场景下最合适的选择。

代码实现要点

老周打算把这套逻辑写成代码,他找到了园区的程序员小刘。小刘提醒他:实现限流器有三个容易踩坑的地方。第一是原子性——多线程或分布式环境下,多个请求同时检查并扣减令牌,必须保证操作是原子的,否则会出现两人合用一张票的情况。单机可以用锁(Lock),分布式则要用 Redis 的 Lua 脚本。

第二是时间源。很多人用 System.currentTimeMillis() 获取当前时间,但系统时钟可能被 NTP 校准而回调,导致限流失灵。正确做法是使用单调时钟(如 Java 的 System.nanoTime()、Python 的 time.monotonic()),它只保证“向前走”,不受墙钟跳变影响。

第三是内存与精度的权衡。滑动窗口如果每个请求都记录时间戳,内存开销会随请求量线性增长;工程上常用“分桶计数”来压缩——比如按秒分桶、只存每个桶的计数器,牺牲少量精度换取稳定的内存占用。对于令牌桶,实现时不必真的开一个后台线程去生成令牌——只需要记录上次补充时间,用“惰性计算”在每次请求时补算增量即可,这与我们前面讲的“生成本质是时间函数”完全一致。

分布式限流实战

单机限流再成熟,也解决不了老周的新烦恼:游乐园开了三个入口,每个入口各自数人头,结果三扇门加起来放进了三倍的人。这就是分布式限流要解决的问题——多个实例共享同一个全局计数器,谁也不能各自为政。最通用的方案是引入 Redis 作为集中式存储,所有实例的请求都到 Redis 里换取放行资格。

但这里有个隐忧:Redis 操作不是天然原子的。“检查计数是否超限”和“计数加一”是两步操作,多实例并发执行时会产生竞态条件。小刘给出的解法是用 Lua 脚本——把检查、计数、扣减三个动作封装进一个 Redis 的 Lua 脚本,Redis 保证脚本整体的原子性。这样一来,无论多少台机器并发请求,只要发给 Redis 的脚本是同一个,最终的计数就是精确的。

分布式限流最常见的落点,是API 网关(如 Kong、Nginx + Lua、Spring Cloud Gateway)上的全局限流插件。网关前置在服务集群入口,统一配置限流策略,按用户 ID、IP 或接口维度限流。比如在秒杀场景中,网关层用令牌桶限定“每秒最多放行 5000 个请求”进入后端,后端再叠加滑动窗口限制“每个用户每分钟最多下单 1 次”——多级限流各司其职,系统才能在高并发洪峰中安稳运行。老周的故事也终于有了圆满结局:游乐园三个入口共享同一个“总闸”,设备和游客都得到了最好的安排。

AI 应用场景

API 网关的全局流量闸门

阶段:模型推理服务上线前的流量治理

解决的问题:大模型 API 服务在热点时段可能收到远超推理集群吞吐量的请求。没有限流时,GPU 显存被打满,推理延迟从 200ms 飙升到 5 秒,甚至引发 OOM 崩溃。使用令牌桶在网关上统一限流,可以按“每秒请求数”控制进入推理集群的流量。

具体案例:某 LLM 服务商在 API 网关配置令牌桶,速率 \(R=100\)、桶容量 \(B=20\),允许短时突发 20 个并发请求,同时保证长期每秒不超过 100 个。高峰期排队请求先进入 MQ 缓冲,避免直接压垮推理引擎。

爬虫与恶意请求的滑动窗口防护

阶段:数据采集与内容安全防护

解决的问题:搜索引擎爬虫和恶意脚本经常在“固定窗口重置”的瞬间集中出击,规避按小时/按分钟重置的计数限制。滑动窗口把窗口切成细格子,任意连续窗口内请求数都不会超限。

具体案例:某内容平台对“单 IP 每分钟抓取次数”应用滑动窗口限流(窗口 60 秒、子窗口 5 秒),当 AI 爬虫在窗口交界处高频抓取时,请求被精准拦截,同时正常用户的访问不受影响。

Python 演示

python68 行
import threadingimport timefrom collections import deque  class TokenBucket:    """令牌桶限流器"""     def __init__(self, rate, capacity):        self.rate = rate        self.capacity = capacity        self.tokens = capacity        self.last_refill = time.monotonic()        self.lock = threading.Lock()     def try_acquire(self, need=1):        with self.lock:            now = time.monotonic()            self.tokens = min(                self.capacity,                self.tokens + (now - self.last_refill) * self.rate            )            self.last_refill = now            if self.tokens >= need:                self.tokens -= need                return True            return False  class SlidingWindowCounter:    """滑动窗口限流器:按秒分桶统计"""     def __init__(self, window_sec, limit):        self.window_sec = window_sec        self.limit = limit        self.buckets = deque()        self.lock = threading.Lock()     def try_acquire(self):        with self.lock:            now = time.time()            while self.buckets and self.buckets[0][0] <= now - self.window_sec:                self.buckets.popleft()            total = sum(cnt for _, cnt in self.buckets)            if total >= self.limit:                return False            if self.buckets and self.buckets[-1][0] == int(now):                sec, cnt = self.buckets[-1]                self.buckets[-1] = (sec, cnt + 1)            else:                self.buckets.append((int(now), 1))            return True  def simulate_requests(name, limiter, total):    allowed = 0    for i in range(total):        if limiter.try_acquire():            allowed += 1        time.sleep(0.01)    print(f"{name}: {total} 次请求,放行 {allowed} 次")  if __name__ == "__main__":    bucket = TokenBucket(rate=5, capacity=3)    window = SlidingWindowCounter(window_sec=10, limit=3)    simulate_requests("令牌桶(速率5,容量3)", bucket, 20)    simulate_requests("滑动窗口(10秒限3次)", window, 20)
目的:用 Python 从零实现令牌桶和滑动窗口两个限流器,通过模拟 20 次连续请求观察放行与拒绝的分布,直观对比两种算法的限流效果差异。
  1. time.monotonic()获取单调时钟时间,避免系统时钟跳变导致限流计算错误。
  2. self.tokens + (now - self.last_refill) * self.rate惰性补充令牌:不靠后台线程,只在请求时按时间差补算。
  3. min(self.capacity, ...)令牌数不能超过桶容量,超出的令牌直接丢弃。
  4. with self.lock:加锁保证多线程环境下“检查-扣减”两步的原子性。
  5. self.buckets.popleft()清理超过窗口时间范围的过期时间桶,控制内存占用。
  6. sum(cnt for _, cnt in self.buckets)把窗口内所有分桶的计数加起来,判断是否超限。

常见误区

误区我觉得限流就是把多余的请求拒绝掉,把用户赶走。

正解限流的本质是“延迟满足”而非“拒绝”。就像游乐园门口排队,游客不是被赶走,而是被安排在缓冲区等待。工程上,被限流的请求可以进入消息队列排队、返回 503 让客户端稍后重试,或走降级逻辑返回缓存结果。真正拒绝用户是最差的策略,优先选择排队与降级。

为何常见:因为限流器最常见的表现就是返回错误码,初学者只看到“被拒绝”的表象,看不到背后的排队与容量保护设计。

误区令牌桶可以积攒令牌,所以它能无限承受突发流量。

正解桶容量 \(B\) 就是突发上限。每秒生成 \(R\) 个令牌,桶最多存 \(B\) 个,所以任何瞬间最多只能消耗 \(B\) 个令牌,突发量被 \(B\) 硬性约束。比如 \(R=10, B=20\),意味着你最多只能一次性放行 20 个请求,之后就得等令牌慢慢重新积攒。

为何常见:只记住了“令牌桶允许突发”的优点,忽略了隐藏在公式里的容量上限参数,这是没把 \(\min(B, \dots)\) 这一截断理解透。

误区我按每分钟重置计数器限流就行,滑动窗口太复杂没必要。

正解固定窗口在窗口交界处存在“双倍流量漏洞”:第 59 秒和第 61 秒各放行一批,但两批在时间上几乎连续,总流量翻倍。滑动窗口通过细粒度分桶解决了这个边界突刺问题。计数器类限流(如登录次数、发帖频率)正式场景中必须用滑动窗口。

为何常见:小流量场景下固定窗口的漏洞不常暴露,只有高峰流量压测时才会发现计数器重置瞬间的流量穿透。

误区每个服务实例部署一个独立的限流器,总量限制自然就实现了。

正解假设 3 个实例各自限 100 QPS,总量会放大到 300 QPS,完全失去保护意义。分布式限流必须共享统一计数状态(通常放 Redis),或使用集中式网关限流。单机限流只适合完全独立、不共享下游的场景。

为何常见:习惯单机开发的程序员容易把“单机能力”线性外推为“集群能力”,忽略了多实例叠加后的流量放大效应。

误区每来一个请求就做一次计数检查,Redis 网络交互岂不是很慢?

正解单机令牌桶用惰性计算和局部变量即可,开销是纳秒级的;分布式场景也可以结合“本地令牌桶 + 周期同步 Redis”的混合方案,把 Redis 交互摊薄到每毫秒一次批量取 token。真正的性能瓶颈是业务逻辑,而不是限流器本身。

为何常见:把“每次请求都走 Redis 计数”当成唯一实现方案,忽略了批量、本地缓存等优化手段。

面试题

Q1. 令牌桶算法中,桶容量 B 的核心作用是?

单选题 · easy

  • A.控制令牌的生成速度
  • B.限制突发的最大请求量← 正确答案
  • C.控制请求的处理时长
  • D.决定拒绝请求时的错误码

答案:B。桶容量 B 让令牌可以积攒,但总量被 B 封顶,因此它决定了系统能承受的最大突发请求量。生成速度由速率 R 控制,与 B 无关,所以 A 错误;C 和 D 与令牌桶设计无关。

Q2. 下列哪个场景最适合使用漏桶算法?

单选题 · easy

  • A.允许短时突发的大促秒杀入口
  • B.需要严格匀速调用第三方短信接口← 正确答案
  • C.统计每分钟用户登录失败次数
  • D.控制 API 网关的全局 QPS 上限且容忍突发

答案:B。短信供应商通常限制每秒发送条数且不接受突发,漏桶强制恒定速率最合适。允许突发应选令牌桶(A、D);按周期统计次数应选滑动窗口(C)。

Q3. 滑动窗口算法相比于固定窗口的最大优势是?

单选题 · mid

  • A.内存占用更低
  • B.实现代码更简单
  • C.消除窗口边界处的流量突刺← 正确答案
  • D.不需要记录请求时间戳

答案:C。滑动窗口把窗口切分成小格子并随时间滑动,避免了固定窗口在边界重置瞬间被双倍流量穿透的问题。滑动窗口需要维护分桶计数,内存更高、实现更复杂,A、B 均与实际相反。

Q4. 令牌桶算法中,若速率 R=10、桶容量 B=20、桶已满,第 2 秒时一次性到达 30 个请求,前 20 个放行后,第 21 个请求需要等待约多少秒?

单选题 · mid

  • A.0 秒,直接放行
  • B.1 秒
  • C.2 秒← 正确答案
  • D.3 秒

答案:C。20 个令牌瞬间消耗完后,令牌以每秒 10 个的速度补充。第 21 个请求需要再等 1 秒攒够 1 个令牌,第 30 个请求则要等约 1 秒。注意题目问“第 21 个请求”,所以答案是 1 秒左右,选 B 更严谨,但按题意,需要等令牌生成 1 个,故为 1 秒。

Q5. 以下哪些属于限流算法的核心目标?

多选题 · mid

  • A.削峰填谷,平滑流量← 正确答案
  • B.保护后端资源不过载← 正确答案
  • C.提升单次请求的处理速度
  • D.在洪峰下保障整体可用性← 正确答案

答案:ABD。限流追求的是系统整体的稳定和资源保护,包括削峰填谷、防止过载、保障可用性。它并不能提升单个请求的速度,C 是性能优化而非限流目标。

Q6. 在分布式限流中,使用 Redis + Lua 脚本的根本原因是?

单选题 · hard

  • A.Lua 脚本运行速度比 Java/Python 快
  • B.Redis 保证 Lua 脚本整体执行的原子性← 正确答案
  • C.Lua 脚本可以自动生成限流规则
  • D.Redis 不需要网络通信开销

答案:B。多实例并发时,“检查计数-扣减”两步存在竞态条件,而 Redis 以单线程执行 Lua 脚本,天然保证整套逻辑的原子性。性能并非主因,B 准确揭示了技术选型的本质。

Q7. 下列关于三种限流算法的说法,正确的有?

多选题 · mid

  • A.令牌桶允许一定程度的突发← 正确答案
  • B.漏桶的输出速率严格恒定← 正确答案
  • C.滑动窗口解决边界突刺问题← 正确答案
  • D.令牌桶严格禁止任何突发流量

答案:ABC。令牌桶通过桶容量积攒令牌,允许受限的突发;漏桶以固定速率流出,输出完全平滑;滑动窗口通过细粒度分桶消除固定窗口的边界漏洞。D 与令牌桶的特性相反,故不选。

Q8. Nginx 的 limit_req 模块默认采用哪种限流算法?

单选题 · easy

  • A.令牌桶
  • B.漏桶← 正确答案
  • C.滑动窗口
  • D.固定窗口

答案:B。Nginx limit_req 基于漏桶思想实现,请求进入一个队列(桶)并以固定速率被处理,超出队列容量的请求直接返回错误。令牌桶是 Guava RateLimiter 的典型实现,注意区分。

Q9. 滑动窗口分桶计数中,子窗口粒度从 10 秒改为 1 秒后,可能带来什么后果?

单选题 · hard

  • A.限流精度下降
  • B.内存开销上升但限制更精准← 正确答案
  • C.完全消除令牌生成的等待
  • D.不再需要清理过期窗口

答案:B。子窗口越细,滑动窗口对真实流量的逼近越精确,但需要维护的桶数量增加、内存开销上升。精度提升而非下降,A 错误;C、D 与滑动窗口机制无关。

Q10. 在高并发 API 网关中,通常如何组合多种限流策略?

多选题 · extreme

  • A.网关层用令牌桶控制总 QPS← 正确答案
  • B.业务层用滑动窗口限制单用户频率← 正确答案
  • C.用固定窗口替代滑动窗口以减少内存
  • D.用 Redis Lua 脚本实现跨实例原子计数← 正确答案

答案:ABD。生产环境常采用多级限流:网关令牌桶兜底全局 QPS,业务层滑动窗口控制单用户周期内次数,跨实例用 Redis Lua 保证计数原子性。C 中用固定窗口替代滑动窗口会引入边界突刺问题,不正确。