跳到主要内容

多目标路由与故障转移

多目标模型使用一个面向调用方的别名承载多个目标模型。AISIX 为每个请求选择目标、重试符合条件的失败,并且可以在不要求应用更改模型名称的情况下执行故障转移。

该别名是面向调用方的模型名称,因此对于有权请求它的每个调用方 API Key,它都会显示在 GET /v1/models 中。将某个 Key 的作用域仅限定到该别名,即可把它作为调用方发现的唯一入口发布;此后可以更改别名的目标,而无需修改应用。

多目标模型包含 routing 块,而不是自身的服务提供方配置。每个目标都是一个已有的直接模型,具有自己的服务提供方密钥和上游模型名称。AISIX Cloud 通过模型 ID 引用这些目标,开源 AISIX 网关则在声明式资源文件中通过 display_name 引用它们。

路由模型选择一个直接目标,再由该目标使用自己的服务提供方密钥调用上游模型:

选择策略

请根据面向调用方的别名应如何分发流量来选择策略:

目标策略选择行为
保留一个主要目标和有序备用目标。failover按声明顺序尝试目标。
在相似目标之间轮转流量。round_robin依次轮换符合条件的目标。
控制每个目标的流量比例。weighted按权重抽样目标。添加 sticky 可实现稳定的 A/B 测试或灰度发布分配。
优先选择预估价格最低的目标。least_cost按配置价格或 AISIX Cloud 目录定价对符合条件的目标排序。
优先选择近期观测到的最快目标。least_latency按近期上游延迟排序。
优先选择进行中请求最少的目标。least_busy按当前负载排序。

failover 是省略 strategy 时的默认值。你也可以使用路由标签按请求缩小符合条件的目标范围。

AISIX 会在可重试的上游失败上执行重试和故障转移,例如 5xx 响应、请求超时和传输错误。大多数上游 4xx 响应会直接返回调用方。启用 retry_on_429 可处理上游限流,配置 fallback_on_statuses 可处理其他服务提供方特定的临时状态码。

AISIX 也会跳过超过自身模型限流的目标。它会将被跳过的目标记录为一次失败的 429 路由尝试,并按策略顺序继续尝试其余目标,不会重试该目标。当所有目标都超过限额时,请求返回 429

如果目标自身的 allowed_cidrs 排除了调用方,该目标同样不会成为候选项。此检查在策略选择前执行,因此不会尝试该目标,也不会消耗 max_fallbacks 预算。当所有目标都排除调用方时,请求返回 403

配置并测试故障转移模型

以下示例会构建并测试一个双目标模型:优先使用 gpt-4o-primary,遇到符合条件的失败后改用 gpt-4o-secondary。本节同时提供 AISIX Cloud 和开源 AISIX 网关的操作说明。

准备工作

开始前,请准备以下资源:

  • 两个或更多用作路由目标的直接模型。要执行故障测试,主要模型和备用模型必须使用不同的服务提供方密钥资源,但两个资源可以包含同一个上游凭据。
  • 一个可以调用多目标模型的调用方 API Key,或创建该 Key 的权限。
  • 用于执行 API 和验证命令的 cURLjq
  • 对于 AISIX Cloud,需要一个已接入网关的环境,以及管理模型和调用方 API Key 的权限。
  • 对于开源 AISIX 网关,需要有权访问声明式资源文件和网关进程。

创建模型

请先创建直接目标模型,再创建多目标模型。以下示例通过两种管理路径配置相同的故障转移行为:AISIX 先使用 gpt-4o-primary,在发生可重试失败后改用 gpt-4o-secondary

AISIX Cloud

设置 AISIX Cloud 组织的控制面 URL、Admin Token 和环境 ID:

# AISIX_CP 是 Admin API 基础 URL;应包含 /api,且末尾不要带斜杠。
# 本地部署快速入门使用 http://localhost:8080/api。
export AISIX_CP="YOUR_AISIX_CLOUD_ADMIN_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 网关

