案例 vLLM 长时运行宕机复盘:自愈 supervisor 误杀 940 次

2026-08-30 21:06:03

vLLM 长时运行宕机复盘:自愈 supervisor 误杀 940 次

事故表象:5-token 请求压垮整个服务

事故现象是一个 5-token 的单一请求打挂了整个 vLLM(DeepSeek-V4) 服务。第一反应都指向"前缀缓存清理不掉",排查方向完全跑偏了。

真实根因:decode 单步耗时超线性增长

实际根因链条:

  • 单个超长上下文请求的 decode 单步耗时随序列长度呈超线性增长
  • EngineCore 等待 worker 返回结果时触发超时:VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS 默认 300s
  • 超时后抛 TimeoutError → EngineCore fatal

关键点:这不是缓存问题,是单请求步耗时把 RPC 超时窗口打穿了。

vLLM 不自愈:容器活着,服务已死

这一层最迷惑:

  • EngineCore fatal 后,容器进程依然存活
  • docker 层面看:running=trueRestartCount=0
  • 实际状态:API 永久挂死,残留 worker 进程继续占着显存

没有外部干预的情况下,这个状态永远不会恢复。

二次事故:自愈 supervisor 本身成为事故源

为了兜底不自愈的问题,引入了外部 supervisor,逻辑设计合理:

  • 每 30s 探测一次 /health
  • 连续 3 次失败,或日志命中 EngineCore fatal,执行 docker rm -f 重建容器

坑在这里:

启动宽限参数 STARTUP_GRACE 设成了 180s,而 vLLM(DeepSeek-V4) 完整冷启动实测要 6-7 分钟。180s 宽限远小于实际启动时间。

于是出现一个反直觉的场面:

  • 没有任何真实流量
  • 服务处于正常冷启动过程中
  • supervisor 在宽限期结束后持续探测 /health 失败,判定为宕机
  • 执行重建,进程被杀死,重新启动
  • 第二次启动又超 180s,再次被打断……

累计重建 940 次。

自愈脚本从"兜底保障"变成了"主动攻击源"。宽限参数 bug 直接导致正常启动的进程被反复误杀。

修复:宽限对齐实际启动时间

修复方案很直接:把启动宽限对齐到容器实际启动时间,STARTUP_GRACE 调到 600s。

修复后零误杀。

工程教训与信号

  • 调大 RPC 超时≠解决卡死VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS 调大是给真实慢请求留出恢复窗口;真卡死不会因为超时调大而恢复。两者要区分清楚。
  • supervisor 的价值是保证最终恢复,但它的判定逻辑必须贴合服务的真实启动耗时,否则自愈脚本自身的误判就是新的故障源。
  • 监控信号:KV 占用高 + worker 吞吐冻结,是卡死的高概率前置信号,应该作为独立的监控组合项接入告警。(事故具体持续时间原文未提供)
# 修复后的核心参数对照
STARTUP_GRACE = 180  # 错误值,远小于实际启动时间 6-7 分钟
STARTUP_GRACE = 600  # 修复值,基于容器实际启动时间对齐

# 判定逻辑(修复后)
def should_rebuild(container):
    if uptime < STARTUP_GRACE:
        return False  # 宽限期内不判定,防止误杀冷启动
    if health_check_fail_count >= 3:
        return True
    if log_contains("EngineCore fatal"):
        return True
    return False

核心结论:监控与自愈系统的参数必须按真实运行时间校准,否则兜底系统本身就是最大的不确定因素。

复制全文 生成海报 运维 vLLM 事故复盘 自愈 Supervisor

推荐文章

程序员茄子在线接单