跳转至

LiteLLM 心跳 / Health Check 全链路文档

本系列文档完整描述 LiteLLM proxy 的"模型心跳"机制:何时启动、按什么节奏跑、向上游打什么请求、多 Pod 怎么协调、配置怎么调、出问题怎么定位。

适用场景: - 排查"前端 logs 里每隔 5 分钟出现一大批模型请求,怎么压下来" - 排查"我设了 DEFAULT_SHARED_HEALTH_CHECK_TTL 但好像没生效" - 排查"K8s /health/readiness 一直不就绪" - 配置贵模型不被自动 ping、把心跳改打便宜的同家族模型 - 理解多 Pod 共享心跳的 Redis 协调如何工作、有什么坑

⚠️ 重要前置概念:"心跳"在 LiteLLM 里有两层含义。本系列文档的"心跳"特指对每一个上游 LLM deployment 的周期性真实推理调用,不是 K8s 的 liveness/readiness 探针。两者代码位置和触发机制不同,详见 01-mechanism.md §1


文档目录

文件 内容
01-mechanism.md 启动入口、后台 task 主循环、SharedHealthCheckManager 决策逻辑、真实 ping 调用栈、通配符模型扇出、DB 持久化
02-config-reference.md 全部配置项:YAML general_settingsDEFAULT_* 环境变量、model_info 单模型字段、HTTP 探针端点速查
03-cost-reduction.md 心跳成本三因子(频率 × 模型数 × 单次成本)+ 推荐组合 + 常见误区(health_check_details / num_retries / wildcard)
04-troubleshooting.md 排障流程图、4 类常见现象的定位 checklist、5min/242 条 真实案例完整复盘、模块常量"导入即冻结"陷阱、多 Pod fallback 放大 bug

整体架构一览

flowchart TB
    subgraph "外部触发(被动)"
        K8S[K8s kubelet]
        MON[监控/巡检]
    end

    subgraph "HTTP 探针层<br/>litellm/proxy/health_endpoints/_health_endpoints.py"
        EP1["/health/liveliness<br/>(无状态)"]
        EP2["/health/readiness<br/>(检 DB / Cache / Callback)"]
        EP3["/health<br/>(查 health_check_results)"]
        EP4["/health/services"]
    end

    subgraph "后台心跳 task(主动)<br/>litellm/proxy/proxy_server.py"
        START["asyncio.create_task<br/>_run_background_health_check<br/>:872"]
        LOOP["while True:<br/>tick every health_check_interval s<br/>:2160-2232"]
        BRANCH{shared_health_manager<br/>!= None ?}
        SHARED["SharedHealthCheckManager.<br/>perform_shared_health_check"]
        DIRECT["perform_health_check<br/>(直接打模型)"]
    end

    subgraph "Redis 共享协调<br/>litellm/proxy/health_check_utils/shared_health_check_manager.py"
        CACHE["health_check_results<br/>TTL=DEFAULT_SHARED_HEALTH_CHECK_TTL"]
        LOCK["health_check_lock<br/>TTL=DEFAULT_SHARED_HEALTH_CHECK_LOCK_TTL"]
    end

    subgraph "真实 LLM 调用<br/>litellm/proxy/health_check.py + litellm/main.py"
        PERFORM["_perform_health_check<br/>filter_deployments_by_id<br/>:83"]
        AHC["litellm.ahealth_check<br/>main.py:7041"]
        MODE{mode 派发}
        REAL["acompletion / aembedding /<br/>aspeech / arerank / ..."]
    end

    subgraph "结果落地"
        MEM["health_check_results<br/>(进程内全局 dict)"]
        DB["LiteLLM_HealthChecks 表<br/>(状态变化时才写)"]
    end

    K8S -->|liveness probe| EP1
    K8S -->|readiness probe| EP2
    MON --> EP3
    MON --> EP4

    START --> LOOP
    LOOP --> BRANCH
    BRANCH -- "use_shared_health_check + redis_usage_cache OK" --> SHARED
    BRANCH -- "其它情况" --> DIRECT

    SHARED -->|"读"| CACHE
    SHARED -->|"抢锁"| LOCK
    SHARED -->|"未命中且抢到锁"| PERFORM
    DIRECT --> PERFORM

    PERFORM --> AHC
    AHC --> MODE
    MODE -->|"chat"| REAL
    MODE -->|"embedding"| REAL
    MODE -->|"audio_speech"| REAL
    MODE -->|"...(共 12 种)"| REAL

    PERFORM --> MEM
    PERFORM --> DB

    EP3 -.读.- MEM
    EP4 -.读.- MEM

    style BRANCH fill:#fdd,stroke:#c33,stroke-width:2px
    style CACHE fill:#dfd,stroke:#3a3
    style LOCK fill:#dfd,stroke:#3a3