为两个直接目标声明不同的服务提供方密钥,再添加多目标模型。这两个密钥可以使用相同的上游凭据;将它们分开可以只测试其中一个目标,而不影响另一个。目标通过 display_name 引用直接模型:

resources.yaml
_format_version: "1"

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 添加到调用方密钥的 allowed_models,然后验证并重新加载该文件。

验证主要目标路由

导出所配置部署的网关 URL:

# 使用网关源地址,不要附加末尾斜杠或端点路径。
# 本地快速入门使用 http://127.0.0.1:3000。
export AISIX_PROXY="YOUR_AISIX_GATEWAY_URL"

对于 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 开头,并包含 x-aisix-served-by: gpt-4o-primary。响应体仍以 chat-prod 作为面向调用方的模型名称。该响应头标识实际处理请求的直接模型;缓存命中、单目标模型响应、错误响应或部分端点族不会包含它。

流式请求可以解析多目标别名,但流已经开始后不会再执行故障转移。

模拟主要目标失败

正常请求到达 gpt-4o-primary 后,让该目标变得不可访问,再重复请求。在此测试中,主要直接模型和备用直接模型必须使用不同的服务提供方密钥,这样修改主要端点时不会同时影响故障转移目标。

警告

请为主要目标使用专用的非生产服务提供方密钥。在 AISIX Cloud 中,服务提供方密钥属于组织作用域,修改其端点会影响所有引用它的模型。

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 中,只修改主要服务提供方密钥的端点:

resources.yaml(主要服务提供方密钥条目)
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 开头,但此时会包含:

x-aisix-served-by: gpt-4o-secondary

AISIX 会重试不可访问的主要目标,再将请求转发到备用目标。响应体仍以 chat-prod 作为面向调用方的模型名称。

如果可以访问网关的私有指标与状态监听器,请查看目标状态:

# 本地快速入门在 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"
)'

主要目标会报告 cooldown,且 status_reasontransport_error;备用目标仍为 healthy。多目标模型本身报告 not_applicable,因为它的可用性来自直接目标。状态监听器的路由不需要认证,因此必须保持为私有。

恢复主要目标路由

对于 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

主要目标会保持冷却,直到当前冷却周期结束。它恢复为 healthy 后,再次发送请求,并确认 x-aisix-served-by 重新标识 gpt-4o-primary

自定义路由行为

使用以下选项调整 AISIX 如何为特定请求重试、排列目标、分配流量或筛选候选项。每个选项彼此独立,因此只需应用路由策略所需的行为。

调整重试与运行时行为

先前配置的 chat-prod 故障转移模型会重试 gpt-4o-primary 一次,并可故障转移一次到 gpt-4o-secondary。以下字段控制尝试和运行时故障转移行为:

字段适用场景
retries请求应在可重试失败后重复尝试同一目标,并在尝试之间逐步延长等待时间。它会为每个目标设置默认值;目标自己设置的 retries 会覆盖该值。
max_fallbacks请求可以转移到另一个符合条件的目标。省略时,AISIX 可以尝试所有后续目标;设为 0 表示只选择目标,不跨目标故障转移。
retry_on_429上游 429 响应应允许触发另一次尝试。默认禁用。
fallback_on_statuses服务提供方使用其他状态码表示应允许再次尝试的临时情况。
when_all_unavailable控制运行时过滤移除所有目标后的行为;当使用 "try_anyway" 尝试一个目标比返回 503 all_candidates_unavailable 更合适时启用。

所有代理端点都遵循 retries,无论请求是否为流式。对于流式请求,同目标重试和故障转移都发生在 AISIX 仍在建立上游流的阶段,因此对调用方不可见。一旦提交响应字节,后续故障会终止响应。

routing.retries 是此模型每个目标的默认值。目标自己设置的 retries 会优先使用,因此一个组可以混用允许重复尝试的目标和应立即放弃的目标。直接指向服务提供方的模型别名也有自己的重试预算——完整解析顺序和部署级默认值请参阅重试预算

