4.2 编排与容灾


文档摘要

4.2 编排与容灾 路由决定了"调谁",编排决定的是"调的过程中如何稳、如何活、如何可控"。在百万 QPS 规模下,任何一个供应商的抖动都会被放大成大面积故障——编排与容灾,正是为这种不确定性上的那层保险。 编排层的位置与职责 编排层夹在路由决策和业务逻辑之间,它拿到"应该调哪个模型"的结论后,负责把它真正执行出去,并在执行过程中处理一切意外:超时、限流、报错、质量不达标。 编排层的两种典型实现 中心化编排服务:独立部署,所有模型调用经它转发。优点是策略统一、易观测、易灰度;代价是多一跳延迟,且自身必须高可用(否则全站不能调模型)。 网关/Sidecar 内嵌:编排逻辑嵌在 API 网关或 sidecar 里,省去一跳。适合延迟极敏感场景,但策略分散、版本难统一。

4.2 编排与容灾

路由决定了"调谁",编排决定的是"调的过程中如何稳、如何活、如何可控"。在百万 QPS 规模下,任何一个供应商的抖动都会被放大成大面积故障——编排与容灾,正是为这种不确定性上的那层保险。

编排层的位置与职责

编排层夹在路由决策和业务逻辑之间,它拿到"应该调哪个模型"的结论后,负责把它真正执行出去,并在执行过程中处理一切意外:超时、限流、报错、质量不达标。

编排层的两种典型实现

  • 中心化编排服务:独立部署,所有模型调用经它转发。优点是策略统一、易观测、易灰度;代价是多一跳延迟,且自身必须高可用(否则全站不能调模型)。
  • 网关/Sidecar 内嵌:编排逻辑嵌在 API 网关或 sidecar 里,省去一跳。适合延迟极敏感场景,但策略分散、版本难统一。

我的建议:百万 QPS 这类规模,优先中心化编排服务,把重试、熔断、灰度、协议归一这些策略当作独立资产集中治理;只要编排服务自身做了多副本 + 无状态 + 快失败,多一跳的延迟通常可压到个位数毫秒,性价比最高。

它的核心职责可以归纳为四件:

  1. 执行调度:按路由结论发起调用,管理并发、超时、重试。
  2. 故障转移:某供应商不可用时,按预设优先级切到备用链路。
  3. 流量治理:灰度、限流、熔断,避免一次异常拖垮整个系统。
  4. 协议归一:屏蔽不同供应商在接口、参数、返回结构上的差异,让上层业务无感。
编排层对多供应商的统一调度

一个判断编排层是否到位的标准:业务代码里有没有出现任何供应商专属的接口细节?如果有,说明协议归一没做干净;如果业务只认"标准模型调用"这一个抽象,说明编排层把复杂度吞掉了。这是编排层存在的根本意义。

重试与跨供应商故障转移

最基础也最关键的韧性手段是"重试 + 转移"。但要注意,重试必须带约束,否则它从保护手段变成放大器:

  • 区分错误类型:限流(429)、超时、服务错误(5xx)才值得重试;4xx 参数错误重试无意义,只会放大流量。
  • 跨供应商重试:同一个供应商内重试三次,往往只是把同一个故障点打三次。正确做法是第一次失败后,按优先级切到备用供应商——这才是真正的容灾。
  • 退避策略:指数退避 + 抖动,避免重试风暴把本就脆弱的供应商彻底打挂。
跨供应商故障转移的链路

一次失败该重试几次、转移几跳

工程上我建议设一个"总预算"而非"每跳次数":比如一次用户请求最多允许 1 次主供应商尝试 + 1 次备用转移 + 1 次兜底,总共不超过 3 次模型调用。超过就快速失败,返回降级响应(如"服务繁忙,请稍后再试"或一段缓存的历史相似回答)。这样既保住了韧性,又护住了延迟和成本,避免"为了不死而把 P99 拖到几秒"。

熔断:别让异常雪崩

重试和转移能解决"偶发抖动",但解决不了"供应商持续不可用"。这时候需要熔断:当某链路的失败率超过阈值,编排层直接短路该链路一段时间,把流量强制导向健康节点,并周期性探测恢复。

熔断有三个关键参数,调不好反而添乱:

  • 失败率阈值:通常 50% 左右触发,太低会误伤,太高则失去保护意义。可以结合绝对失败量(如 10 秒内失败 20 次且失败率 >50%)来避免低流量下偶发误触。
  • 熔断时长:太短等于没熔断,太长则恢复慢。建议用"半开"机制——熔断一段时间后放少量探测流量验证。
  • 恢复探测:绝不能熔断后就永远不用,必须自动探测可用性再逐步放量。

一个常见反模式是"全局单一熔断器":把供应商 A 的所有场景共用一个熔断状态,结果某个不重要的批量任务把 A 熔断了,连核心实时链路也被殃及。正确做法是按"供应商 × 场景"或至少按"供应商 × 优先级"做细粒度熔断,核心链路有独立的状态与预算。

一个真实故障链