几点关键事实

  1. K8s 探针(左上)和后台心跳(中间)是两套独立机制/health/liveliness 永远返回 200,不会触发模型调用;前端 logs 里看到的"批量心跳请求"全部来自后台 task。
  2. 后台 task 是单例的,每个 Pod 进程内只有一份 _run_background_health_check 协程在跑。
  3. 是否走 SharedManager 由两个条件决定:YAML 的 use_shared_health_check=true litellm.cache.cacheRedisCache / RedisClusterCache 实例。任一不满足就走 DIRECT 路径,每个 Pod 独立地按 health_check_interval 节奏打模型。
  4. 真实模型调用通过 litellm.ahealth_check,按 mode 派发到 12 种实际 API(chat / embedding / audio_speech / ...)。每个 deployment 一次真实推理调用。
  5. 请求数 ≈ 1 × 参与轮询的 deployment 数 ÷(TTL/小时)。修复前是 Pod 数 × deployment 数,本 fork 已消除 Pod 放大(详见 04 §5)。

🔧 本 fork 修复说明:上游 PR #15380 的 SharedHealthCheckManager 在 follower 抢锁失败后只等 2 秒就回退到本地自打,导致多 Pod 部署下 cache 过期那一刻仍然 N×deployment 数请求。本仓库已把 follower 路径改为"polling 直到 lock_ttl 超时,超时跳过这一 tick,绝不本地自打"。如未来同步上游需保留本 fork 改动。详见 01 §3.304 §5


速查:配置项与节奏

配置 类型 默认 说明 详见
background_health_checks YAML general_settings false 不开就完全没有后台心跳 02 §1.1
use_shared_health_check YAML general_settings false 多 Pod 共享 Redis 协调(去重) 02 §1.1
health_check_interval YAML general_settings 300 后台 loop tick 间隔(秒) 02 §1.1
health_check_details YAML general_settings true 仅控制 /health 返回字段,不减请求数 02 §1.1 / 03 §3
DEFAULT_HEALTH_CHECK_INTERVAL env 300 YAML 未给值时的兜底 02 §2
DEFAULT_SHARED_HEALTH_CHECK_TTL env 300 shared 模式下真实 ping 节奏由它决定,YAML interval 只是 tick 02 §2
DEFAULT_SHARED_HEALTH_CHECK_LOCK_TTL env 60 leader pod 抢锁超时 02 §2
disable_background_health_check model_info(per model) false 单个 deployment 跳过轮询 02 §3
health_check_model model_info(per model) 心跳时把 model 替换成便宜的同家族模型 02 §3
health_check_timeout model_info(per model) 60 单次心跳超时(秒) 02 §3
mode model_info(per model) 自动推断 决定打 chat / embedding / 等 02 §3

决策树:你应该看哪一篇?

flowchart TD
    Q{你的问题是?}

    Q -->|"想理解整体怎么跑的"| A1[01 mechanism]
    Q -->|"配置在哪里写、有哪些字段"| A2[02 config-reference]
    Q -->|"心跳请求太多想压下去"| A3[03 cost-reduction]
    Q -->|"线上有异常想排查"| A4[04 troubleshooting]
    Q -->|"想看真实案例"| A4

    A1 -->|"还想知道单模型怎么配"| A2
    A1 -->|"还想知道怎么省钱"| A3
    A3 -->|"配置改完没生效"| A4

    style Q fill:#cef,stroke:#369

