你的负载测试在撒谎:生产环境中的 LLM 供应商容量争用
你运行了一个负载测试。你的 p95 延迟是 450ms。你对此感觉良好,上线了该功能,然后两周后你的轮值告警响了,因为用户在周二上午 9 点看到了 25 秒的响应时间。
你的代码没有发生任何变化。没有部署,没有配置更改。供应商的状态页面显示“正常运行 (operational)”。然而,你的应用在业务高峰时段持续 20 分钟无法使用。
这就是 LLM 容量争用问题,也是工程师在被坑之前最常忽视的故障模式之一。
为什么你的负载测试无法复现这一问题
当你对传统 API 进行负载测试时,你控制着服务器。你的测试直接映射到被测系统。施加足够的流量,你就能找到崩溃点。
LLM API 的工作方式不同。你是共享基础设施中成千上万个租户之一。供应商的容量是池化的——运行你请求的 GPU 同时也运行来自同一层级、同一地区、同一模型的所有其他客户的请求。
你的负载测试在凌晨 2 点的一个专用测试环境中运行。它在全局需求较低时访问供应商。此时容量充足,队列为空,你得到了具有代表性的响应时间。你围绕这些数字调整超时设置,大功告成,然后发布。
你没有测试的是:当美国东海岸开始一天的流量峰值时,成千上万的其他组织同时访问你正在使用的同一模型,你的延迟会发生什么变化。或者当某家你从未听说过的公司发布的病毒式产品导致 gpt-4o 需求激增,而你的任务正好在运行时。或者当供应商推出一个新的模型层级,悄悄地从你所在的层级抽走容量时。
你的测试环境与这种共享的生产现实之间的差距是结构性的。无论你如何调整测试套件都无法消除这种差距。
特定模式:共享峰值需求压力
LLM 供应商以每分钟请求数 (RPM) 和每分钟 Token 数 (TPM) 来发布速率限制。这些是针对单个客户的限制,而不是全局容量指标。大多数团队忽略了一个暗示:保持在速率限制内并不意味着当供应商的共享基础设施面临压力时,你能免受延迟下降的影响。
当全局需求激增时,供应商层面会发生几件事:
- 队列深度增加。你的请求在被安排到 GPU 运行之前会进入一个更长的队列。这甚至在第一个 Token 开始生成之前就增加了延迟。
- 批处理决策发生变化。推理系统将请求分批处理以提高 GPU 利用率。在高负载下,批处理组合会发生变化,这甚至会影响那些本来很快的请求的首字延迟 (TTFT)。
- 区域失衡出现。供应商在不同区域调度流量,但容量分布并不完美。如果一个区域过载,请求会溢出到更远的区域,增加往返延迟。
2025 年 11 月的 Azure OpenAI 事件清楚地说明了这一点:由于全球高需求导致的后端容量限制,Sweden Central 地区的延迟从 2-5 秒飙升至 60 秒以上。单个客户的速率限制从未被触及。共享基础设施只是饱和了。
OpenAI 2024 年 12 月的事件遵循类似的模式——在高需求期间,负载均衡器配置错误和基础设施升级产生了连锁反应,导致 45% 的 API 请求在 90 分钟内返回错误。这并不是因为任何单个客户的限制被超出,而是因为共享系统在负载下失效了。
到 2025 年第一季度,整个行业的 LLM API 正常运行时间从同比 99.66% 下降到 99.46%——随着需求增长超过基础设施扩展速度,停机时间增加了 60%。像 Anthropic 这样的个别供应商在 90 天内平均发生了大约 158 起事件,中位持续时间超过一小时。
你应该追踪(但可能没有)的指标
大多数负载测试测量传统工具测量的指标:每秒请求数、错误率、响应时间。这些对 LLM API 来说是错误的指标。
真正重要的指标:
首字延迟 (TTFT):从提交请求到接收响应的第一个 Token 的延迟。这就是用户感知的“卡顿”。目标值:对话式界面低于 500ms,Copilot 类助手低于 200ms。在供应商容量压力下,TTFT 是第一个下降的指标——它直接反映了队列深度。
Token 间延迟 (ITL):流式响应中连续 Token 之间的时间。超过 100ms 的值会产生明显的断断续续感。这反映了供应商当前的解码吞吐量,并且对 GPU 资源争用很敏感。
p99 延迟,而不是 p50:你的中位响应时间是一个虚荣指标。遇到 p99 情况的用户,即那些在别人等 2 秒时要等 20 秒的用户,才是提交支持工单并流失的用户。在生产环境中独立于测试环境追踪你的 p95 和 p99。
有效吞吐量 (Goodput):在你的 SLO 截止日期内成功完成的请求比例。对于超时设置为 10 秒的产品,一个耗时 90 秒并成功的请求并不是成功——它是一个消耗了配额却没给用户返回任何结果的失败。
多供应商对冲:实用架构
对抗供应商容量争用的唯一结构性防御手段是不将所有流量依赖于单一供应商。这不仅仅是一种“应急备份供应商”的设置——它是一种主动的流量分配。
经历过这种情况的团队所采用的模式如下:
基于延迟的路由:将每个请求路由到该模型层级中当前显示延迟最低的供应商。像 LiteLLM 这样的工具原生支持此功能。你可以配置一个供应商池(OpenAI + Azure OpenAI + Anthropic,或任何组合),设置健康检查间隔,路由器将根据观察到的响应时间动态分配流量。当一个供应商开始变慢时,流量会自然地从该供应商转移。
回退链 (Fallback chains):每个主要供应商配置都应该有一个明确的回退序列。一个常见的模式是:首选供应商上的首选模型 → 次要供应商上的相同模型 → 任何供应商上的更小/更快的模型。这种降级对用户是可见的(一个更快但能力稍弱的模型),但另一种选择——超时——则更糟糕。
请求对冲 (Request hedging):对于延迟敏感的路径,同时向两个供应商发送相同的请求,并返回最先响应的结果。取消较慢的那个。这虽然成本更高(你为两个请求付费),但适用于延迟波动不可接受的面向用户的流程。请仅将其保留在关键路径上。
重要的实现细节:确保你的路由层测量的是实际的生产延迟,而不仅仅是检查状态页指标。供应商在宣布“运行正常”的同时,速度可能明显慢于正常水平。你在请求级别的 p95 延迟测量会在状态页更新之前捕获到这一点。
容量预留:当对冲不足以解决问题时
多供应商对冲减少了你暴露于任何单一供应商故障的风险。但如果你有严格的 SLO 承诺(无论是内部还是外部),且拥有可预测的高容量工作负载,仅靠对冲可能是不够的。你仍然在与共享池中的其他所有租户竞争。
供应商通过预留容量层级解决了这个问题:
AWS Bedrock 预置吞吐量 (Provisioned Throughput):为选定模型提供专用容量,无论实际使用情况如何,均按小时计费。你不再处于共享池中——你的请求将进入预留的计算资源。损益平衡点:如果你在预留 容量上的使用率超过了按需付费价格的 50–70%,那么预置吞吐量不仅能省钱,还能带来可预测性。
Azure OpenAI 预置吞吐量单位 (PTUs):类似的预留模式,按每个 PTU 每月计费。这种权衡是明确的:你提前承诺容量,作为交换,你获得可预测的延迟,且不会暴露在共享层级的拥塞中。
OpenAI 的层级系统:OpenAI 的付费层级(Tier 3 及以上)提供更高的速率限制,以及隐含的优先访问权。持续支出较高的团队较少受到共享层级容量压力的影响。
需要注意的一点:如果供应商的基础设施发生更广泛的故障,预留容量也无济于事。它将你从租户之间的争用中隔离出来,但无法免受基础设施故障的影响。这就是为什么预留和多供应商对冲是互补的,而不是替代关系。
熔断机制与感知 SLO 的重试
重试是应对 LLM API 故障的默认反应,但在容量压力事件期间,它们往往会适得其反。当一个供应商性能降级时,每次重试都是另一个争夺稀缺容量的请求。重试风暴——数千个客户端同时重试——可能会让一个已经在苦苦支撑的供应商崩溃的时间比底层问题本身持续的时间更长。
正确的模式是分层的:
超时优先,而非重试优先:设置激进的单次请求超时,以反映你实际的 SLO 预算,而不是供应商能承受的最大值。如果你的产品要求 10 秒内响应,那么在 8 秒时就超时,而不是 30 秒。不要让慢请求堆积。
带有全抖动的指数退避 (Exponential backoff with full jitter):重试时,不要按固定间隔重试。每次尝试将等待时间翻倍,并加入随机抖动来分散重试分布,同时限制总重试预算(不只是次数——一个在 30 秒内重试 5 次的单个请求太慢了,已经失去了意义)。抖动是必选项;如果没有它,重试会聚集并引发惊群效应 (thundering herd)。
供应商层面的熔断器:跟踪每个供应商的错误率和 p95 延迟。当任一指标超过阈值时,开启熔断——在冷却期内停止向该供应商发送请求。冷却期结束后,允许单个探测请求测试恢复情况。这种三态模型(关闭 -> 开启 -> 半开启)既能防止级联故障,又能避免永久排除。
在实践中有效的配置:在 60 秒窗口内错误率达到 20–30%,或 p95 延迟超过正常基准 2 倍并持续 5 分钟后,熔断器开启。冷却期:30–60 秒。这些数字需要根据你的具体工作负载进行调整,但它们是合理的初始默认值。
可观测性:在用户感知前察觉供应商容量压力
在“出现异常”与“用户受影响”之间的空隙,正是你的可观测性技术栈发挥价值的地方。在用户可见的性能下降之前,会有如下信号:
按供应商和区域划分的 TTFT:在特定的供应商-区域组合中,TTFT 中位数增加 20% 是容量压力正在积聚的早期预警。你会在 p99 延迟影响到用户前的 5–10 分钟观察到这一现象。应针对 TTFT 趋势进行告警,而不仅仅是绝对阈值。
请求队列时长 (Request queue age):如果你使用了网关层(如 LiteLLM、Portkey 或自研工具),请追踪请求在调度前在队列中停留的时间。队列时长的增长是供应商响应变慢和自身吞吐量达到极限的先行指标。
按供应商划分的错误率:来自特定供应商的 429 (速率限制) 和 5xx 错误是最清晰的信号。以 1 分钟为粒度聚合这些数据,并在任何单一供应商的错误率达到 5% 时触发告警。
Token 吞吐效率:追踪每秒实际交付的 token 数与预期值的对比。一个通常每秒生成 80 个 token 但现在平均只有 20 个的模型,正经历着后端竞争,而这种竞争在错误率中是体现不出来的。
OpenTelemetry 的 LLM 可观测性规范为这些指标提供了标准模式。如果你通过 Langfuse 或 LangSmith 等平台对 LLM 调用进行埋点,它们通常开箱即用。如果你是自行构建,请在每次请求中将 TTFT、ITL、总 token 数和错误代码作为结构化日志事件输出 —— 你可以以此为基础构建仪表盘和告警。
总结:生产级就绪的架构
那些不会因为 LLM 容量事件而被呼叫的团队,通常运行着如下架构:
-
在所有 LLM 调用前设置网关层 —— 而不是让每个独立服务直接调用供应商。网关负责集中管理路由决策、重试逻辑和熔断器状态。
-
针对同等模型能力在 2 个以上供应商间实现双活 (Active-active),并采用基于延迟的路由。不是热备,而是实时流量切分。
-
针对每个供应 商设置熔断器,并以 p95 延迟 + 错误率为触发条件。
-
重试预算 (Retry budgets) 与面向用户的请求全局截止时间挂钩,而不是单个 LLM 调用超时时间。
-
分供应商的 TTFT 和 p99 延迟仪表盘,在达到基准值 1.5 倍时设置告警,在值班电话响起前发出通知。
-
预留容量,用于合同中承诺了 SLO 的最高优先级、高流量业务。
这些方法并不新奇。这些模式直接借鉴了团队运行数据库和有可用性要求的外部 API 的经验。不同之处在于,LLM API 还很年轻,故障历史记录尚不完善,而且共享容量模式意味着你的受影响范围包含了同一层级的所有其他组织。
你的压力测试并非有意误导。它们只是无法预见那些无法控制的因素。构建架构时,不要假设测试环境就是现实的全部。
- https://reintech.io/blog/llm-load-testing-benchmark-ai-application-production
- https://gatling.io/blog/load-testing-an-llm-api
- https://status.openai.com/history
- https://status.anthropic.com/history
- https://docs.litellm.ai/docs/routing
- https://openrouter.ai/docs/guides/routing/provider-selection
- https://portkey.ai/blog/failover-routing-strategies-for-llms-in-production/
- https://portkey.ai/blog/retries-fallbacks-and-circuit-breakers-in-llm-apps/
- https://aws.amazon.com/bedrock/pricing/
- https://www.palantir.com/docs/foundry/aip/llm-capacity-management
- https://opentelemetry.io/blog/2024/llm-observability/
- https://zuplo.com/learning-center/token-based-rate-limiting-ai-agents
