LiteLLM Cooldown / 冷却期 全链路文档¶
本系列文档完整描述 LiteLLM Router 的 cooldown(冷却期)机制:什么时候触发、写到哪里、路由层怎么读、多久恢复、跟 num_retries / fallback / health check 怎么互动。
适用场景:
- 排查"上游某个 deployment 挂了,proxy 怎么自动避让"
- 设计多区域 / 多 key 负载均衡时理解失败逻辑
- 调整 cooldown_time / allowed_fails 等参数前理解默认行为
- 排查"为什么我设了 max_budget,超了还能调"——cooldown 不是这个语义(详见 docs/rate-limiting/)
⚠️ 常见误解:cooldown ≠ health check ≠ rate limiting。 - Cooldown:单个 deployment 短期失败 → 临时跳过这个 deployment(路由层) - Health check:定期主动 ping → 写
/health端点 + DB 表(不影响路由) - Rate limiting:用户额度超了 → 拒绝请求(auth 层)三者互不耦合。详见 04-troubleshooting.md §跟其它机制的关系.
文档目录¶
| 文件 | 内容 |
|---|---|
| 01-mechanism.md | 触发条件第一关 / 第二关(V2 默认 + V1 allowed_fails)、写入 CooldownCache、路由层读取过滤、Redis key 格式、TTL 自然恢复、Prometheus 通知 |
| 02-config-reference.md | YAML router_settings / litellm_settings / model_info 单 deployment / 环境变量 / cooldown_time 优先级 4 层 |
| 03-best-practices.md | 默认 5 秒太短的问题 / 推荐配置 / 跟 num_retries/fallbacks / latency-based-routing 的叠加 / 常见误区 |
| 04-troubleshooting.md | 6 类常见现象速查 / single deployment 特殊行为 / wildcard 永不 cooldown / allowed_fails 是 InMemoryCache 陷阱 / 跟其它机制对比 / 状态可观测性 |
| 05-git-history.md | Diff in Cooldown — 变更时间线 / 关键里程碑 / 文件级变更详情 / 最近半年变更趋势 / 核心贡献者 |
整体架构一览¶
flowchart TB
subgraph "失败方:业务请求"
Req["业务请求<br/>(用户 / 内部调用)"]
LLM["上游 LLM 返回失败<br/>5xx/429/401/408/404"]
end
subgraph "Router 失败回调<br/>litellm/router.py"
FailureCB["deployment_callback_on_failure<br/>:5740-5798"]
PreCheck["async_pre_call_check<br/>RateLimitError 分支<br/>:6040-6089"]
end
subgraph "Cooldown 决策<br/>cooldown_handlers.py"
Gate1{"_is_cooldown_required<br/>状态码白名单<br/>:40-95"}
Gate2["_should_cooldown_deployment<br/>:166-257"]
Mode1{"V2 默认模式<br/>4 条触发路径"}
Mode2{"V1 allowed_fails 模式<br/>累计计数"}
Skip[直接放行]
end
subgraph "存储层"
CC["CooldownCache (DualCache)<br/>cooldown_cache.py"]
Redis["Redis<br/>key: deployment:<id>:cooldown<br/>TTL=cooldown_time"]
Mem["InMemoryCache<br/>(60s 本地缓存)"]
end
subgraph "Router 选 deployment<br/>litellm/router.py"
ChooseDep["async_get_healthy_deployments<br/>:8523-8531"]
Filter["_filter_cooldown_deployments<br/>:9202-9223"]
AllStrategies["所有 routing_strategy 共用<br/>(latency/lowest_tpm/simple_shuffle/...)"]
end
subgraph "通知"
Callback["asyncio.create_task<br/>router_cooldown_event_callback"]
Prom["Prometheus:<br/>litellm_deployment_cooled_down"]
end
Req --> LLM
LLM --> FailureCB
LLM -.RateLimit 提前.-> PreCheck
FailureCB --> Gate1
PreCheck --> Gate1
Gate1 -- "白名单内" --> Gate2
Gate1 -- "其他错误" --> Skip
Gate2 --> Mode1
Gate2 --> Mode2
Mode1 -- "命中任一路径" --> CC
Mode2 -- "fails > 阈值" --> CC
Mode1 -- "都不命中" --> Skip
Mode2 -- "未达阈值,只 +1" --> Skip
CC --> Redis
CC --> Mem
CC --> Callback
Callback --> Prom
Req2["下一次业务请求"] --> ChooseDep
ChooseDep --> Filter
Redis -. async_batch_get_cache .-> Filter
Filter --> AllStrategies
AllStrategies --> Skip2[选可用 deployment 发请求]
style Gate1 fill:#fdd,stroke:#c33,stroke-width:2px
style Gate2 fill:#fdd,stroke:#c33,stroke-width:2px
style Filter fill:#dfd,stroke:#3a3,stroke-width:2px
style CC fill:#ffd,stroke:#aa3
几点关键事实:
- 触发只发生在 2 个位置:业务请求 failure callback(router.py:5782)+ pre-call rate-limit check(router.py:6066)。心跳失败不触发 cooldown。
- 过滤只发生在 1 个位置:router.py:9202-9223
_filter_cooldown_deployments。所有 routing strategy(latency-based / lowest-tpm / simple-shuffle / cost-based 等)共用这个过滤器。 - 没有"主动恢复"逻辑:Redis key TTL 自然过期 → 该 deployment 重新进备选池。下一次业务请求过来如果撞上它失败,再次 cooldown。
- 存储用 DualCache:内存层 60s 缓存 + Redis 层。多 Pod 共享 Redis,所以一个 Pod cooldown 一个 deployment,其他 Pod 也跳过。
allowed_fails计数器则不是 DualCache 而是 InMemoryCache(router.py:487-489)—— 多 Pod 间不共享!见 04 §4.
速查:默认行为¶
| 维度 | 默认值 | 说明 |
|---|---|---|
DEFAULT_COOLDOWN_TIME_SECONDS |
5 秒 | TTL 短得激进,强调"快速试错快速恢复" |
| 触发模式 | V2 默认(基于失败率) | YAML 没设 allowed_fails 时 |
| 触发状态码 | 401/404/408/429/5xx | 4xx 其它(400/403/422 等)不触发 |
disable_cooldowns |
False | 全局可关 |
| 数据存储 | DualCache(内存 60s + Redis 5s) | 多 Pod 共享 Redis 列表 |
| Prometheus 指标 | litellm_deployment_cooled_down |
累计计数器 |
触发条件速查(V2 默认模式)¶
flowchart LR
Fail[失败请求<br/>状态码白名单已通过]
Path1{"429 + 多 deployment ?"}
Path2{"100% 失败<br/>且当分钟流量 ≥1000 ?"}
Path3{"失败率 >50%<br/>且当分钟流量 ≥5<br/>且多 deployment ?"}
Path4{"litellm._should_retry<br/>== False<br/>(永久错误如 401)?"}
Skip[不 cooldown]
Cool[★ Cooldown 该 deployment<br/>5 秒]
Fail --> Path1
Path1 -- 是 --> Cool
Path1 -- 否 --> Path2
Path2 -- 是 --> Cool
Path2 -- 否 --> Path3
Path3 -- 是 --> Cool
Path3 -- 否 --> Path4
Path4 -- 是 --> Cool
Path4 -- 否 --> Skip
style Cool fill:#fdd
style Skip fill:#dfd
4 条触发路径任一命中即 cooldown。代码见 cooldown_handlers.py:166-257 _should_cooldown_deployment。
详细每条路径的实际触发场景见 01-mechanism.md §3.
决策树:你应该看哪一篇?¶
flowchart TD
Q{你的问题是?}
Q -->|"想理解整体怎么跑"| A1[01-mechanism]
Q -->|"配置在哪里写、有哪些字段"| A2[02-config-reference]
Q -->|"想调优(默认 5s 太短/想加 allowed_fails)"| A3[03-best-practices]
Q -->|"线上 deployment 没自动避让/避让过度"| A4[04-troubleshooting]
Q -->|"想知道跟 health check 啥区别"| A4
style Q fill:#cef,stroke:#369
一分钟自检¶
如果你怀疑当前 proxy 的 cooldown 行为不符预期:
1. 你 router 走的是哪个模式?¶
grep -E "cooldown_time|allowed_fails|allowed_fails_policy|disable_cooldowns" \
proxy_server_config.yaml
- 啥都没设 → V2 默认模式,5 秒 cooldown,按失败率触发
- 设了
allowed_fails或allowed_fails_policy→ V1 模式,按累计失败次数触发
2. cooldown 在不在工作?¶
业务请求触发一次失败(比如改个错误 api_key 强制 401)后立刻看 Redis:
.venv312/bin/python -c "
import os, redis
r = redis.Redis(host=os.environ['REDIS_HOST'], port=int(os.environ['REDIS_PORT']),
password=os.environ.get('REDIS_PASSWORD'))
print('cooldown keys:', r.keys('deployment:*:cooldown'))
"
- 看到
deployment:<uuid>:cooldownkey → cooldown 工作正常 - 没看到 → 看 04 §1 排查
3. cooldown 持续多久?¶
.venv312/bin/python -c "
import os, redis, time
r = redis.Redis(host=os.environ['REDIS_HOST'], port=int(os.environ['REDIS_PORT']),
password=os.environ.get('REDIS_PASSWORD'))
keys = r.keys('deployment:*:cooldown')
for k in keys:
print(f'{k.decode()} TTL={r.ttl(k)}s')
"
TTL 应该 ≤ 你配的 cooldown_time(默认 5)。
跟 Health Check 的对比(再次明确)¶
| 维度 | Cooldown | Health Check |
|---|---|---|
| 触发 | 业务请求失败 | proxy 主动 ping |
| 影响 | 路由决策(跳过该 deployment) | 仅 /health 端点 + DB 表 |
| 默认时长 | 5 秒 | 30s(tick)or 5min(真实 ping) |
| 多 Pod 协调 | Redis 共享 | Redis 共享(详见心跳文档 §3) |
| 恢复 | TTL 自然过期 | 下一次心跳 |
| 真正防护业务 | ✅ | ❌ |
→ 真正在 prod 防止用户撞墙的是 cooldown,不是心跳。
三个最常用配置组合¶
A. 默认(你们 prod 当前)¶
代价:单次抽风可能让该 deployment 进 cooldown(路径 1.4 永久错误立即冷)。但 5 秒就恢复,影响很小。
B. 推荐(小流量服务)¶
代价:上游真挂时,"无效失败请求"频率从每 5s → 每 30s。但小流量服务 prod 上其它 deployment 充裕,不影响业务。
C. 大流量 / 多 deployment 部署¶
litellm_settings:
allowed_fails_policy: # 启用 V1 累计计数模式
AuthenticationErrorAllowedFails: 3
TimeoutErrorAllowedFails: 50
RateLimitErrorAllowedFails: 100
InternalServerErrorAllowedFails: 20
router_settings:
cooldown_time: 60
num_retries: 2 # 业务请求自动 retry,失败再算 cooldown
代价:偶发抽风不会立即 cooldown,"累计失败 N 次"才冷。需要监控失败率别忘了。
详细推荐组合见 03-best-practices.md §推荐配置.
关于本系列文档¶
- 行号引用基于
master分支同步点(2026-05),上游可能漂移,遇到 grep 不到时按文件名 + 函数名定位 - 实战经验来自本项目 prod / dev 调研,不是搬运上游 docs
- 上游 cooldown 文档:https://docs.litellm.ai/docs/routing (有提及但不深入)