想象你所在的小区用水高峰期。水厂的水先进入小区蓄水池,再通过总管道分到每家每户。如果所有住户同时打开水龙头,水压会骤降,低层住户可能连洗澡水都打不上。物业的方法是:在总管道装一个智能阀门,限制单位时间内的出水量;再装一个自动保护开关,一旦发现压力异常就主动切断;同时准备应急水车。这套组合,就像 LLM 应用的稳压器。 在人工智能应用里,请求就是水流,大模型服务就是水厂。限流是防止流量过载的智能阀门——通过令牌桶等算法,把并发请求控制在服务能承受的范围内;熔断是防止故障蔓延的保险丝——当下游模型持续报错,主动切断流量;降级是提供兜底方案,半开探测则让服务在恢复后安全回归。本文就从小区的供水故事出发,把这台「稳压器」的零件逐一拆开看。

岗位关联

掌握限流熔断,等于给 LLM 应用装上稳压器:流量再猛也能稳,服务再抖也不崩,这是大模型工程化落地的核心必修课。

目录
  1. 可视化演示
  2. 限流算法对比
  3. 令牌桶原理
  4. 限流策略选择
  5. 熔断基础概念
  6. 熔断状态机
  7. 降级策略
  8. 半开探测
  9. LLM应用实践
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态演示限流熔断的核心机制,通过源头/管道/节点/终端四种模式展示关键概念与交互流程。

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

限流熔断:限流熔断 限流熔断核心概念与架构设计 限流算法原理与对比分析 核心机制可视化 限流熔断整体架构与关键组件

限流算法对比

小区供水总管进了蓄水池之前,物业要控制高峰时段的水流。如果任用户狂接水,水压会崩,水管会裂。于是工程师拿出四种阀门方案:有的按整点开关,有的盯住过去一小时的总用水量,有的把出水口做得极小,有的则配一个蓄水桶。这就是限流算法的雏形。

在 LLM 应用中,固定窗口限流相当于「每个整点重开一扇门,窗口内最多放行 N 个请求」;滑动窗口则把统计区间切成许多小片,不断往前平移;漏桶把请求像水一样漏到下游,速率恒定;令牌桶在每秒补充一定量令牌,请求必须拿到令牌才能通过。

选型没有银弹:固定窗口实现成本低,但窗口边界可能冲进两倍流量;滑动窗口更精确但内存开销大;漏桶适合严格匀速的场景,比如批量导入;令牌桶允许短时突发,适合读多写少、有尖峰的 API。

四种阀门对应四种取舍。做 LLM 应用时,先想清楚你的用户是「早晚高峰的住宅」还是「持续出水的工厂」,再决定用哪种阀门。

∑t−Δttni≤L\displaystyle \sum_{t-\Delta t}^{t} n_i \le L
滑动窗口把「过去一段时间内的总请求数」压到上限 L 以内,比固定窗口更平滑。
  1. 请求计数n_i 表示第 i 个时间片内的请求数,滑动窗口把总时间划分成小片。
    ni=requests in slice in_i = \text{requests in slice } i
  2. 窗口累加从当前时刻往前推 Δt 秒,把所有小片的请求数加起来。
    ∑t−Δttni\sum_{t-\Delta t}^{t} n_i
  3. 限流条件总和不超过上限 L 才允许请求进入,否则拒绝。
    ∑t−Δttni≤L\sum_{t-\Delta t}^{t} n_i \le L

令牌桶原理

物业最终选中了蓄水桶方案。水阀内部藏着一只小桶,水管以每秒 r 滴的速率往桶里滴水;桶满后多余的水漫出不再积累。每次用户请求进来,必须从桶里取走 k 滴水,取不到就被拒之门外。

这只桶在技术上叫令牌桶。r 是令牌补充速率,C 是桶容量,k 是单个请求消耗的令牌数。它能实现两件事:平时匀速向系统注入流量,遇到瞬时爆发时,把桶里囤积的令牌一次性放出去。

实现时不需要真的定时器,只要记录上次补充时间,用时间差乘以 r 就能算出本次补了多少令牌。

