想象你所在的小区用水高峰期。水厂的水先进入小区蓄水池,再通过总管道分到每家每户。如果所有住户同时打开水龙头,水压会骤降,低层住户可能连洗澡水都打不上。物业的方法是:在总管道装一个智能阀门,限制单位时间内的出水量;再装一个自动保护开关,一旦发现压力异常就主动切断;同时准备应急水车。这套组合,就像 LLM 应用的稳压器。 在人工智能应用里,请求就是水流,大模型服务就是水厂。限流是防止流量过载的智能阀门——通过令牌桶等算法,把并发请求控制在服务能承受的范围内;熔断是防止故障蔓延的保险丝——当下游模型持续报错,主动切断流量;降级是提供兜底方案,半开探测则让服务在恢复后安全回归。本文就从小区的供水故事出发,把这台「稳压器」的零件逐一拆开看。
掌握限流熔断,等于给 LLM 应用装上稳压器:流量再猛也能稳,服务再抖也不崩,这是大模型工程化落地的核心必修课。
可视化演示
动态演示限流熔断的核心机制,通过源头/管道/节点/终端四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
限流熔断:限流熔断 限流熔断核心概念与架构设计 限流算法原理与对比分析 核心机制可视化 限流熔断整体架构与关键组件
限流算法对比
小区供水总管进了蓄水池之前,物业要控制高峰时段的水流。如果任用户狂接水,水压会崩,水管会裂。于是工程师拿出四种阀门方案:有的按整点开关,有的盯住过去一小时的总用水量,有的把出水口做得极小,有的则配一个蓄水桶。这就是限流算法的雏形。
在 LLM 应用中,固定窗口限流相当于「每个整点重开一扇门,窗口内最多放行 N 个请求」;滑动窗口则把统计区间切成许多小片,不断往前平移;漏桶把请求像水一样漏到下游,速率恒定;令牌桶在每秒补充一定量令牌,请求必须拿到令牌才能通过。
选型没有银弹:固定窗口实现成本低,但窗口边界可能冲进两倍流量;滑动窗口更精确但内存开销大;漏桶适合严格匀速的场景,比如批量导入;令牌桶允许短时突发,适合读多写少、有尖峰的 API。
四种阀门对应四种取舍。做 LLM 应用时,先想清楚你的用户是「早晚高峰的住宅」还是「持续出水的工厂」,再决定用哪种阀门。
- 请求计数n_i 表示第 i 个时间片内的请求数,滑动窗口把总时间划分成小片。
- 窗口累加从当前时刻往前推 Δt 秒,把所有小片的请求数加起来。
- 限流条件总和不超过上限 L 才允许请求进入,否则拒绝。
令牌桶原理
物业最终选中了蓄水桶方案。水阀内部藏着一只小桶,水管以每秒 r 滴的速率往桶里滴水;桶满后多余的水漫出不再积累。每次用户请求进来,必须从桶里取走 k 滴水,取不到就被拒之门外。
这只桶在技术上叫令牌桶。r 是令牌补充速率,C 是桶容量,k 是单个请求消耗的令牌数。它能实现两件事:平时匀速向系统注入流量,遇到瞬时爆发时,把桶里囤积的令牌一次性放出去。
实现时不需要真的定时器,只要记录上次补充时间,用时间差乘以 r 就能算出本次补了多少令牌。
这只蓄水桶就像家里的小水塔,白天偶尔多洗几件衣服不会断水,但如果天天爆水管,水塔也没用。令牌桶让系统在「容忍突发」和「总量可控」之间找到了平衡点。
- 当前令牌数T 表示桶中可用令牌数,初始值为容量 C。
- 按时间补充经过 Δt 秒后,补入 r×Δt 个令牌。
- 容量封顶超过 C 的部分直接丢弃,保证突发量有上限。
限流策略选择
选阀门不是越贵越好。办公楼白天用水集中,住宅区早晚高峰,医院必须 24 小时不断水。小区给不同区域配了不同阀门,这就是限流策略的选择。
如果你的 LLM 应用是给电商大促做客服,突发流量集中在开售 10 分钟,令牌桶能轻松扛住;如果是风控模型评分,要求每秒请求量绝对平滑,漏桶更合适;如果需要精确审计每一秒的流量,滑动窗口更透明。
还要算成本:桶容量 C 定太大,等于把限流关了;定太小,日常流量都过不去。更复杂的系统会做多级混合,比如入口用滑动窗口限总量,下游给每个用户用令牌桶限配额。
选择限流策略,本质是回答一个问题:你的用户能接受「偶尔等一下」,还是「必须每分钟都有水」?答案决定阀门形状。
- 峰值请求量Q_peak 是业务高峰期累计到达的请求总数。
- 时间窗口T_window 是这段高峰持续的时间长度,单位秒。
- 需求速率两者相除得到必须提供的平均处理速率,低于它就一定会排队或拒绝。
熔断基础概念
就算装了阀门,下游管道仍然可能因为压力异常爆裂。小区管道上还有一个自动保护开关,一旦检测到水压超限,就直接把水切断,保护住户电器不被冲坏。这个开关就是熔断器。
LLM 应用的熔断器要监控三个信号:失败次数、错误比例、响应超时。当错误比例超过阈值,或者大量请求超时,熔断器认为下游「水管破了」,主动把流量切断,让故障服务有时间恢复。
阈值设定是艺术:设太低,一次小抖动就熔断,用户体验雪上加霜;设太高,保护作用形同虚设。一般先根据历史 p99 延迟和错误率基线来定,再逐步压测调整。
保险丝不是为了停电,而是为了不让整栋楼烧掉。熔断器牺牲一部分请求,换来的却是整个 LLM 服务不会因为一个慢服务而全军覆没。
- 失败计数F_count 是统计窗口内失败的请求数。
- 总请求数N_total 是同一窗口内所有进入下游的请求数。
- 失败率两者相除得到失败比例,一旦大于阈值 θ,触发熔断。
熔断状态机
保护开关不是只有「开」和「关」,而是有三档:正常供水、泄压切断、试探恢复。三档之间靠状态机自动切换,不需要人盯着。
关闭状态(CLOSED)下所有请求正常放行;一旦失败率超阈值,进入开启状态(OPEN),请求快速失败;过了冷却时间,再进入半开状态(HALF_OPEN),放少量请求试探;试探成功就回到关闭,失败就重新打开。
状态机的价值在于「有记忆」:它不会因为一次偶发抖动就反复开合,也不会永远卡在故障态。工程实现上,每个状态都对应独立逻辑,迁移条件用原子变量保护,避免并发请求同时改状态。
三档状态就像一个懂事的物业:平时不管,出事果断断水,确认修好后慢慢恢复供水。有了状态机,系统才拥有自我修复的循环。
- 熔断开启当失败率超过阈值 θ,下一个状态变为 OPEN。
- 冷却结束OPEN 状态持续超过冷却时间 T_cool 后,转入 HALF_OPEN。
- 试探成功HALF_OPEN 下探测成功率达标,回到 CLOSED。
降级策略
熔断切断供水之后,住户不能干等着。物业会启动备用水源:临时接一根管子送应急水,或者让大家先用水桶里的存水。这就是降级策略。
LLM 应用常见的降级方案有四类:兜底响应,直接返回「服务繁忙,请稍后再试」;缓存降级,把上一次成功的结果返回给用户;静态回复,给定期权公告等固定文案;异步降级,把非实时任务丢进消息队列,等下游恢复后再慢慢处理。
降级的核心原则是「有响应好过无响应」。对用户来说,五分钟之后拿到准确的摘要,比 30 秒后收到超时报错更友好。工程上把降级逻辑封装成 fallback 函数,与主流程解耦。
备用管道虽然没有自来水新鲜,但至少不让整栋楼断水。降级的本质是用户可感知的轻微劣化,换回系统整体的稳定。
- 命中缓存优先返回缓存中最近一次成功结果,保证信息不缺失。
- 无缓存兜底没有可用缓存时,返回固定文案,让用户知道服务状态。
半开探测
水管修好后,物业不会一下子把总闸开到底,而是先拧开一点,观察水压是否正常。确认没问题,再完全恢复。这个动作叫半开探测。
熔断器进入 HALF_OPEN 后,放行 m 个探测请求。如果其中有 k 个成功,且成功率不低于设定阈值,就认为下游已经恢复,状态切回 CLOSED;如果探测失败,立刻回到 OPEN,并重新计时冷却。
探测流量要控制得很小,比如 m=10、k=8,否则下游刚喘口气又被打垮。实际系统中,探测请求可以走单独的小流量集合,甚至只探测健康检查端点。
小步快跑比一次性全量恢复更让人安心。半开探测让系统在恢复过程中始终保持警惕,既不放走故障,也不错过康复。
- 探测总量m 是半开状态放行的探测请求数,通常是个位数。
- 成功数k 是其中成功返回的请求数,成功定义由健康检查决定。
- 成功率k/m 达到 θ_probe,状态机关闭熔断;否则保持开启。
LLM应用实践
阀门、保护开关、备用水源都装好了,还得有一个调度室把它们串起来。调度室里能看到每个环节的压力、水温和报警。这就是 LLM 应用可观测性的雏形。
高并发场景下,在 API 网关层做令牌桶限流,在调用大模型 SDK 的客户端里做熔断,再在业务层写降级函数。三层各司其职:限流挡住过量请求,熔断挡住慢依赖,降级兜住用户体验。
最怕的是级联雪崩:一个模型服务变慢,导致所有上游线程阻塞,最终整个应用死去。所以还要监控可用性指标、设置告警水位线,让调度室能在深夜自动叫醒值班人。
稳压器不是把用户挡在门外,而是让尽量多的人在波动中依然能用上水。限流、熔断、降级、半开探测组合起来,就是 LLM 应用最坚实的稳压器。
- 成功请求数N_success 是单位时间内正常完成响应的请求数。
- 总请求数N_total 是进入系统的总请求数。
- 可用性两者相除并乘以 100%,得到服务可用性指标,用来设定告警和容量水位。
AI 应用场景
LLM API 网关层限流
阶段:部署与运维 / 流量治理
解决的问题:没有限流时,突发的用户请求会拖垮模型服务,产生超时毛刺。用令牌桶在网关层把 QPS 控制在模型峰值吞吐以下,把多余请求排队或拒绝。
具体案例:一家公司上线文生图功能,营销活动带来 5 倍流量。通过在网关前置令牌桶限流,保证峰值 1000 QPS 以内,剩余请求返回 429 并引导稍后重试。
客服机器人熔断降级
阶段:运行时故障处理
解决的问题:底层大模型 API 故障时,所有客服请求都会失败。熔断器在错误率超阈值后自动开启,后续请求走缓存或兜底话术,避免用户体验崩盘。
具体案例:电商大促期间大模型供应商限流,客服机器人调用多次超时。熔断器 5 秒内打开,机器人改回复「正在升级,请稍后」,同时把对话记录异步写入队列,服务恢复后补答。
批量推理任务保护
阶段:批处理与异步调度
解决的问题:离线批量推理任务一次性提交上万条,容易压垮 GPU 服务。用令牌桶给任务队列配速,结合熔断器,在推理服务异常时暂停消费。
具体案例:内容审核系统每天凌晨跑全量视频标注,使用令牌桶限流到 200 并发,熔断阈值设为错误率 30%。某天模型升级失误导致部分请求失败,熔断触发后任务自动暂停,避免无效重试浪费算力。
Python 演示
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()
self.tokens = min(self.capacity, self.tokens + delta * self.rate)按时间差补充令牌,并用容量封顶。if self.tokens >= cost: self.tokens -= cost令牌足够才放行请求,否则直接拒绝。if self._total_count >= self.fail_limit and self._failure_count / self._total_count > self.threshold:失败比例超过阈值后开启熔断。time.monotonic() >= self._recovered_at冷却时间结束后才允许进入半开状态。half_open_probes < self.fail_limit半开状态只放行少量探测请求,避免再次打垮下游。
常见误区
误区限流和熔断差不多,都是限制请求,熔断就是限流的一种。
正解限流是事前控制,像阀门,阻止流量超过安全水位;熔断是事后保护,像保险丝,发现下游异常后切断电流。限流防止过载,熔断防止故障蔓延。二者可以独立使用,也必须配合。
为何常见:两者都出现在稳定性治理里,名字里都有「流/断」,很容易混。
误区令牌桶容量设得越大越好,这样用户永远不会被拒。
正解桶容量 C 决定瞬时突发上限。C 太大,突发流量会直接击穿下游 LLM 服务,限流形同虚设。应按下游峰值吞吐和用户容忍度设定。
为何常见:产品总想少拒绝用户,技术容易在指标上妥协。
误区熔断阈值越低系统越安全,稍微出点错就应该立刻熔断。
正解阈值过低会让偶发抖动触发熔断,造成大面积不可用。熔断阈值应高于正常波动、低于灾难水平,通常在错误率 0.5 附近,再经压测调整。
为何常见:把「保护」理解成越激进越好,忽略了可用性也有成本。
误区熔断后直接抛 500 错误就行,反正故障不是我的责任。
正解熔断的意义是提供降级体验。返回兜底文案或缓存结果,用户至少知道服务在努力;直接抛错等于把故障变成事故。降级策略是熔断闭环的一部分。
为何常见:低峰期没做演习,只实现了熔断没实现降级。
误区半开探测放行越多请求,恢复得越快,所以可以直接放一半流量。
正解半开状态是为探测恢复情况,应只放行极小流量(如 5-10 个请求)。放行太多可能把刚缓过来的下游再次打死,形成反复熔断。
为何常见:希望尽快恢复,忽略了熔断器原本就是「怀疑下游还没好」。
面试题
Q1. 以下哪种限流算法允许一定程度的突发流量?
- A.固定窗口法
- B.滑动窗口法
- C.令牌桶算法← 正确答案
- D.漏桶算法
答案:C。令牌桶靠预存令牌在突发时一次性释放,允许短时突发;固定窗口和滑动窗口在窗口内也允许突发但有边界问题,漏桶输出速率恒定,不允许突发。因此选 C。
Q2. 熔断器处于开启状态时,进入的请求应如何处理?
- A.直接放行
- B.排队等待
- C.快速失败← 正确答案
- D.重试三次
答案:C。熔断开启意味着下游故障,请求应当快速失败,避免继续压榨下游。放行、排队、重试都会放大故障影响。
Q3. 令牌桶中决定瞬时最大放行量的参数是?
- A.入桶速率
- B.桶的容量← 正确答案
- C.请求超时
- D.重试次数
答案:B。桶容量 C 是囤积令牌的上限,限制了瞬时放行总量;入桶速率决定长期平均速率,超时和重试与令牌桶无关。
Q4. 半开状态的核心目的是?
- A.永久拒绝请求
- B.试探下游恢复← 正确答案
- C.降低令牌桶速率
- D.增加请求重试
答案:B。半开状态放行少量请求,目的是试探下游是否恢复,成功则关闭熔断,失败则重新打开。与限流参数无关。
Q5. 固定窗口限流的主要缺点是?
- A.实现逻辑复杂
- B.无法平滑限速← 正确答案
- C.不支持秒级控
- D.占用内存过大
答案:B。固定窗口在窗口切换边界可能出现两倍突发流量,本质是限速不平滑。它实现简单、内存小,也支持更小窗口,因此 B 正确。
Q6. 熔断器从开启状态进入半开状态通常依赖什么?
- A.手动重启服务
- B.冷却时间结束← 正确答案
- C.请求全部成功
- D.令牌桶被清空
答案:B。开启状态要等待冷却时间 T_cool 结束才进入半开,冷却时间是为了给下游留出恢复窗口。手动重启不符合自动恢复理念。
Q7. 以下哪些属于合理的降级策略?
- A.返回缓存结果← 正确答案
- B.返回兜底文案← 正确答案
- C.无限重试请求
- D.异步写消息队列← 正确答案
答案:ABD。缓存、兜底文案、异步降级都能在有损情况下保持系统响应;无限重试会放大流量压力,不是降级策略。
Q8. 关于滑动窗口与固定窗口,下列说法正确的有?
- A.滑动窗口平滑边界← 正确答案
- B.固定窗口实现简单← 正确答案
- C.滑动窗口无峰值流量
- D.固定窗口可用计数← 正确答案
答案:ABD。滑动窗口通过细化时间片平滑边界流量,固定窗口用计数器即可实现,因此 A、B、D 正确。滑动窗口只是边界更平滑,不代表没有峰值流量,C 错。
Q9. 关于令牌桶与漏桶,下列说法正确的有?
- A.令牌桶允许突发流量← 正确答案
- B.漏桶输出速率恒定← 正确答案
- C.令牌桶无需时间戳
- D.漏桶能应对流量峰值
答案:AB。令牌桶允许突发,漏桶输出速率恒定,因此 A、B 正确。令牌桶需要时间戳计算补充量,C 错;漏桶本身不能应对峰值,只会把突发平滑掉,D 错。
Q10. 熔断器关闭状态下,窗口内总请求数 100,失败 60,阈值 0.5,此时应进入?
- A.保持关闭状态
- B.进入开启状态← 正确答案
- C.进入半开状态
- D.立即降级响应
答案:B。失败率 60% 大于阈值 50%,满足开启条件,应进入 OPEN。半开是开启后冷却结束才进入;降级是熔断后的策略,不是状态。