高可用不是堆组件:AI 平台的可用性瓶颈常常不在代码里 — 方向10
一、加了一堆中间件,怎么还是一次 GPU 驱动故障就全跪
构建 AI 推理平台时,团队容易陷入一个惯性思维:把 Web 服务的高可用最佳实践原封不动地搬到推理服务上。于是架构图上出现了 API 网关、服务网格、多副本部署、熔断、限流、自动扩缩容——组件齐全,看起来很可靠。
然后某天的故障报告是这样的:一台 GPU 节点的驱动因为内存管理 bug 崩溃了。该节点上的 4 个推理副本全部不可用。理论上,另外 2 个节点上的 8 个副本应该无缝接管流量。但实际上,流量切过去了,但因为那 2 个节点还没做 KV Cache 预热,P99 延迟从 300ms 飙到 5 秒,上游熔断器全部跳闸,整个推理链路不可用。
问题出在哪里?不是缺少高可用组件,而是这些组件对推理场景特有的状态(GPU 显存状态、KV Cache 预热状态、张量并行的通信拓扑)不感知。一个只检查 TCP 端口的健康探测,在推理场景下形同虚设。
AI 平台的可用性瓶颈,常常不在代码里,也不在中间件配置里,而在于这些"非代码但决定生死"的环节。
二、推理场景的隐性单点与状态依赖
AI 推理平台有四种传统高可用组件无法覆盖的隐性单点:
GPU 驱动与固件:这是最底层的单点。一台 8×GPU 的节点,如果驱动 crash,8 张 GPU 同时不可用。而驱动问题是操作系统级别的故障,Kubernetes 的 Pod 级健康检查对此完全无感知。Node Problem Detector 可以检测到部分 GPU 异常,但做不到驱动级的细粒度监控。
模型权重文件可用性:推理 Pod 的启动强依赖从对象存储下载模型权重。如果对象存储的某台服务器故障、网络分区导致下载中断、或镜像仓库不可用,Pod 永远处于 ContainerCreating 状态。这比 Pod CrashLoopBackOff 更隐蔽——调度器认为 Pod 还在初始化,不会触发任何故障转移。
KV Cache 预热状态:推理服务从"进程启动"到"能低延迟响应"之间存在一个预热期。预热完成前,第一批请求的延迟会高出数倍。传统健康探测认为"端口连通"就是可用,这意味着切过来的流量直接打到未预热的副本上。
向量数据库连接池:这个隐藏得最深。推理服务调用向量库做 RAG 检索,连接池的默认配置(如 max_connections=10)在正常负载下够用,但在流量切换时有大量积压请求涌来,连接池瞬间耗尽。这个问题不是代码 bug,而是运维配置没有随架构变化同步更新。
三、可观测性是可用性的前置条件
许多"不可用"的本质是"不知道为什么不可用"。当故障发生时,如果能在一分钟内定位到根因,可用性损失就是一分钟。如果花三十分钟排查,可用性损失就是三十分钟。
推理场景的可观测性需要覆盖从 GPU 硬件到应用层的完整链路:
// InferenceObservability 推理全链路可观测性收集器 type InferenceObservability struct { gpuMetrics GPUMetricsCollector modelMetrics ModelMetricsCollector infraMetrics InfraMetricsCollector } type GPUMetrics struct { NodeName string GPUIndex int MemoryUsed int64 MemoryTotal int64 Temperature float64 PowerDraw float64 PCIeRxBytes int64 PCIeTxBytes int64 NVLinkActive bool ECCErrors int64 // ECC错误计数,非零可能预示硬件故障 } type ModelMetrics struct { ModelName string WeightHash string WarmUpPercent float64 // 预热完成度 0.0-1.0 KVCacheHitRate float64 // KV Cache 命中率 ActiveSeqs int // 当前活跃的推理序列数 QueueDepth int // 等待队列深度 TokenThroughput float64 // tokens/s } // CheckHealthWithContext 感知推理状态的健康检查 func (o *InferenceObservability) CheckHealthWithContext() HealthStatus { gpu := o.gpuMetrics.Collect() model := o.modelMetrics.Collect() // 规则1:GPU ECC 错误 → 硬件预故障告警 for _, g := range gpu { if g.ECCErrors > 0 { return HealthStatus{ Ready: false, Reason: fmt.Sprintf("node %s gpu %d ECC errors detected", g.NodeName, g.GPUIndex), Severity: SeverityHardwareDegradation, } } } // 规则2:KV Cache 预热低于 50% → 不可接收流量 // 端口 Ready 但性能不 Ready for _, m := range model { if m.WarmUpPercent < 0.5 { return HealthStatus{ Ready: false, Reason: fmt.Sprintf("model %s cache warmup %.0f%%", m.ModelName, m.WarmUpPercent*100), Severity: SeverityNotReady, } } } // 规则3:排队深度超过阈值 → 即将超时 for _, m := range model { if m.QueueDepth > 50 { return HealthStatus{ Ready: false, Reason: fmt.Sprintf("model %s queue depth %d exceeds threshold", m.ModelName, m.QueueDepth), Severity: SeverityOverloaded, } } } return HealthStatus{Ready: true} }可观测性的价值不在于"能看到多少指标",而在于"关键指标在故障时是否能直接指向根因"。GPU ECC 错误、KV Cache 预热率、连接池饱和度——这些指标才是 AI 推理平台真正的"可用性仪表盘"。
四、架构冗余永远不该替代根本原因分析
高可用组件的堆叠存在一个危险的陷阱——它让人产生"我已经做好了容灾"的错觉,从而不再关注底层的可靠性短板。
一个真实的例子:某团队在推理服务前堆了三层高可用防护(多副本、自动扩缩、熔断降级),但他们的模型权重存储在单机 NFS 上。NFS 服务器网络线缆松动,导致所有副本在模型重载时集体失败。三层防护形同虚设。
这说明了一个原则:每个组件的故障模式独立分析,比叠加更多组件更重要。
如何做故障模式分析:
- 列出推理链路上的每个组件(GPU 节点、对象存储、向量数据库、API 网关等)。
- 对每个组件,假设它完全不可用,推导对最终服务的影响。
- 找出那些"单一依赖"的组件——它们的故障会导致全链路瘫痪,且当前没有降级方案。
- 优先给这些单一依赖增加冗余或降级路径。
适用边界:
- 适合:任何从零搭建或正在运营 AI 推理平台的团队。
- 适合:已经堆满组件但仍然不定期故障的环境——问题大概率在设计盲区。
- 不适合:只有一个 GPU 节点的实验环境——此时讨论高可用为时过早。
五、总结
高可用不是组件齐全,而是"每一个故障模式都有对应的降级路径或冗余替代"。
AI 推理平台的可用性瓶颈常常不在代码里,而在 GPU 驱动的稳定性、模型权重的可获取性、KV Cache 的预热状态和连接池的容量规划上。这些不是代码能解决的问题,但也不是代码解决不了就可以忽视的问题。
落地方向:第一,建立覆盖 GPU 硬件到应用层的全链路可观测性。第二,对推理链路的每个组件做故障模式独立分析,找出隐性单点。第三,优先给单点依赖增加冗余,而不是继续在已经有冗余的层级上叠加。
基础设施不需要漂亮话,需要的是在故障发生后,你能在 30 秒内——而不是 30 分钟内——知道哪里出了问题。