未设置 retries 时,AISIX 会优先转移到下一个符合条件的目标,而不是重复当前目标,并在最后一个目标上回退到部署默认值。如果目标应在故障转移前被重复尝试,请显式设置 retries

选择额外的故障转移状态码

只有当服务提供方使用某个状态表示模型过载、队列饱和或配额耗尽等临时情况时,才应使用 fallback_on_statuses。在多目标模型的 routing 块中设置:

{
"routing": {
"fallback_on_statuses": [408, 409]
}
}

条目必须位于 400599 之间。5xx 响应本来就会故障转移,重复列出不会改变行为。除非已知服务提供方会用认证(401403)或校验(400)状态表示临时失败,否则不要添加这些状态。重试调用方或凭据错误只会把相同的失败请求发送到更多目标。

fallback_on_statuses 只影响当前请求。直接模型的 cooldown.trigger_statuses 决定重复失败是否会让目标退出未来请求的轮转。例如,把 422 加入 fallback_on_statuses 可以把当前请求移到另一个目标,但不会让失败目标进入冷却。

处理流式失败

对于流式 Chat Completions,AISIX 只能在向调用方发送响应字节前重试或故障转移。当模型或路由模型设置 stream_timeout,或回退到自身的 timeout 时,AISIX 会等待第一个上游分片。此时的连接错误、空流或超时可以把请求移到另一个目标。流式传输开始后,超时只会结束当前流。

部署级 upstream.timeout_msupstream.stream_timeout_ms 默认值不会增加这项首分片等待。只使用这些默认值时,AISIX 会在上游响应开始时提交下游响应,之后的首分片停滞会表现为流内超时,而不是故障转移。

按成本、延迟或负载路由

least_costleast_latencyleast_busy 策略会根据运行时信号对每个目标排序。AISIX 会优先尝试排名最前的目标,再依次尝试其余目标。顺序来自信号,而不是目标声明顺序。

  • least_cost 根据每个目标模型每 1,000 个 Token 的输入和输出合计价格排序。没有已知价格的目标排在最后。
  • least_latency 根据近期上游延迟的移动平均值排序;对于流式响应,使用首 Token 时间。还没有样本的目标排在最前,以便每个目标先被探测再参与排序。
  • least_busy 根据当前分派到每个目标的进行中请求数排序。

在路由块中设置策略。以下 AISIX Cloud 示例会创建一个优先选择最便宜健康目标的别名:

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-cheapest",
"routing": {
"strategy": "least_cost",
"targets": [
{"model_id": "'"$PRIMARY_MODEL_ID"'"},
{"model_id": "'"$SECONDARY_MODEL_ID"'"}
]
}
}'

对于开源网关,请使用目标模型名称配置 least_cost

resources.yaml(模型条目)
- display_name: chat-cheapest
routing:
strategy: least_cost
targets:
- model: gpt-4o-primary
- model: gpt-4o-secondary

价格来源取决于网关使用开源资源文件还是连接 AISIX Cloud:

  • 连接 AISIX Cloud 控制面的网关会根据每个目标精确配置的服务提供方和上游 model_name 投射路由价格。模型定价中的组织覆盖价格优先于目录价格;价格变化会重新排序现有路由组,无需修改模型配置。AISIX Cloud 控制面拒绝把通配符直接模型作为路由目标,因此 AISIX Cloud 中的路由组必须指向显示名称和上游模型名称都明确的模型。
  • 开源 AISIX 网关根据每个目标模型的 cost 块在 resources.yaml 中记录的值排序。请参阅模型别名

为 A/B 测试和灰度发布拆分流量

使用 weighted 并设置 sticky: true,可以将固定比例的流量发送到灰度目标,同时让每个调用方稳定命中同一个变体。权重决定拆分比例;粘性分配会按调用方确定性选择目标,因此同一会话不会在多次请求间切换变体。

