多目标路由与故障转移
多目标模型使用一个面向调用方的别名承载多个目标模型。AISIX 为每个请求选择目标、重试符合条件的失败,并且可以在不要求应用更改模型名称的情况下执行故障转移。
该别名是面向调用方的模型名称,因此对于有权请求它的每个调用方 API Key,它都会显示在 GET /v1/models 中。将某个 Key 的作用域仅限定到该别名,即可把它作为调用方发现的唯一入口发布;此后可以更改别名的目标,而无需修改应用。
多目标模型包含 routing 块,而不是自身的服务提供方配置。每个目标都是一个已有的直接模型,具有自己的服务提供方密钥和上游模型名称。控制面通过模型 ID 引用这些目标,开源 AISIX 网关则在声明式资源文件中通过 display_name 引用它们。
路由模型选择一个直接目标,再由该目标使用自己的服务提供方密钥调用上游模型:
选择策略
请根据面向调用方的别名应如何分发流量来选择策略:
| 目标 | 策略 | 选择行为 |
|---|---|---|
| 保留一个主要目标和有序备用目标。 | failover | 按声明顺序尝试目标。 |
| 在相似目标之间轮转流量,可按比例分配。 | round_robin | 按目标 weight 做平滑加权轮询;权重相等或缺省时按声明顺序轮换。 |
| 将每个会话固定到同一目标。 | consistent_hash | 在加权哈希环上对每个请求的键做哈希,让相同键持续命中同一个健康目标。weight 改变的是哈希键的近似份额,而不是固定的请求百分比。适用于 A/B 测试和灰度发布以及会话亲和 + 池间故障转移。 |
| 优先选择预估价格最低的目标。 | least_cost | 按配置价格或控制面目录定价对符合条件的目标排序。 |
| 优先选择近期观测到的最快目标。 | least_latency | 按近期上游延迟排序。 |
| 优先选择加权并发数最低的目标。 | least_busy | 按 (1 + 当前进行中的请求数) / max(weight, 1) 对符合条件的目标排序,数值最低者优先。 |
failover 是省略 strategy 时的默认值。你也可以使用路由标签按请求缩小符合条件的目标范围。
每种策略都支持以下两个目标级属性:
weight(默认1)在使用它的策略下决定目标的份额:round_robin的轮转份额、consistent_hash的哈希环份额、least_busy的并发折算系数。failover、least_cost和least_latency接受该字段但不使用。priority(默认0)将符合条件的目标划分为层级——数值越大越优先,因此给备用目标配置-1。策略在每层内部对目标排序,只有当没有更高层目标可用时,较低层才会接收流量。请参阅会话绑定与池间故障转移。
AISIX 会先应用路由标签和 allowed_cidrs 过滤,再把剩余目标划分到各优先级层。因此,如果某个健康的高优先级目标因其中任一过滤条件而不适用于当前请求,较低优先级目标仍可为该请求提供服务。
AISIX 会在可重试的上游失败上执行重试和故障转移,例如 5xx 响应、请求超时和传输错误。AISIX 无法读取的 2xx 答复不属于此类:上游已经给出了答复,因此调用方收到 502,该答复不会被重试、不会故障转移,也不计入冷却。大多数上游 4xx 响应会直接返回调用方。启用 retry_on_429 可处理上游限流,配置 fallback_on_statuses 可处理其他服务提供方特定的临时状态码。
AISIX 也会跳过超过自身模型限流的目标。它会将被跳过的目标记录为一次失败的 429 路由尝试,并按策略顺序继续尝试其余目标,不会重试该目标。当所有目标都超过限额时,请求返回 429。
每次分发到目标的尝试(包括同一目标的重试)都会计入该目标自身的限制,而调用方 API Key 和路由模型自身的限制对每个请求只计一次。因自身限制被跳过的目标不消耗额度;因所有目标都超过限额、在任何尝试到达上游之前就返回 429 的请求,在任何一层都不消耗额度。参见请求如何计数。
如果目标自身的 allowed_cidrs 排除了调用方,该目标同样不会成为候选项。此检查在策略选择前执行,因此不会尝试该目标,也不会消耗 max_fallbacks 预算。当所有目标都排除调用方时,请求返回 403。
支持的端点
调用方可以在以下端点上请求多目标模型。每个端点都会按本页所述的策略、重试、故障转移和运行时过滤逐个尝试目标:
/v1/chat/completions、/v1/messages、/v1/messages/count_tokens和/v1/responses。/v1/completions、/v1/embeddings、/v1/rerank、/v1/systemone、/v1/decisions、/v1/images/generations、/v1/images/edits、/v1/audio/transcriptions、/v1/audio/translations、/v1/audio/speech,以及POST /v1/videos提交。
/v1/realtime 以及 Files、Batches 和 Fine-tuning API 要求使用直接模型,会拒绝多目标模型,因为会话或服务提供方签发的 ID 必须始 终留在同一个服务提供方上。
在第二组端点上,每个目标都必须能够处理该端点。端点无法处理的目标会像不可重试的失败一样结束请求,AISIX 不会转而尝试下一个目标;只有可重试的上游失败才会触发故障转移。/v1/rerank、/v1/images/generations、/v1/images/edits 和决策端点返回 400,错误信息会同时写出多目标模型和目标,例如 model `my-group` (target `claude-img`) is not an OpenAI provider。当目标的服务提供方缺少对应能力时,/v1/completions、/v1/embeddings、/v1/images/generations 和 /v1/videos 返回 501。这两种响应都应视为该组的配置错误。/v1/messages/count_tokens 是例外:它会跳过无法处理的目标,继续尝试下一个目标。
这些端点记录多目标请求的方式与 Chat Completions 相同:
- 每次上游尝试都会产生自己的用量事件,包括导致重试或故障转移的失败尝试。失败尝试的 Token 数为零,且不计费。给出响应的那次尝试的事件会写明实际提供服务的目标,计价也按该目标而不是按组进行。
- 部署与回退指标以及访问日志中的路由字段覆盖这些端点。
least_latency和least_busy会从这些端点的流量中学习。对于语音合成和流式转写,least_latency测量的是到上游响应头的时间,而在中继的流结束之前,该目标在least_busy中一直计为忙碌。
通过多目标模型 提交的视频会始终留在接受该任务的服务提供方上:返回的视频 ID 标识了该目标,因此 GET /v1/videos/{video_id} 及其 /content 路由会到达同一个服务提供方,状态响应中回显的是提交时使用的多目标模型名称。上游已经应答之后才失败的提交永远不会重放,无论是在同一目标还是下一个目标上,因为付费任务可能已经创建。
/v1/responses 对话会留在产出其上下文的目标上。AISIX 会在返回的推理 encrypted_content 和响应 id 上标记处理该请求的目标,并把携带它们的后续轮次优先发回该目标,或同一服务提供方密钥上的其他目标,不按策略顺序。参见在多目标模型上保持对话上下文。
配置并测试故障转移模型
以下示例会构建并测试一个双目标模型:优先使用 gpt-4o-primary,遇到符合条件的失败后改用 gpt-4o-secondary。本节同时提供 AISIX Enterprise、AISIX Cloud 和开源 AISIX 网关的操作说明。
准备工作
开始前,请准备以下资源:
- 两个或更多用作路由目标的直接模型。要执行故障测试,主要模型和备用模型必须使用不同的服 务提供方密钥资源,但两个资源可以包含同一个上游凭据。
- 一个可以调用多目标模型的调用方 API Key,或创建该 Key 的权限。
- 用于执行 API 和验证命令的 cURL 和 jq。
- 对于 AISIX Enterprise 或 AISIX Cloud,需要一个已接入网关的环境,以及管理模型和调用方 API Key 的权限。
- 对于开源 AISIX 网关,需要有权访问声明式资源文件和网关进程。
创建模型
请先创建直接目标模型,再创建多目标模型。以下示例通过两种管理路径配置相同的故障转移行为:AISIX 先使用 gpt-4o-primary,在发生可重试失败后改用 gpt-4o-secondary。
AISIX Enterprise 或 AISIX Cloud
设置控制面 URL、Admin Token 和环境 ID:
# AISIX_CP 是 Admin API 基础 URL;应包含 /api,且末尾不要带斜杠。
# 本地 AISIX Enterprise 快速入门使用 http://localhost:8080/api。
export AISIX_CP="YOUR_AISIX_CONTROL_PLANE_API_URL"
export AISIX_TOKEN="YOUR_ADMIN_TOKEN"
export ENV_ID="YOUR_ENVIRONMENT_ID"
导出直接目标模型的 ID:
export PRIMARY_MODEL_ID="YOUR_PRIMARY_MODEL_ID"
export SECONDARY_MODEL_ID="YOUR_SECONDARY_MODEL_ID"
创建一个故障转移模型:它会先从主要目标开始,再在失败时回退到备用目标。
ROUTING_MODEL_ID=$(curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/models" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"kind": "routing",
"display_name": "chat-prod",
"routing": {
"strategy": "failover",
"targets": [
{"model_id": "'"$PRIMARY_MODEL_ID"'"},
{"model_id": "'"$SECONDARY_MODEL_ID"'"}
],
"retries": 1,
"max_fallbacks": 1,
"retry_on_429": true
}
}' | jq -r '.model.id')
使用该配置时,AISIX 会从 gpt-4o-primary 开始。如果该目标发生可重试失败,AISIX 可以先重试一次,然后故障转移一次到 gpt-4o-secondary。
使用获取的 ROUTING_MODEL_ID 授予调用方访问权限,并在以后更新、查看或删除该模型。如果已有调用方 API Key 需要使用 chat-prod,请把此 ID 添加到它的 allowed_models 列表。
为了快速验证,请创建一个只能调用 chat-prod 的调用方 API Key:
KEY_RESPONSE=$(curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/api_keys" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "routing-guide-caller",
"allowed_models": ["'"$ROUTING_MODEL_ID"'"]
}')
export AISIX_API_KEY=$(echo "$KEY_RESPONSE" | jq -r '.plaintext')
API_KEY_ID=$(echo "$KEY_RESPONSE" | jq -r '.api_key.id')
明文调用方密钥只返回一次。上述命令保存了它,供验证请求使用,并保存密钥 ID,供稍后更新允许列表。模型和调用方密钥配置会自动投射到已接入的网关。
开源 AISIX 网关
将两个模型服务提供方 Key、对应的直接模型以及 chat-prod 路由模型添加到完整资源文件。将两个 Key 分开可以只测试其中一个目标,而不影响另一个:
provider_keys:
- display_name: openai-primary
provider: openai
adapter: openai
api_key: ${OPENAI_API_KEY}
- display_name: openai-secondary
provider: openai
adapter: openai
api_key: ${OPENAI_API_KEY}
models:
- display_name: gpt-4o-primary
provider: openai
model_name: gpt-4o
provider_key: openai-primary
- display_name: gpt-4o-secondary
provider: openai
model_name: gpt-4o-mini
provider_key: openai-secondary
- display_name: chat-prod
routing:
strategy: failover
targets:
- model: gpt-4o-primary
- model: gpt-4o-secondary
retries: 1
max_fallbacks: 1
retry_on_429: true
将 chat-prod 添加到调用方 Key 的 allowed_models,然后验证并重新加载组装后的文件。
验证主要目标路由
导出所配置部署的网关 URL:
# 使用网关源地址,不要附加末尾斜杠或端点路径。
# 本地快速入门使用 http://127.0.0.1:3000。
export AISIX_PROXY="YOUR_AISIX_GATEWAY_URL"
对于 AISIX Enterprise 或 AISIX Cloud,请继续使用先前创建的 AISIX_API_KEY。对于开源网关,请导出调用方 API Key:
export AISIX_API_KEY="YOUR_CALLER_API_KEY"
向多目标别名发送请求:
curl -sSi -X POST "$AISIX_PROXY/v1/chat/completions" \
-H "Authorization: Bearer ${AISIX_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"model": "chat-prod",
"messages": [
{"role": "user", "content": "Hello from AISIX routing."}
]
}'
成功请求以 HTTP/1.1 200 OK 开头。响应体仍以 chat-prod 作为面向调用方的模型名称,因此默认情况下,响应不会告诉调用方由哪个目标处理了请求。要查看目标,请复制响应头 x-aisix-request-id 的值,在网关访问日志中找到该请求对应的行,其 served_by_model 字段为 gpt-4o-primary。使用开源快速入门容器时:
docker logs aisix-quickstart 2>&1 | grep "YOUR_REQUEST_ID"
在测试网关上,也可以在启动配置中将 proxy.expose_debug_headers 设为 true 并重启网关,此时响应还会包含 x-aisix-served-by: gpt-4o-primary。该响应头标识实际处理请求的直接模型;缓存命中、单目标模型响应、错误响应以及 /v1/chat/completions 以外的端点不会包含它。
流式请求可以解析多目标别名,但流已经开始后不会再执行故障转移。
模拟主要目标失败
正常请求到达 gpt-4o-primary 后,让该目标变得不可访问,再重复请求。在此测试中,主要直接模型和备用直接模型必须使用不同的服务提供方密钥,这样修改主要端点时不会同时影响故障转移目标。
请为主要目标使用专用的非生产服务提供方密钥。在 AISIX Enterprise 和 AISIX Cloud 中,服务提供方密钥属于组织作 用域,修改其端点会影响所有引用它的模型。
AISIX Enterprise 或 AISIX Cloud
导出仅由主要目标使用的服务提供方密钥 ID,再保存其当前端点:
export PRIMARY_PROVIDER_KEY_ID="YOUR_PRIMARY_PROVIDER_KEY_ID"
PRIMARY_API_BASE=$(curl -sS \
"$AISIX_CP/provider_keys/$PRIMARY_PROVIDER_KEY_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" \
| jq -r '.provider_key.api_base // ""')
将该服务提供方密钥指向保留 的、不可访问的域名:
curl -sS -X PATCH \
"$AISIX_CP/provider_keys/$PRIMARY_PROVIDER_KEY_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"api_base":"https://api.openai.invalid/v1"}'
控制面会把更新投射到允许使用该服务提供方密钥的每个环境。发送测试请求前,请确认目标网关已经应用最新配置。有关控制面修订版本和网关状态检查,请参阅资源投射。
开源 AISIX 网关
在完整 resources.yaml 中,将现有 openai-primary 模型服务提供方 Key 条目替换为以下配置块,只更改其端点。保留其他条目和集合:
provider_keys:
- display_name: openai-primary
provider: openai
adapter: openai
api_key: ${OPENAI_API_KEY}
api_base: https://api.openai.invalid/v1
如果你按照开源快速入门完成部署,请使用现有容器验证并重新加载该文件:
docker exec aisix-quickstart \
/usr/local/bin/aisix validate --resources /etc/aisix/resources.yaml
docker kill --signal=HUP aisix-quickstart
如果你的配置不同,请使用自己的网关容器名称和资源文件路径。如果重新加载失败,网关会继续使用上一个有效配置。发送测试请求前,请确认 GET /status/config 报告重新加载成功。
验证故障转移
通过任一管理路径模拟失败后,重复验证主要目标路由中的请求。成功响应仍以 HTTP/1.1 200 OK 开头,但该请求访问日志行中的 served_by_model 此时为 gpt-4o-secondary;当 proxy.expose_debug_headers 为 true 时,响应还会包含以下响应头:
x-aisix-served-by: gpt-4o-secondary
AISIX 会重试不可访问的主要目标,再将请求转发到备用目标。响应体仍以 chat-prod 作为面向调用方的模型名称。
如果可以访问网关的私有指标与状态监听器,请查看目标状态:
# 开源快速入门使用 http://127.0.0.1:9090
curl -sS "http://127.0.0.1:9090/status/models" \
| jq '.[] | select(
.display_name == "gpt-4o-primary" or
.display_name == "gpt-4o-secondary" or
.display_name == "chat-prod"
)'
两个直接目标都报告 healthy,多目标模型本身报告 not_applicable,因为它的可用性来自直接目标。状态监听器的路由不需要认证,因此必须保持为私有。
不可访问的主要目标仍为 healthy,是因为这些目标模型没有配置 cooldown 块,而冷却需要显式开启。因此故障转移是逐请求发生的:每个请求都会先尝试 gpt-4o-primary——由于本示例设置了 retries: 1,实际会尝试两次——遇到同样的失败后才转到 gpt-4o-secondary。如果希望反复失败的目标在一段时间内退出候选列表,从而让后续请求省下这些尝试,请把主要目标的 cooldown.enabled 设为 true;此后该目标会报告 cooldown,并带上 transport_error 之类的 status_reason,直到冷却结束。
恢复主要目标路由
对于 AISIX Enterprise 或 AISIX Cloud,请恢复保存的端点:
jq -n --arg api_base "$PRIMARY_API_BASE" '{api_base: $api_base}' \
| curl -sS -X PATCH \
"$AISIX_CP/provider_keys/$PRIMARY_PROVIDER_KEY_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @-
确认恢复后的配置已到达网关。对于开源网关,请恢复或移除测试用 api_base 覆盖项,再次验证并重新加载 resources.yaml。
再次发送请求,并确认 served_by_model