这只蓄水桶就像家里的小水塔,白天偶尔多洗几件衣服不会断水,但如果天天爆水管,水塔也没用。令牌桶让系统在「容忍突发」和「总量可控」之间找到了平衡点。

T←min⁡(C,T+r×Δt)\displaystyle T \leftarrow \min(C, T + r \times \Delta t)
令牌桶像一个有水塔的小区:平时慢慢蓄水,高峰时能多放一会儿,但总储量有上限。
  1. 当前令牌数T 表示桶中可用令牌数,初始值为容量 C。
    T0=CT_0 = C
  2. 按时间补充经过 Δt 秒后,补入 r×Δt 个令牌。
    T+r×ΔtT + r \times \Delta t
  3. 容量封顶超过 C 的部分直接丢弃,保证突发量有上限。
    min⁡(C,T+r×Δt)\min(C, T + r \times \Delta t)

限流策略选择

选阀门不是越贵越好。办公楼白天用水集中,住宅区早晚高峰,医院必须 24 小时不断水。小区给不同区域配了不同阀门,这就是限流策略的选择。

如果你的 LLM 应用是给电商大促做客服,突发流量集中在开售 10 分钟,令牌桶能轻松扛住;如果是风控模型评分,要求每秒请求量绝对平滑,漏桶更合适;如果需要精确审计每一秒的流量,滑动窗口更透明。

还要算成本:桶容量 C 定太大,等于把限流关了;定太小,日常流量都过不去。更复杂的系统会做多级混合,比如入口用滑动窗口限总量,下游给每个用户用令牌桶限配额。

选择限流策略,本质是回答一个问题:你的用户能接受「偶尔等一下」,还是「必须每分钟都有水」?答案决定阀门形状。

Rrequired=QpeakTwindow\displaystyle R_{required} = \frac{Q_{peak}}{T_{window}}
先算峰值需求速率,再选算法——这是限流选型的起点。
  1. 峰值请求量Q_peak 是业务高峰期累计到达的请求总数。
    QpeakQ_{peak}
  2. 时间窗口T_window 是这段高峰持续的时间长度,单位秒。
    TwindowT_{window}
  3. 需求速率两者相除得到必须提供的平均处理速率,低于它就一定会排队或拒绝。
    Rrequired=QpeakTwindowR_{required} = \frac{Q_{peak}}{T_{window}}

熔断基础概念

就算装了阀门,下游管道仍然可能因为压力异常爆裂。小区管道上还有一个自动保护开关,一旦检测到水压超限,就直接把水切断,保护住户电器不被冲坏。这个开关就是熔断器。

LLM 应用的熔断器要监控三个信号:失败次数、错误比例、响应超时。当错误比例超过阈值,或者大量请求超时,熔断器认为下游「水管破了」,主动把流量切断,让故障服务有时间恢复。

阈值设定是艺术:设太低,一次小抖动就熔断,用户体验雪上加霜;设太高,保护作用形同虚设。一般先根据历史 p99 延迟和错误率基线来定,再逐步压测调整。

保险丝不是为了停电,而是为了不让整栋楼烧掉。熔断器牺牲一部分请求,换来的却是整个 LLM 服务不会因为一个慢服务而全军覆没。

Pfail=FcountNtotal>θ\displaystyle P_{fail} = \frac{F_{count}}{N_{total}} > \theta
失败率是熔断器的「压力表」,超过阈值 θ 就触发保护。
  1. 失败计数F_count 是统计窗口内失败的请求数。
    FcountF_{count}
  2. 总请求数N_total 是同一窗口内所有进入下游的请求数。
    NtotalN_{total}
  3. 失败率两者相除得到失败比例,一旦大于阈值 θ,触发熔断。
    Pfail=FcountNtotal>θP_{fail} = \frac{F_{count}}{N_{total}} > \theta

熔断状态机

保护开关不是只有「开」和「关」,而是有三档:正常供水、泄压切断、试探恢复。三档之间靠状态机自动切换,不需要人盯着。