设想供应商 A 突发 5xx,主链路失败率冲到 70%。没有熔断时,每个请求都在 A 上超时 3 秒才转 B,P99 直接崩到 3 秒以上,且 B 开始被积压流量反压;加上熔断后,A 在失败率超阈值的瞬间被短路,流量直接走 B,P99 只抖动了几十毫秒。这个对比,是熔断"值不值得做"最直观的答案。

熔断状态机

灰度发布:新模型别一把梭

换模型不是点个开关。新模型、新供应商、新版本,都应该走灰度:

  • 按比例放量:先 1% 流量,观察质量分数、用户停留、报错率,再逐步放大到 5%、20%、100%。
  • 对比评估:灰度期间保留老链路做对照,用一致的指标衡量"新的是否真的更好",而不是凭感觉。
  • 一键回滚:任何阶段发现指标恶化,应能秒级把流量切回老链路。
灰度发布与回滚流程

灰度时有个细节:对照组的流量分配最好"按用户稳定哈希"而非随机,否则同一用户一会儿走新模型一会儿走老模型,体验割裂且评估失真。让一个用户在灰度期内稳定落在同一链路,评估结果才可信。

统一协议:让业务无感切换

不同供应商的接口千差万别:有的用 messages 数组,有的用 prompt 字段;有的返回 choices,有的返回 outputs;错误码体系也各不相同。编排层必须做一层协议归一:

  • 请求归一:上层只传"标准请求对象",编排层翻译成各家格式。
  • 响应归一:把不同返回结构统一成内部标准结构,上层逻辑不关心背后是谁。
  • 错误归一:把各家的限流、超时、内容拦截映射成统一错误类型,便于上层做一致的重试/降级决策。

这一层抽象做好了,后面"加一个供应商""换一个模型"都只是配置变更,而不是代码改动。反过来,如果协议归一做一半——比如只归一了成功路径,错误处理各家各写——那么一旦切换供应商,bug 会集中爆发在最容易出错的异常分支上,恰好是容灾最需要的路径。

协议归一的最小字段集

建议内部标准请求对象至少包含:模型标识、消息列表、采样参数(temperature/top_p/max_tokens)、停止符、超时、是否流式;标准响应至少包含:文本内容、finish_reason、用量(prompt/completion tokens)、延迟、供应商标识。把这些固化下来,上层所有逻辑只围绕它们写,供应商差异被彻底关在编排层里。

多区域容灾与配额管理

当规模真正到百万 QPS,单一区域也可能整体不可用(机房、云region故障)。此时需要:

  • 多区域部署:编排层按用户就近或按健康度,把请求导向不同区域的模型集群。
  • 配额与预算护栏:每个供应商、每个区域设调用配额和成本上限,触发上限自动降级或转移,防止单家账单失控。
  • 全局健康视图:实时聚合各链路成功率、延迟、限流率,作为路由和熔断的输入。
多区域容灾的健康驱动调度

预算护栏这点值得单独强调:一次供应商故障 + 熔断没生效,可能让流量全部涌向备用供应商并瞬间打满其配额,引发二次雪崩。所以"转移目标"也要带各自的配额与降级策略,不能因为"它是备用"就无限放量。容灾链路之间也要互相设限。

降级响应怎么写才不惹用户烦

兜底不一定是"报错"。当所有模型链路都不可用时,返回一段缓存的历史相似回答、或一句温和的"当前服务繁忙,已为你保留上下文,稍后回复",都比冷冰冰的 500 更能留住用户。降级响应是产品体验的一部分,别把它当纯技术问题丢给前端。

可观测性:看不见就管不了

编排层产生的每一个调用,都应该留下结构化日志:调了谁、耗时多少、是否重试、是否转移、最终质量分、成本。这些是可观测性的底座。没有它,故障只能靠用户投诉发现,路由和熔断参数也永远调不准。

建议至少监控四类黄金指标:成功率、P99 延迟、跨供应商转移率、单位请求成本。转移率突然升高,往往比单个供应商失败更早预示系统性风险——它说明主链路已经在悄悄不健康,只是还没到熔断阈值。

编排层可观测性的四个黄金指标

常见坑

  • 同供应商重试风暴:前面强调过,重试一定要跨供应商,否则只是重复打同一个故障点。
  • 熔断参数一刀切:所有链路用同一阈值,结果健康的链路被误熔断,脆弱的链路却没被保护。应按场景/优先级细粒度熔断。
  • 灰度无对照:新模型上了全量,却说不清"到底好在哪"。没有稳定对照,灰度就失去意义。
  • 协议归一偷懒:只归一了成功路径,错误处理各家各写,结果切换供应商时 bug 集中爆发。
  • 容灾链路无限放量:备用供应商被主链路故障瞬间打满,引发二次雪崩。转移目标也要带配额护栏。
编排与容灾的五个常见坑

小结:路由让请求"去对的地方",编排与容灾保证"到了也能稳稳回来"。重试跨供应商、熔断防雪崩、灰度控风险、协议归一降耦合、多区域兜底、预算护栏防失控——这几件事做好了,百万 QPS 下的模型调用才真正具备生产级的韧性,而不是纸面上的"多模型架构"。


发布者: 作者: 转发
评论区 (0)
U