此示例复用本指南中的两个目标模型作为稳定版本和灰度版本,然后将新别名添加到调用方 API Key 允许列表。allowed_models 是替换列表,因此请包含该 Key 需要保留的所有模型:

CANARY_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-canary",
"routing": {
"strategy": "weighted",
"sticky": true,
"targets": [
{"model_id": "'"$PRIMARY_MODEL_ID"'", "weight": 95},
{"model_id": "'"$SECONDARY_MODEL_ID"'", "weight": 5}
]
}
}' | jq -r '.model.id')

curl -sS -X PATCH "$AISIX_CP/environments/$ENV_ID/api_keys/$API_KEY_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"allowed_models": ["'"$ROUTING_MODEL_ID"'", "'"$CANARY_MODEL_ID"'"]
}'

默认情况下,粘性分配以调用方 API Key 为键,因此每个 Key 会稳定落到同一个目标。如需改为按会话或最终用户分配,请发送 x-aisix-routing-key 请求头:

curl -sS -X POST "$AISIX_PROXY/v1/chat/completions" \
-H "Authorization: Bearer ${AISIX_API_KEY}" \
-H "x-aisix-routing-key: session-1a2b" \
-H "Content-Type: application/json" \
-d '{
"model": "chat-canary",
"messages": [
{"role": "user", "content": "Hello."}
]
}'

如果不设置 stickyweighted 会在每次请求时独立采样流量比例。

在开源资源文件中,使用模型名称配置 95/5 拆分:

resources.yaml(模型条目)
- display_name: chat-canary
routing:
strategy: weighted
sticky: true
targets:
- model: gpt-4o-primary
weight: 95
- model: gpt-4o-secondary
weight: 5

按请求标签路由

为目标添加标签,可以按请求特定的信号路由,例如团队、套餐或环境。给每个目标添加 tags 后,在请求中通过 x-aisix-routing-tags 请求头发送逗号分隔的标签。

以下示例将主要目标标记为高级层,将备用目标标记为默认层,然后将新别名添加到调用方 API Key 允许列表:

TIERED_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-tiered",
"routing": {
"strategy": "failover",
"targets": [
{"model_id": "'"$PRIMARY_MODEL_ID"'", "tags": ["premium"]},
{"model_id": "'"$SECONDARY_MODEL_ID"'", "tags": ["default"]}
]
}
}' | jq -r '.model.id')

curl -sS -X PATCH "$AISIX_CP/environments/$ENV_ID/api_keys/$API_KEY_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"allowed_models": ["'"$ROUTING_MODEL_ID"'", "'"$TIERED_MODEL_ID"'"]
}'

PATCH 请求会替换调用方密钥的允许列表。如果该密钥还应保留对 chat-canary 的访问权限,请把 CANARY_MODEL_ID 也加入列表。

携带 x-aisix-routing-tags: premium 的请求只会由标记为 premium 的目标处理,随后由配置的策略对剩余目标排序。请求未携带路由标签时,如果存在标记为 default 的目标,AISIX 会使用它们;否则所有目标都保持为候选项。如果请求提供了标签但没有匹配项,AISIX 会尝试 default 目标,只有不存在这类目标时才拒绝请求。

curl -sS -X POST "$AISIX_PROXY/v1/chat/completions" \
-H "Authorization: Bearer ${AISIX_API_KEY}" \
-H "x-aisix-routing-tags: premium" \
-H "Content-Type: application/json" \
-d '{
"model": "chat-tiered",
"messages": [
{"role": "user", "content": "Hello."}
]
}'

路由请求头只会从请求头中读取,不会转发给上游服务提供方。

在开源资源文件中,按模型名称为目标分配标签:

resources.yaml(模型条目)
- display_name: chat-tiered
routing:
strategy: failover
targets:
- model: gpt-4o-primary
tags:
- premium
- model: gpt-4o-secondary
tags:
- default

下一步

如果目标选择应取决于请求语义,请继续阅读语义路由。使用代理错误与重试围绕网关和上游失败设计应用重试行为。