关闭状态(CLOSED)下所有请求正常放行;一旦失败率超阈值,进入开启状态(OPEN),请求快速失败;过了冷却时间,再进入半开状态(HALF_OPEN),放少量请求试探;试探成功就回到关闭,失败就重新打开。

状态机的价值在于「有记忆」:它不会因为一次偶发抖动就反复开合,也不会永远卡在故障态。工程实现上,每个状态都对应独立逻辑,迁移条件用原子变量保护,避免并发请求同时改状态。

三档状态就像一个懂事的物业:平时不管,出事果断断水,确认修好后慢慢恢复供水。有了状态机,系统才拥有自我修复的循环。

St+1={OPEN,Pfail>θHALF_OPEN,t≥Tcool and St=OPENCLOSED,Pprobe≥θprobe and St=HALF_OPEN\displaystyle S_{t+1} =\begin{cases}OPEN, & P_{fail} > \theta \\HALF\_OPEN, & t \ge T_{cool} \text{ and } S_t = OPEN \\CLOSED, & P_{probe} \ge \theta_{probe} \text{ and } S_t = HALF\_OPEN\end{cases}
三档状态让熔断器像一个有经验的值班员:先断水,再等冷却,最后小流量试水。
  1. 熔断开启当失败率超过阈值 θ,下一个状态变为 OPEN。
    St+1=OPEN,Pfail>θS_{t+1} = OPEN, \quad P_{fail} > \theta
  2. 冷却结束OPEN 状态持续超过冷却时间 T_cool 后,转入 HALF_OPEN。
    St+1=HALF_OPEN,t≥TcoolS_{t+1} = HALF\_OPEN, \quad t \ge T_{cool}
  3. 试探成功HALF_OPEN 下探测成功率达标,回到 CLOSED。
    St+1=CLOSED,Pprobe≥θprobeS_{t+1} = CLOSED, \quad P_{probe} \ge \theta_{probe}

降级策略

熔断切断供水之后,住户不能干等着。物业会启动备用水源:临时接一根管子送应急水,或者让大家先用水桶里的存水。这就是降级策略。

LLM 应用常见的降级方案有四类:兜底响应,直接返回「服务繁忙,请稍后再试」;缓存降级,把上一次成功的结果返回给用户;静态回复,给定期权公告等固定文案;异步降级,把非实时任务丢进消息队列,等下游恢复后再慢慢处理。

降级的核心原则是「有响应好过无响应」。对用户来说,五分钟之后拿到准确的摘要,比 30 秒后收到超时报错更友好。工程上把降级逻辑封装成 fallback 函数,与主流程解耦。

备用管道虽然没有自来水新鲜,但至少不让整栋楼断水。降级的本质是用户可感知的轻微劣化,换回系统整体的稳定。

Responsedegraded={cache_hit,if cache availablestatic_text,otherwise\displaystyle Response_{degraded} =\begin{cases}cache\_hit, & \text{if cache available} \\static\_text, & \text{otherwise}\end{cases}
降级是熔断后的「应急水车」:优先给缓存,缓存也没有就给固定文案。
  1. 命中缓存优先返回缓存中最近一次成功结果,保证信息不缺失。
    Responsedegraded=cache_hitResponse_{degraded} = cache\_hit
  2. 无缓存兜底没有可用缓存时,返回固定文案,让用户知道服务状态。
    Responsedegraded=static_textResponse_{degraded} = static\_text

半开探测

水管修好后,物业不会一下子把总闸开到底,而是先拧开一点,观察水压是否正常。确认没问题,再完全恢复。这个动作叫半开探测。

熔断器进入 HALF_OPEN 后,放行 m 个探测请求。如果其中有 k 个成功,且成功率不低于设定阈值,就认为下游已经恢复,状态切回 CLOSED;如果探测失败,立刻回到 OPEN,并重新计时冷却。

探测流量要控制得很小,比如 m=10、k=8,否则下游刚喘口气又被打垮。实际系统中,探测请求可以走单独的小流量集合,甚至只探测健康检查端点。

小步快跑比一次性全量恢复更让人安心。半开探测让系统在恢复过程中始终保持警惕,既不放走故障,也不错过康复。

Pprobe=km≥θprobe⇒CLOSED\displaystyle P_{probe} = \frac{k}{m} \ge \theta_{probe} \Rightarrow CLOSED
半开探测是「先拧开一点水龙头」,用最小代价确认下游是否真的好了。
  1. 探测总量m 是半开状态放行的探测请求数,通常是个位数。
    mm
  2. 成功数k 是其中成功返回的请求数,成功定义由健康检查决定。
    kk
  3. 成功率k/m 达到 θ_probe,状态机关闭熔断;否则保持开启。
    Pprobe=km≥θprobe⇒CLOSEDP_{probe} = \frac{k}{m} \ge \theta_{probe} \Rightarrow CLOSED

LLM应用实践

阀门、保护开关、备用水源都装好了,还得有一个调度室把它们串起来。调度室里能看到每个环节的压力、水温和报警。这就是 LLM 应用可观测性的雏形。

高并发场景下,在 API 网关层做令牌桶限流,在调用大模型 SDK 的客户端里做熔断,再在业务层写降级函数。三层各司其职:限流挡住过量请求,熔断挡住慢依赖,降级兜住用户体验。

最怕的是级联雪崩:一个模型服务变慢,导致所有上游线程阻塞,最终整个应用死去。所以还要监控可用性指标、设置告警水位线,让调度室能在深夜自动叫醒值班人。

稳压器不是把用户挡在门外,而是让尽量多的人在波动中依然能用上水。限流、熔断、降级、半开探测组合起来,就是 LLM 应用最坚实的稳压器。

A=NsuccessNtotal×100%\displaystyle A = \frac{N_{success}}{N_{total}} \times 100\%
可用性就是「每一百个请求里有多少个成功」,它是稳压器是否有效的最直观指标。
  1. 成功请求数N_success 是单位时间内正常完成响应的请求数。
    NsuccessN_{success}
  2. 总请求数N_total 是进入系统的总请求数。
    NtotalN_{total}
  3. 可用性两者相除并乘以 100%,得到服务可用性指标,用来设定告警和容量水位。
    A=NsuccessNtotal×100%A = \frac{N_{success}}{N_{total}} \times 100\%

AI 应用场景

LLM API 网关层限流

阶段:部署与运维 / 流量治理

解决的问题:没有限流时,突发的用户请求会拖垮模型服务,产生超时毛刺。用令牌桶在网关层把 QPS 控制在模型峰值吞吐以下,把多余请求排队或拒绝。

具体案例:一家公司上线文生图功能,营销活动带来 5 倍流量。通过在网关前置令牌桶限流,保证峰值 1000 QPS 以内,剩余请求返回 429 并引导稍后重试。

客服机器人熔断降级

阶段:运行时故障处理

解决的问题:底层大模型 API 故障时,所有客服请求都会失败。熔断器在错误率超阈值后自动开启,后续请求走缓存或兜底话术,避免用户体验崩盘。

具体案例:电商大促期间大模型供应商限流,客服机器人调用多次超时。熔断器 5 秒内打开,机器人改回复「正在升级,请稍后」,同时把对话记录异步写入队列,服务恢复后补答。

批量推理任务保护

阶段:批处理与异步调度

解决的问题:离线批量推理任务一次性提交上万条,容易压垮 GPU 服务。用令牌桶给任务队列配速,结合熔断器,在推理服务异常时暂停消费。

具体案例:内容审核系统每天凌晨跑全量视频标注,使用令牌桶限流到 200 并发,熔断阈值设为错误率 30%。某天模型升级失误导致部分请求失败,熔断触发后任务自动暂停,避免无效重试浪费算力。

Python 演示

python84 行
import time  class TokenBucket:    def __init__(self, rate, capacity):        self.rate = rate        self.capacity = capacity        self.tokens = capacity        self.last_refill = time.monotonic()     def acquire(self, cost=1):        now = time.monotonic()        delta = now - self.last_refill        self.tokens = min(self.capacity, self.tokens + delta * self.rate)        self.last_refill = now        if self.tokens >= cost:            self.tokens -= cost            return True        return False  class CircuitBreaker:    def __init__(self, threshold, fail_limit, cooldown):        self.threshold = threshold        self.fail_limit = fail_limit        self.cooldown = cooldown        self.state = "closed"        self.half_open_probes = 0        self._failure_count = 0        self._total_count = 0        self._recovered_at = time.monotonic()     def record(self, success):        if self.state == "closed":            self._total_count += 1            if not success:                self._failure_count += 1            if self._total_count >= self.fail_limit and self._failure_count / self._total_count > self.threshold:                self.state = "open"                self._recovered_at = time.monotonic() + self.cooldown                self._failure_count = 0                self._total_count = 0        elif self.state == "half_open":            if success:                self.state = "closed"            else:                self.state = "open"                self._recovered_at = time.monotonic() + self.cooldown            self._failure_count = 0            self._total_count = 0     def can_pass(self):        if self.state == "closed":            return True        if self.state == "open":            if time.monotonic() >= self._recovered_at:                self.state = "half_open"                self.half_open_probes = 0                return True            return False        if self.half_open_probes < self.fail_limit:            self.half_open_probes += 1            return True        return False  def simulate():    bucket = TokenBucket(rate=10, capacity=20)    breaker = CircuitBreaker(threshold=0.5, fail_limit=10, cooldown=0.1)    for i in range(100):        if not bucket.acquire():            print(f"request {i}: limited by token bucket")            continue        if not breaker.can_pass():            print(f"request {i}: circuit open, fallback response")            continue        success = not (40 <= i < 60)        breaker.record(success)        status = "ok" if success else "upstream failed"        print(f"request {i}: {status}, breaker.state={breaker.state}")  if __name__ == "__main__":    simulate()
目的:演示令牌桶限流与熔断状态机如何协同工作,模拟下游故障时自动熔断、冷却后半开探测并恢复的过程。
  1. self.tokens = min(self.capacity, self.tokens + delta * self.rate)按时间差补充令牌,并用容量封顶。
  2. if self.tokens >= cost: self.tokens -= cost令牌足够才放行请求,否则直接拒绝。
  3. if self._total_count >= self.fail_limit and self._failure_count / self._total_count > self.threshold:失败比例超过阈值后开启熔断。
  4. time.monotonic() >= self._recovered_at冷却时间结束后才允许进入半开状态。
  5. half_open_probes < self.fail_limit半开状态只放行少量探测请求,避免再次打垮下游。

常见误区

误区限流和熔断差不多,都是限制请求,熔断就是限流的一种。

正解限流是事前控制,像阀门,阻止流量超过安全水位;熔断是事后保护,像保险丝,发现下游异常后切断电流。限流防止过载,熔断防止故障蔓延。二者可以独立使用,也必须配合。

为何常见:两者都出现在稳定性治理里,名字里都有「流/断」,很容易混。

误区令牌桶容量设得越大越好,这样用户永远不会被拒。

正解桶容量 C 决定瞬时突发上限。C 太大,突发流量会直接击穿下游 LLM 服务,限流形同虚设。应按下游峰值吞吐和用户容忍度设定。

为何常见:产品总想少拒绝用户,技术容易在指标上妥协。

误区熔断阈值越低系统越安全,稍微出点错就应该立刻熔断。

正解阈值过低会让偶发抖动触发熔断,造成大面积不可用。熔断阈值应高于正常波动、低于灾难水平,通常在错误率 0.5 附近,再经压测调整。

为何常见:把「保护」理解成越激进越好,忽略了可用性也有成本。

误区熔断后直接抛 500 错误就行,反正故障不是我的责任。

正解熔断的意义是提供降级体验。返回兜底文案或缓存结果,用户至少知道服务在努力;直接抛错等于把故障变成事故。降级策略是熔断闭环的一部分。

为何常见:低峰期没做演习,只实现了熔断没实现降级。

误区半开探测放行越多请求,恢复得越快,所以可以直接放一半流量。

正解半开状态是为探测恢复情况,应只放行极小流量(如 5-10 个请求)。放行太多可能把刚缓过来的下游再次打死,形成反复熔断。

为何常见:希望尽快恢复,忽略了熔断器原本就是「怀疑下游还没好」。

面试题

Q1. 以下哪种限流算法允许一定程度的突发流量?

单选题 · easy

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

答案:C。令牌桶靠预存令牌在突发时一次性释放,允许短时突发;固定窗口和滑动窗口在窗口内也允许突发但有边界问题,漏桶输出速率恒定,不允许突发。因此选 C。

Q2. 熔断器处于开启状态时,进入的请求应如何处理?

单选题 · easy

  • A.直接放行
  • B.排队等待
  • C.快速失败← 正确答案
  • D.重试三次

答案:C。熔断开启意味着下游故障,请求应当快速失败,避免继续压榨下游。放行、排队、重试都会放大故障影响。

Q3. 令牌桶中决定瞬时最大放行量的参数是?

单选题 · easy

  • A.入桶速率
  • B.桶的容量← 正确答案
  • C.请求超时
  • D.重试次数

答案:B。桶容量 C 是囤积令牌的上限,限制了瞬时放行总量;入桶速率决定长期平均速率,超时和重试与令牌桶无关。

Q4. 半开状态的核心目的是?

单选题 · mid

  • A.永久拒绝请求
  • B.试探下游恢复← 正确答案
  • C.降低令牌桶速率
  • D.增加请求重试

答案:B。半开状态放行少量请求,目的是试探下游是否恢复,成功则关闭熔断,失败则重新打开。与限流参数无关。

Q5. 固定窗口限流的主要缺点是?

单选题 · mid

  • A.实现逻辑复杂
  • B.无法平滑限速← 正确答案
  • C.不支持秒级控
  • D.占用内存过大

答案:B。固定窗口在窗口切换边界可能出现两倍突发流量,本质是限速不平滑。它实现简单、内存小,也支持更小窗口,因此 B 正确。

Q6. 熔断器从开启状态进入半开状态通常依赖什么?

单选题 · mid

  • A.手动重启服务
  • B.冷却时间结束← 正确答案
  • C.请求全部成功
  • D.令牌桶被清空

答案:B。开启状态要等待冷却时间 T_cool 结束才进入半开,冷却时间是为了给下游留出恢复窗口。手动重启不符合自动恢复理念。

Q7. 以下哪些属于合理的降级策略?

多选题 · mid

  • A.返回缓存结果← 正确答案
  • B.返回兜底文案← 正确答案
  • C.无限重试请求
  • D.异步写消息队列← 正确答案

答案:ABD。缓存、兜底文案、异步降级都能在有损情况下保持系统响应;无限重试会放大流量压力,不是降级策略。

Q8. 关于滑动窗口与固定窗口,下列说法正确的有?

多选题 · hard

  • A.滑动窗口平滑边界← 正确答案
  • B.固定窗口实现简单← 正确答案
  • C.滑动窗口无峰值流量
  • D.固定窗口可用计数← 正确答案

答案:ABD。滑动窗口通过细化时间片平滑边界流量,固定窗口用计数器即可实现,因此 A、B、D 正确。滑动窗口只是边界更平滑,不代表没有峰值流量,C 错。

Q9. 关于令牌桶与漏桶,下列说法正确的有?

多选题 · hard

  • A.令牌桶允许突发流量← 正确答案
  • B.漏桶输出速率恒定← 正确答案
  • C.令牌桶无需时间戳
  • D.漏桶能应对流量峰值

答案:AB。令牌桶允许突发,漏桶输出速率恒定,因此 A、B 正确。令牌桶需要时间戳计算补充量,C 错;漏桶本身不能应对峰值,只会把突发平滑掉,D 错。

Q10. 熔断器关闭状态下,窗口内总请求数 100,失败 60,阈值 0.5,此时应进入?

单选题 · extreme

  • A.保持关闭状态
  • B.进入开启状态← 正确答案
  • C.进入半开状态
  • D.立即降级响应

答案:B。失败率 60% 大于阈值 50%,满足开启条件,应进入 OPEN。半开是开启后冷却结束才进入;降级是熔断后的策略,不是状态。