一分钟自检

如果你怀疑当前 proxy 的心跳行为不对,按以下顺序快速验证:

1. 后台 task 是否启动?

启动日志里 grep:

grep "Initialized shared health check manager\|_run_background_health_check" <proxy-startup-log>
  • 看到 Initialized shared health check manager → 走 SHARED 路径,节奏受 DEFAULT_SHARED_HEALTH_CHECK_TTL 控制
  • 没有这行但 YAML 里 background_health_checks: true → 走 DIRECT 路径,节奏受 health_check_interval 控制
  • YAML 里 background_health_checks: false 或没写 → 没有后台心跳

2. 真实节奏是多少?

进 Redis 看 cache key:

.venv312/bin/python -c "
import os, redis, time
r = redis.Redis(host=os.environ.get('REDIS_HOST','127.0.0.1'),
                port=int(os.environ.get('REDIS_PORT','6379')),
                password=os.environ.get('REDIS_PASSWORD'))
ttl = r.ttl('health_check_results')
print(f'TTL={ttl}s  (key {\"exists\" if ttl > 0 else \"missing/no-ttl\"})')
"
  • TTL ≈ 你期望值(比如 1000 或 3000)→ shared 模式工作正常
  • TTL ≈ 300 → env var 没生效(pod 没真重启 / configmap 不一致)
  • key 不存在(ttl=-2)→ shared 模式没跑起来;走的是 DIRECT 路径

3. 一次 burst 多少个请求?

近似公式:

请求数/burst ≈ Pod 数 × (model_list 中未禁用心跳的 deployment 数)

/v1/model/info 拿到 deployment 总数:

curl -sH "Authorization: Bearer $LITELLM_MASTER_KEY" \
  http://your-proxy/v1/model/info \
  | python -c "import json,sys; print(len(json.load(sys.stdin)['data']))"

如果实际观察的"请求数/burst"远大于这个数,可能是命中过的"多 Pod fallback 放大 bug"——本 fork 已修复,详见 04-troubleshooting.md §5。如果你的 prod 还在用未修复版本(上游 main),就是这个 bug。


三个最常用配置组合

A. 完全关掉后台心跳(最省成本)

general_settings:
  background_health_checks: false

代价:上游故障靠 cooldown 机制自然探测(某个 deployment 失败几次后被冷却),不再主动巡检。/health 端点仍可手动调。

B. 单 Pod / 不需要 Redis 协调

general_settings:
  background_health_checks: true
  use_shared_health_check: false
  health_check_interval: 600          # 10 分钟一次

每 10 分钟把 model_list 全部 ping 一遍。

C. 多 Pod + Redis 协调 + 贵模型跳过(生产推荐)

general_settings:
  background_health_checks: true
  use_shared_health_check: true
  health_check_interval: 30           # tick 高频,但 cache 命中时不打模型
# k8s deployment env
DEFAULT_SHARED_HEALTH_CHECK_TTL=1800     # 真实 ping 每 30 min 一次
DEFAULT_SHARED_HEALTH_CHECK_LOCK_TTL=180 # 必须大于一次完整 health check 总耗时
# UI 或 model_list 里给贵模型加
model_info:
  disable_background_health_check: true
# 或保留心跳但用便宜模型替代
model_info:
  health_check_model: gpt-4o-mini
  mode: chat

详细场景化推荐见 03-cost-reduction.md §推荐组合


关于本系列文档

  • 行号引用基于 master 分支同步点(2026-05),上游持续重构可能漂移,遇到 grep 不到时按文件名 + 函数名定位
  • 实战案例和踩坑总结来自本项目 prod / dev 环境的真实排查,不是上游 docs 的搬运
  • /health 端点行为、HTTP API 等通用内容请直接看上游 https://docs.litellm.ai/docs/proxy/health