EN

AI 服务的优雅停机

正确处理 AI 推理服务器中的关闭信号

返回教程列表
高级10 分钟

AI 服务的优雅停机

正确处理 AI 推理服务器中的关闭信号

AI 服务优雅停机(2026):AI 请求在途时间长(秒-分钟级),naive shutdown 损失更大。三种形态实现:API(readiness 翻红+排空窗口=p99 生成时长)、流式(带内事件+取消上游止损)、队列 worker(redelivery+幂等让 SIGKILL 都不丢活)。

AI 服务的优雅停机

每次部署、自动缩容和抢占式实例回收都会向你的 AI 服务发送 SIGTERM。接下来的操作决定了是干净地切换,还是留下一堆半成品生成、断流的连接和重复计费的工作:AI 服务的在途请求异常长(生成时间从秒到分钟级),因此 naive 的关闭方式比普通 API 损失更多工作。 本指南为三种 AI 服务形态实现优雅停机:请求/响应 API、流式端点和队列 worker。

关闭契约

收到 SIGTERM 后,行为良好的服务应:

  • 停止接受新工作(使 readiness 探测失败 / 停止轮询队列)
  • 在排空窗口内完成或安全中止在途工作
  • 干净地释放资源(刷新日志/指标,关闭数据库/HTTP 连接池,正确确认/否认队列消息)
  • 在编排器的硬杀(SIGKILL)截止时间前退出
  • AI 特有的矛盾:一个 70B 模型的生成时间可能超过默认的 30 秒宽限期。你要么延长截止时间,要么设计不会浪费工作的中止方式。

    请求/响应 API(FastAPI 形态)

    python
    import asyncio, signal
    from contextlib import asynccontextmanager
    from fastapi import FastAPI, Response

    draining = False in_flight = 0

    @asynccontextmanager async def lifespan(app: FastAPI): loop = asyncio.get_running_loop() loop.add_signal_handler(signal.SIGTERM, start_drain) yield # lifespan 退出:等待在途请求归零(有界) for _ in range(120): # 匹配你的最大生成时间 if in_flight == 0: break await asyncio.sleep(1)

    def start_drain(): global draining draining = True # 下面的 readiness 变为红色

    @app.get('/healthz/ready') async def ready(): return Response(status_code=503 if draining else 200)

    负载均衡器看到 readiness 失败,将新流量路由到其他地方;现有请求继续完成。terminationGracePeriodSeconds(K8s)设置为你的 p99 生成时间加上余量——这是最容易被遗忘的一行;默认的 30 秒会在规模上静默截断长生成。

    流式端点:特殊情况

    半路中断的流比错误更糟糕——用户看到回答在句子中间停止。排空时:

  • 让活跃的流完成(它们已经在生产;杀死它们会浪费已花费的成本)——这就是为什么你的宽限期必须覆盖完整的生成时间。
  • 如果必须中止(硬截止时间临近):在关闭前发送一个显式的带内事件(data: {"error":"server_restarting","resume":true}),这样客户端显示“重新连接中……”而不是冻结的光标——并且取消上游提供商的调用,这样你就不会为无人接收的 token 付费(与流式配方中相同的断开连接纪律)。
  • 客户端:一个恢复路径(以部分响应作为上下文重新提问,或从任务状态端点重新获取)将重启变成一个小插曲。
  • 队列 Worker:依赖重新投递,但要确保安全

    对于批量/异步 AI 工作(富化运行、webhook 处理器),如果满足两个属性,关闭会更简单:

  • 收到 SIGTERM 后停止轮询,完成当前消息,确认它。 如果截止时间在任务中途到达:不确认/让可见性超时过期 → 队列将消息重新投递给另一个 worker。
  • 幂等性使重新投递无成本:按消息 ID 键控工作(如果结果已存在则跳过),这样重新投递的任务不会重复调用 LLM 或重复写入。没有幂等性,“优雅”关闭仍然会产生重复的副作用——这与 API 重试处理中的精确一次纪律相同。
  • 对于多步骤 agent 任务,检查点进度(LangGraph 检查点器原生支持)使重新投递从图中恢复,而不是重新开始一个 20 步的运行。

    自托管模型服务器

    vLLM 类服务器处理 SIGTERM 时的在途完成;你的工作是围绕它们的编排:在停止 pod 之前通过 readiness 排空,并在滚动更新期间保持 maxUnavailable 保守——GPU pod 启动慢(模型加载以分钟计),激进的滚动更新设置会造成容量缺口。在排空旧 pod 之前预加载/预热替换 pod(服务指南)。

    测试它,否则它不工作

    优雅停机静默地腐烂。两个廉价的测试:一个 CI/staging 脚本,启动负载,发送 SIGTERM,并断言零丢弃/重复请求;以及在 staging 中按计划进行混沌式的 pod 杀死。第一次在高峰流量期间部署是了解你的宽限期只有 30 秒的错误时机。

    FAQ

    SIGKILL 怎么办? 按定义不可处理——你的保护是重新投递+幂等性设计,它使即使硬杀死也只损失时间。

    抢占式/可抢占 GPU? 相同的机制,但保险丝更短(云抢占通知范围从约 30 秒到 2 分钟)——积极检查点,并将抢占通知视为 SIGTERM。

    无服务器平台? 平台拥有排空语义——阅读你的提供商的生命周期文档;你的幂等性层仍然是拯救你的东西。


    *最后更新:2026 年 6 月。*

    相关工具

    pythonpython