Fault Tolerance
容错使服务在副本、节点、网络或模型加载失败时保持可控降级和恢复。目标不是「永不失败」,而是在故障发生时让影响面可控、恢复过程自动、对用户可感知但可预期,并把故障与告警对应起来,避免同类问题反复发生。
快速开始¶
先配置基础防线:对每个副本做健康检查(存活与就绪分开)、为请求设置超时、对可重试错误做有限次重试、并部署多个副本。健康检查要能区分「进程活着但推理已不可用」的情况,例如显存耗尽或模型进程卡死。
存活与就绪探针的分工可以这样落地到 Kubernetes:存活探针只判断「进程是否还在」,就绪探针判断「是否已加载完权重、能接流量」:
livenessProbe:
httpGet:
path: /health
port: 8000
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8000
periodSeconds: 5
若就绪探针依赖模型已加载,新副本在预热完成前就不会被分配流量,避免「刚起来就再次被打挂」。
为请求设置幂等 ID 是重试安全的关键。若请求会触发有副作用的工具调用(写库、发消息、扣费),盲目重试会导致副作用重复执行;幂等 ID 让服务端能识别重复请求并返回上一次结果,而不是再次执行。
重试必须有限并配合退避:无限重试会放大故障,形成重试风暴。客户端应在收到可重试错误时使用指数退避,并对不可重试错误(如参数非法、模型不存在)直接失败,避免无意义地反复打满服务。指数退避通常写成:
其中 base 是初始退避时长,attempt 从 0 起每失败一次加 1,cap 是上限,jitter 是随机抖动。抖动让多个客户端不会在同一时刻同时重试,是防重试风暴的关键一环。
策略¶
区分瞬态故障与确定性错误是容错策略的前提。瞬态故障(网络抖动、连接重置、临时限流)值得重试;确定性错误(无效参数、权限不足、不支持的模型)重试也不会成功,应直接返回错误并告警,让调用方尽快修正。
不同故障需要不同处理。流式连接中断时,服务应停止生成并释放对应资源,客户端按可重试与否决定是否重发;GPU OOM 需要重启或降级该副本、必要时回退到更小 batch;模型进程崩溃则依赖进程管理器拉起并完成预热后再接入流量;上游限流需要退避并反馈到调度层。
可以用下面这段伪代码统一表达「按错误类型决定是否重试」:
def handle_error(err, attempt):
if err.is_retryable and attempt < max_retries:
delay = min(cap, base * (2 ** attempt)) + random_jitter()
time.sleep(delay)
return RETRY
if err.is_retryable:
return GIVE_UP_AND_ALERT
return FAIL_FAST
告警要按故障类型分级:单副本失败是常规事件,多点失败是严重事件。把故障类型、发生副本、影响请求数和恢复耗时记录到监控,才能形成「失败-恢复-改进」的闭环,而不是每次靠人工重启。
案例¶
某副本发生 OOM 后,健康检查标记其为不健康,负载均衡器停止向它分配新流量,其余副本继续服务。该副本被重启后重新加载权重、完成预热并通过就绪检查,才被重新加入流量池,避免「刚起来就再次被打挂」。
客户端侧只对可重试错误使用指数退避:第一次失败退避较短,连续失败逐步延长间隔并设置上限,超过重试次数后放弃并上报。对确定性错误则不重试,直接快速失败。
验证时观察故障期间的成功率与恢复耗时:单副本 OOM 不应导致整体不可用,p95 延迟的波动应被其他副本吸收。结论是容错的核心在于「健康检查 + 有限重试 + 幂等 + 分类型处理」,四者缺一不可。