「川香居」火锅店周五晚上最热闹,门口等位的队伍能绕两圈。王阿姨是门口的叫号员,她心里门儿清:店里 30 张桌子,后厨 6 口锅,一小时最多翻三轮台,满打满算只能接待 90 桌客人。如果敞开大门让所有人都往里冲,后厨出菜跟不上,客人干等半小时,差评满天飞。于是王阿姨立下一条规矩——过去一小时内,最多放进 90 桌。这条「规矩」在计算机世界里就叫限流。 那怎么数这 90 桌呢?笨办法是每小时换一张纸,纸上记满 90 个「正」字就关门,这就是固定窗口;聪明的办法是拿一卷长纸,记下每个客人进店的时刻,随时把超过一小时的旧记录划掉,再数一数最近一小时来了多少人,这就是滑动窗口。滑动窗口能解决固定窗口的一个致命缺陷——「临界突刺」。本文就带你从火锅店的账本说起,把滑动窗口限流的算法、精度与内存的权衡、Redis 分布式实现,以及生产事故里的坑,一次讲透。
职业价值:限流是高压系统的「保命符」。掌握滑动窗口限流,你就能在高并发架构设计中做出精准的容量控制决策,也能在流量突刺引发的事故排查中一眼锁定根因——这是架构师和高可用工程师的必备技能。
可视化演示
展示滑动窗口多格子计数与窗口滑动机制、固定窗口临界突刺问题与滑动窗口平滑控制对比、以及用户/IP/全局三级限流策略。
本节配有交互动效(进入视口后自动加载,约 3KB)。
滑动窗口:多格子计数与窗口滑动 限流周期划分为N个小窗口独立计数 窗口随时间滑动统计总请求数 sum(buckets) ≤ threshold 平滑流量控制
限流背景与目标
「川香居」最怕什么?不是没客人,而是一下子涌进太多客人。后厨六口锅,平均三分钟出一桌菜,王阿姨心里有本账:一小时最多接待 90 桌,超过这个数,后厨压单、客人催菜、服务员跑断腿,口碑直接崩掉。所以她必须在门口把关,把进店速度摁在 90 桌/小时以内。这个「把进店速度摁住」的动作,就是限流。
互联网系统同样如此。一个订单服务背后,数据库连接池只有 200 个连接,线程池只有 400 个线程——这就是它的「桌子」和「锅」。双十一零点,流量洪峰瞬间能冲到每秒十万次请求,如果全放进去,数据库连接被占满,线程排队时间越来越长,最终雪崩式宕机。限流的目的就是自我保护:与其让服务被洪峰打垮,不如在入口处提前说「不」,把请求速率压在服务能力之内。
限流的关键指标是 QPS(每秒查询数)和 TPS(每秒事务数)。一次限流判定,本质上就是一道选择题:
限流、降级、熔断经常被放在一起说,但三者分工不同:限流管「进」,控制有多少请求能进入系统;降级管「给什么」,当压力大时只返回核心数据;熔断管「坏没坏」,当下游故障时直接短路,不再调用。王阿姨只负责控制进店人数,菜能不能上齐是后厨的事,锅坏了就得熔断停业检修——各司其职,才能让整家店在高客流下不倒下。
- 当前速率 \(R(t)\)这里的 \(R(t)\) 是「此刻观察到的请求速率」,可以理解为最近一段时间内请求数量除以时间长度,反映系统当前承受的压力。
- 阈值 \(L\)\(L\) 是业务方设定的容量上限,它必须小于等于系统真实处理能力,是给系统留出的安全余量。
- 判定结果当 \(R(t) \ge L\) 时返回 deny,拒绝新请求,让客户端等待或走降级逻辑,从而保护底层资源不被击穿。
固定窗口缺陷
一开始,王阿姨图省事,用一个小本本记账:每小时一页纸,每页纸上画 90 个格子,进一桌画一格,画满了就关门。8 点到 9 点是一页,9 点到 10 点是一页。这个「整点换页」的记账方式,就是固定窗口限流——把时间切成固定长度的大块,每一块独立计数,满了就拒绝。
问题出在换页的那一瞬间。8:59 的时候,8 点那页已经画了 89 格,王阿姨拦住了一批客人;9:00 整,她翻到新的一页,纸上干干净净 90 格,于是又放进来 89 桌。可是 8 点进场的第一批客人,早就吃完结账走了,店里明明有位置!然而 9 点到 10 点的额度已经被这 89 桌占满,等到 10:01 新一批客人想进来,又被挡在外面。换句话说,8:59 和 9:00 交界处,系统实际放进了接近两倍限额的流量——这就是著名的「临界突刺」问题。
固定窗口的数学表达很简洁,设窗口长度为 \(W\),第 \(i\) 个窗口内请求集合为 \([iW, (i+1)W)\):
固定窗口的优点是实现极简单:一个计数器加一个定时重置就够了。它的缺点也正因为「整点重置」——重置瞬间,系统对流量失去了连续记忆,给了突发流量可乘之机。对王阿姨来说,这个漏洞就像两页账本之间有一道缝,客人能从缝里「挤」进来,后厨却要承受双倍的锅。
- 指示函数 \(\mathbf{1}(\cdot)\)公式里的 \(\mathbf{1}(\text{条件})\) 是数学里的「指示函数」——括号里的条件成立时取 1,不成立取 0,用来把落在窗口内的时间戳「挑」出来计数。
- 窗口计数 \(C_i\)把第 \(i\) 个窗口 \( [iW, (i+1)W) \) 内所有请求的指示函数值加起来,得到这个窗口内的请求总数。
- 临界突刺条件当窗口 \(i\) 的尾部(如最后的 1 秒)和窗口 \(i+1\) 的头部(如最开始的 1 秒)都接近阈值 \(L\) 时,两段流量在时间上几乎连续,合计可逼近 \(2L\),形成流量突刺。
滑动窗口算法
要堵住整点换页的漏洞,王阿姨换了一套记账工具:一卷长长的流水账纸。她把 1 小时切成 60 个小格,每格记录 1 分钟内进店的桌数。每过 1 分钟,就把最左边那格划掉,在最右边新起一格。任何时候想知道「过去一小时来了多少桌」,就把当前这 60 格加一遍。账本不是「整点清零重来」,而是像一扇窗户一样持续向前滑——我们看到的永远是「最近一小时」的真实画面,而不是「这一个整点时段」。
这就是滑动窗口限流:维护一个长度为 \(W\) 的窗口,窗口由 \(m\) 个时间片(格子)组成,每个时间片记录自己的请求数;每滑过一个时间片,丢弃最旧的格子、加入新的空格子,然后重新求和。请求到达时,先推进窗口、剔除过期数据,再统计窗口内总数,与阈值 \(L\) 比较决定放行还是拒绝。于是任意时刻的计数都严格对应「最近 \(W\) 秒」,临界突刺被天然消除。
窗口计数的数学表达如下,设当前时刻为 \(t_0\),窗口长度为 \(W\):
王阿姨的流水账本还有一个妙处:因为每格只有 1 分钟,8:59 和 9:00 交界时,账本只是「最左边划掉一格、最右边加一格」,8:59 的客人还在账本里被统计,9:00 的新客人挤进来后总数超了就会被拦住。窗口在滑,记忆在延续,后厨的锅终于不用面对双倍爆发了。
- 窗口区间 \((t_0 - W, t_0]\)这是一个左开右闭的时间区间——从当前时刻 \(t_0\) 往前数 \(W\) 秒,包含了恰好在 \(t_0\) 时刻到达的请求,但不包含 \(t_0-W\) 时刻之前的旧请求。
- 窗口内请求数 \(N(t_0)\)对每一个请求时间戳 \(t_k\) 判断是否落在窗口内,把落在窗口内的请求全部计数相加,得到此刻窗口内的总请求数。
- 放行条件只有窗口内请求数 \(N(t_0)\) 严格小于阈值 \(L\) 才放行;等于或超过 L 一律拒绝,从而保证任意连续 W 秒内的请求量不超过 L。
精度内存权衡
王阿姨很快就发现,流水账的格子大小是个学问。如果用 10 分钟一格,账本只有 6 格,记起来轻松,但「过去 1 小时」的数字最多只能精确到 10 分钟的量级——比如 8:55 来了 20 桌,这 20 桌会被算进「8:50-9:00」这一格,导致 9:05 时窗口显示的计数偏大或偏小。如果用 1 分钟一格,账本有 60 格,精确多了,但每天要翻几十页纸。如果用 1 秒一格,那账本就变成厚厚一摞,王阿姨根本记不过来。
这就是滑动窗口的精度与内存权衡:格子切得越细,窗口越接近「完全平滑」的理想状态;但格子越多,需要存储和更新的计数器就越多。假设窗口长度为 \(W\),每个时间片的精度为 \(p\)(即多长时间记一格),那么窗口内的格子数为:
工程上的折中策略很务实:先看业务对「平滑度」的真实要求,再反推精度。比如商品详情页限流,1 秒精度足够;而金融扣款接口,可能需要 10 毫秒精度。同时可以用多级限流分层设防——网关层用粗粒度快速拦截,服务层用细粒度精确控制,各取所长。王阿姨最终选了 1 分钟一格:账本只有 60 格,后厨压力却已经平稳了九成,剩下的靠「高峰期提前备桌」来解决。
- 格子数 \(m\)窗口长度 \(W\) 除以切片精度 \(p\),向上取整,得到把窗口切成多少个小格。比如 \(W=60\text{s}\)、\(p=1\text{s}\),则 \(m=60\)。
- 单格存储成本 \(c\)每个时间片通常用一个整数计数器表示(如 4 字节的 int),加上时间戳标识等元数据,\(c\) 就是这样一个格子在内存中占用的字节数。
- 总内存 \(M\)格子数乘以单格成本。当 \(W=3600\text{s}\)、\(p=1\text{ms}\) 时,\(m=3{,}600{,}000\),单格 8 字节就是约 28MB——如果每个用户一个窗口,内存直接爆炸。
Redis实现方案
王阿姨开的不是一家店,而是五家分店。如果每家分店各自拿一本流水账记账,客人在这家店吃不上跑去那家店,五本账互不相同,总量早就失控了。分布式系统也一样:多个服务实例各自维护本地计数器,只能管住「自己这台机器」的请求量,全局总量依然可能超限。所以滑动窗口要真正落地到高并发系统,必须依赖一个所有实例共享的账本——Redis。
把王阿姨的流水账挪到 Redis 里,最经典的做法是用 ZSET(有序集合):每个请求到达时,把它当成一位客人,以「到达时间戳毫秒」作为 score,以「请求唯一 ID」作为 member,写进集合。查询时,先清理掉所有 score 小于「当前时间 − W」的过期成员,再用 ZCOUNT 数出窗口内还剩多少成员。整个过程必须塞进一个 Lua 脚本 原子执行,否则就会发生「两个服务员同时翻账本」的竞态:A 服务员数出还有 1 个空位放进了客人,B 服务员也数出 1 个空位放进了客人,实际超员了。
Redis 实现的核心判定逻辑可以用下面这个式子概括,设 Redis key 为 \(K\):
这套方案的妙处在于,五家分店(多实例)共用 Redis 这一本账,任何时刻查到的都是全局总量。代价是每次请求多一次网络往返(RTT),所以生产中通常配合网关前置的本地粗筛:本地先快速查一个粗粒度计数,粗略放行,再异步去 Redis 精确校验。王阿姨的电子账本,把分店之间对不上账的烦恼彻底解决了。
- ZCOUNT 计数\(\operatorname{ZCOUNT}(K, \text{min}, \text{max})\) 是 Redis 原生命令,返回有序集合 \(K\) 中 score 在区间 \([\text{min}, \text{max}]\) 内的成员数量,天然适合统计窗口内请求数。
- 过期清理每次请求先执行 \(\operatorname{ZREMRANGEBYSCORE}(K, -\infty, t_0-W)\),把窗口左边界之前的旧记录全部删除,防止内存无限增长。
- 判定与写入若 \(N_{\text{win}} < L\),则 \(\operatorname{ZADD}(K, t_0, \text{reqId})\) 写入新请求并放行;否则直接拒绝。三步操作由 Lua 脚本保证原子性,避免并发下超限。
边界抖动与实战
王阿姨还遇到过一个诡异现象:明明账本显示「最近一小时 88 桌」,下一秒却突然跳到 92 桌——多出来的那几桌,是门口被拒的客人在不停「重试」:被拒的人等 1 秒再敲一次门,同一毫秒里好几个被拒的客人一起重新挤进来,计数在窗口边界附近剧烈抖动。这就是窗口边界抖动,它在真实系统里的表现是:瞬时并发 + 客户端重试风暴同时打向限流器,导致窗口计数在边缘处跳变,甚至误伤正常请求。
边界抖动的成因主要有三类:一是客户端「被拒后立刻重试」导致的请求聚集;二是多台服务器时钟不同步,导致 Redis 里时间戳比较出现偏差;三是 ZSET 过大时一次性清理过期成员造成阻塞。对应的处理策略很明确——重试退避(被拒后指数退避而不是立刻重试)、时钟校准(服务器统一 NTP 同步)、批量过期清理(定时小批量删除而不是一次性大删)。更平滑的手段是引入预热(warm-up):窗口刚开启时放宽阈值,随着时间逐步收紧,让流量像烧水一样慢慢升温,而不是瞬间灌入。
衡量边界抖动程度,可以引入一个抖动比 \(J\)——窗口内瞬时速率的最大波动与平均速率的比值:
生产事故复盘里,最典型的一幕发生在某电商库存系统:双十一 0 点大促,网关用了固定窗口限流,结果 0 点前后两个窗口交界处的流量叠加,实际请求量冲到阈值的 1.8 倍,库存扣减服务被压垮,超卖告警响成一片。切换到滑动窗口后,配合预热和重试退避,峰值被熨平,系统稳稳扛住了整波洪峰。王阿姨后来也学会了这个道理:账本再精确,也架不住一群客人同时重试敲门——规矩要定,门要稳,客人也要教。
- 瞬时波动 \(\Delta R_{\text{win}}(t)\)在窗口滑动过程中,前后两个极短时刻的窗口计数差值,反映流量在窗口边界处的突变幅度。
- 平均速率 \(\bar{R}\)在一段较长时间内,总请求数除以总时长,代表流量的「正常水位」,作为衡量波动的基准尺度。
- 抖动比 \(J\)波动峰值除以平均速率,归一化后的相对抖动。阈值通常在 0.3 以下可接受,超过则需检查是否出现重试风暴或时钟偏差。
AI 应用场景
大模型 API 网关的令牌限额
阶段:模型服务接入与流量治理
解决的问题:大模型 API 面向大量开发者开放,单用户可能在一秒内发起几十次请求,如果全部透传给 GPU 推理服务,显存会被打爆、排队时间飙升,还容易被恶意刷接口。传统固定窗口限流每秒重置,用户卡着重置点就能突破限制。
具体案例:某 AI 公司开放平台用滑动窗口限流,对每个 API Key 设置「过去 60 秒最多 20 次请求」的阈值,用 Redis ZSET + Lua 实现分布式原子校验。某开发者脚本在边界处反复重试,被平滑拦截,推理集群的 P99 延迟从 2.3 秒降到 800 毫秒。
推荐系统大促峰值保护
阶段:在线推理服务稳定性保障
解决的问题:双十一大促期间,推荐接口 QPS 瞬时翻 30 倍,召回和排序服务如果来多少接多少,线程池会在几秒内耗尽,导致全站推荐不可用。限流器要既能挡住洪峰,又不能在波谷时误杀正常流量。
具体案例:某电商推荐平台在入口网关配置滑动窗口限流,窗口 10 秒、精度 1 秒、阈值按容量 80% 设定。大促零点峰值时,限流器平稳削掉最尖的突刺,排序服务 CPU 始终控制在 70% 以下,推荐点击率反而因时延降低而提升。
推理服务集群的并发窗口
阶段:LLM 推理服务的资源调度
解决的问题:大语言模型推理显存占用高,一个 10B 模型服务单实例最多并发 8 个请求,超出后显存溢出或排队饿死。需要按实例维度控制「滑动窗口内正在处理的请求数」,而不是简单按 QPS 计数。
具体案例:某 MLOps 团队为每个推理 Pod 挂载本地滑动窗口限流器,窗口 30 秒、精度 5 秒,配合信号量控制并发。当在线业务波峰与离线批处理任务重叠时,在线请求的推理时延保持稳定,显存 OOM 次数降为零。
Python 演示
import timeimport randomfrom collections import deque class SlidingWindowLimiter: """滑动窗口限流器:用队列记录窗口内每个请求的到达时刻""" def __init__(self, limit=5, window=5.0): self.limit = limit self.window = window self.stamps = deque() def allow(self): now = time.time() while self.stamps and self.stamps[0] <= now - self.window: self.stamps.popleft() if len(self.stamps) >= self.limit: return False self.stamps.append(now) return True def main(): limiter = SlidingWindowLimiter(limit=3, window=5.0) print("模拟 30 个请求:窗口 5 秒,最多放行 3 个") blocked = 0 for i in range(30): ok = limiter.allow() if not ok: blocked += 1 status = "放行" if ok else "拒绝" print(f"请求 {i + 1:>2} -> {status} 窗口内={len(limiter.stamps)}") time.sleep(0.18) print(f"共拒绝 {blocked} 个请求") if __name__ == "__main__": main()
collections.deque 实现一个可运行的滑动窗口限流器,模拟请求流并输出每个请求的放行/拒绝结果,展示窗口滑动与过期清理的真实过程。self.stamps = deque()用双端队列存储请求时间戳,队头是最早的请求,方便从左侧弹出过期记录while self.stamps and self.stamps[0] <= now - self.window:循环弹出所有「超过窗口 W 秒」的旧时间戳,这一步就是滑窗的「划掉过期账本」self.stamps.popleft()从队列左侧移除最早的时间戳,保持窗口内只保留最近 W 秒的请求if len(self.stamps) >= self.limit:清理后统计窗口内请求数,若已达到阈值 L 则拒绝本次请求self.stamps.append(now)放行后把当前时间戳追加到队列右侧,代表新客人在账本「最近 W 秒」区间内登记time.sleep(0.18)模拟请求到达间隔,让运行过程肉眼可见地展示「满额 → 拒绝 → 过期 → 恢复」的完整循环
常见误区
误区滑动窗口把所有流量都熨平了,任何时刻都不会出现超过阈值的突发,系统绝对安全。
正解滑动窗口限定的是「任意连续 W 秒内请求总量不超过 L」,但窗口内部仍然允许瞬时脉冲——比如 1 秒内冲进 L 个请求是合法的,只要它们分布在不同的格子。它消除的是「整点重置导致的 2 倍突刺」,而不是「窗口内的自然波动」。真要熨平脉冲,还得叠加令牌桶或预热机制。
为何常见:看到滑动窗口的名字就联想到「平滑流动」,误以为它像限速器一样均匀放行,忽略了它本质是「窗口粒度」的计数器。
误区精度越高控制越精确,那我把窗口切成 1 毫秒一格,效果一定最好,多花点内存无所谓。
正解内存成本随格子数线性增长:1 小时窗口、1 毫秒精度需要 360 万个格子,单用户就是几十 MB;如果按用户维度各建一个窗口,内存轻松突破 GB 级别。且 Redis 里超大 ZSET 的清理操作还会阻塞主线程。工程上应先定业务可容忍的「最大突刺时长」,再反推精度,而不是盲目追求精细。
为何常见:教科书讲算法时只提「精度越高越平滑」,没强调成本曲线,初学者容易把「更精细」等同于「更优」。
误区用 Python 先 ZCOUNT 再 ZADD,两步操作也能实现限流,没必要学 Lua。
正解先查后写不是原子的。两个请求同时读到「窗口还剩 1 个名额」,都会执行 ZADD,实际放行了 2 个,阈值被击穿。Lua 脚本把「清理过期 → 计数 → 决定放行 → 写入」打包成一段服务端原子执行的操作,Redis 单线程模型保证脚本执行期间不会被其他命令插入,才真正堵住并发竞态。
为何常见:单机开发时很少遇到并发写,自然意识不到「读-改-写」三步之间的竞态窗口;一旦压测到高并发,超限就立刻暴露。
误区滑动窗口每次请求都要清理过期数据、重新求和,性能开销大,还是固定窗口快,将就用吧。
正解滑动窗口的额外开销是 O(1) 的:队列头部的过期清理摊还到每次请求上,求和也可以维护一个累计值增量更新。相比一次网络请求、一次数据库查询的成本,这点 CPU 开销可以忽略。固定窗口的「快」省出的是纳秒级,却可能放大出一个 2 倍流量的雪崩风险——这笔账怎么算都不划算。
为何常见:把窗口实现等同于「每次全量扫描所有格子」,误以为复杂度是 O(m);实际工程实现用累计值或队列,复杂度远低于想象。
面试题
Q1. 固定窗口限流最经典的缺陷是什么?
- A.内存占用率过高
- B.窗口边界存在临界突刺← 正确答案
- C.无法统计请求数量
- D.不支持分布式部署
答案:B。固定窗口在整点重置瞬间会失去流量记忆,导致窗口交界处可能涌入接近两倍阈值的请求,这就是临界突刺。A 是滑动窗口细粒度时才突出,C、D 与固定窗口特征无关。
Q2. 以下哪个是典型的限流指标?
- A.CPU 主频
- B.磁盘总容量
- C.每秒查询数 QPS← 正确答案
- D.屏幕分辨率
答案:C。限流衡量的是请求速率,QPS(每秒查询数)是最常用的指标,代表单位时间内的请求量。CPU 主频、磁盘容量、屏幕分辨率都是硬件参数,与流量控制无关。
Q3. 滑动窗口相比固定窗口,核心改进点是什么?
- A.不再需要存储请求记录
- B.用连续滑动消除整点重置突变← 正确答案
- C.可以完全拒绝所有超限请求
- D.请求处理速度明显更快
答案:B。滑动窗口通过细粒度分片和窗口前移,让计数始终对应「最近 W 秒」,消除固定窗口整点重置时的记忆断层。它仍需存储时间戳,也无法彻底消除窗口内脉冲,处理速度取决于实现而非窗口类型。
Q4. 窗口长度 W=10 秒,切片精度 p=0.5 秒,滑动窗口需要多少个格子?
- A.5
- B.10
- C.20← 正确答案
- D.50
答案:C。格子数 m = W / p = 10 / 0.5 = 20。每个格子代表 0.5 秒的请求计数,窗口由这 20 个格子组成,滑动时每 0.5 秒前移一格。
Q5. 关于滑动窗口的精度与内存关系,以下说法正确的是?
- A.精度越高所需内存越小
- B.精度越低突发控制越严格
- C.内存随格子数线性增长← 正确答案
- D.精度与内存完全无关
答案:C。格子数 = W / p,精度 p 越小格子越多,内存 M = c × m 线性增长。精度高控制更平滑,但内存代价大,需要折中,因此 A、B、D 都不对。
Q6. Redis 实现滑动窗口限流时,ZSET 的 score 字段应该存什么?
- A.请求的唯一 ID
- B.请求到达的时间戳← 正确答案
- C.用户的身份标识
- D.窗口的剩余额度
答案:B。滑动窗口需要按时间范围统计和清理成员,ZSET 的 score 必须存时间戳,才能用 ZRANGEBYSCORE / ZREMRANGEBYSCORE 按窗口边界操作。请求 ID 通常放在 member 字段,用户 ID 和剩余额度与计数逻辑无关。
Q7. 关于固定窗口临界突刺的产生条件,以下说法正确的是?
- A.窗口末尾请求量接近阈值← 正确答案
- B.下一个窗口开头请求量接近阈值← 正确答案
- C.两个窗口之间相隔时间较长
- D.整点重置导致两段流量相连← 正确答案
答案:ABD。临界突刺的本质是「上一个窗口尾部」和「下一个窗口头部」的请求在时间上连续,叠加后接近 2L。C 说两窗口相隔较长,与突刺条件相反,故不选。
Q8. 为什么 Redis 滑动窗口必须用 Lua 脚本保证原子性?
- A.脚本语言比 ZCOUNT 执行更快
- B.并发下先查后写会超限放行← 正确答案
- C.Lua 可以自动扩容 Redis
- D.不使用脚本就无法存时间戳
答案:B。「清理过期 → 数窗口计数 → 写新请求」是读-改-写三步。并发请求同时读到「还剩一个名额」就会同时写入,击穿阈值。Lua 在 Redis 服务端原子执行整段逻辑,杜绝竞态。与执行速度和扩容无关。
Q9. 分布式多实例环境下使用滑动窗口限流,以下说法正确的是?
- A.各实例本地计数可能导致总量超限← 正确答案
- B.Redis 共享计数能实现全局精确限流← 正确答案
- C.本地计数在任何场景都优于集中计数
- D.集中计数会引入额外网络延迟代价← 正确答案
答案:ABD。本地计数各管一段,总和会超过全局阈值;Redis 共享计数全局唯一账本,但每次判定多一次 RTT。实际常做「本地粗筛 + Redis 精确校验」的混合方案,C 的「绝对优于」不成立。
Q10. 窗口边界抖动通常由哪类因素引发?
- A.客户端被拒后立即重试造成聚集← 正确答案
- B.服务器磁盘空间不足
- C.请求体体积异常庞大
- D.数据库索引创建失败
答案:A。被拒请求立即重试会形成「重试风暴」,在同一毫秒内集中涌入窗口边界,造成计数跳变。磁盘空间、请求体大小、索引故障都不影响窗口内时间戳计数的抖动特性。