502 还是 503?「无可用渠道」其实只有三个原因

0 阅读6分钟

排查一个 503 花了两小时,最后是分组没配对上

线上突然开始报错,客户端拿到的是一段很熟悉的 JSON:

{
  "error": {
    "message": "模型 my-qwen 无可用渠道(distributor)",
    "type": "openstarry-error",
    "code": "model_not_found"
  }
}

HTTP 状态是 503。

第一反应是上游挂了。打开供应商控制台,服务正常;拿上游的 key 直接请求,也是通的。

问题在我们自己这边。

这篇文章把那次排查完整记一遍,包括我一开始判断错的地方。结论放在前面:「无可用渠道」这四个字,说明请求根本没出网关,剩下只有三个可能。


一、先搞清楚是谁在报错

这里有个很容易误判的点:错误码是 model_not_found,但 HTTP 是 503。

如果是客户端路径写错,一般返回 404,而且响应体多半是 HTML;如果是上游不认模型,会返回供应商自己的错误结构,字段不固定。

而这段响应里有两个特征:

  • type 是网关自己的错误类型
  • message 里带 distributor

distributor 是网关里负责「选渠道」的中间件。它拿不到渠道,就直接把请求拦下来了,请求没有发往上游。

所以第一个结论:不要去查供应商,也不要查模型 ID 拼写。查网关自己的配置。

代码里这一层很短:选渠道时如果返回的是 nil, nil,distributor 就把它包装成 503 + model_not_found。nil, nil 在这里不是「系统错误」,而是「没有可用渠道」。


二、网关是怎么「找渠道」的

要理解这个报错,得先知道网关查的是什么表。

它不是直接遍历渠道列表,而是查一张「能力表」。能力记录是这么生成的:

渠道的 models 列表 × 渠道的 group 列表
  → 每个组合生成一条能力记录

请求进来时,网关拿两个东西去查:

令牌实际生效的 group + 请求里的 model

查到了就选中渠道,查不到就是「无可用渠道」。

所以问题只可能出在这两个维度上:分组,或者模型名。再加上一个分组的特殊情况 auto,一共三个位置。


三、第一处:令牌分组和渠道分组没对上

我们那次就是这个问题。

配置是这样的:

令牌分组:default
渠道分组:vip
渠道 models:my-qwen
请求:my-qwen

能力表里只有 vip + my-qwen,没有 default + my-qwen。所以从网关角度看,这个模型「不存在」。

修法只有两个:

  1. 渠道分组里加上 default(多个分组用逗号分隔)
  2. 令牌分组改成 vip,并确认用户有权限

我当时的第一反应是删掉令牌重建,完全没用——令牌只是分组和额度的载体,分组不匹配不是令牌坏了。

一个容易踩的隐藏项

分组解析的优先级是:

订阅套餐 UpgradeGroup > 令牌 Group > 用户分组

也就是说,如果令牌绑了订阅套餐,页面上显示的分组可能不是实际生效的那个。

排查时不要只看令牌页面,去看日志里最终使用的 group。这一步能省掉很多自我怀疑。


四、第二处:渠道 models 里没有这个请求名

这个坑我见过不止一次:

渠道 models:qwen3-max
请求:my-qwen
model_mapping:my-qwen → qwen3-max

看起来 mapping 配得完全正确,但请求还是 503。

原因很简单:

model_mapping 不会凭空创造模型。

能力记录来自「渠道 models × 分组」,不是来自 mapping。渠道 models 里没有 my-qwen,选渠道阶段就已经失败了,后面那段映射代码根本没有机会执行。

所以正确的顺序是:

先让渠道 models 包含请求名
  → 网关能选到渠道
  → 才轮得到 mapping 把请求名换成上游模型名

三个名字一定要分清:

名字出现在哪作用
请求名客户端 model网关用它选渠道
渠道模型名渠道 models生成能力记录
上游模型名mapping 的 value真正发给供应商

五、第三处:auto 分组不是万能分组

有同学把令牌分组设成 auto,以为它会自动匹配所有渠道。不是。

auto 的逻辑是:遍历系统里配置过的 auto 分组列表。

  • 列表为空:直接报 auto groups is not enabled
  • 列表不为空:按顺序逐个分组找渠道

所以 auto 成立的前提是:系统里配了 auto 分组,并且这些分组下确实有目标模型的渠道。

如果没配,auto 反而比写死分组更容易出问题,因为它把「没有渠道」这个事实藏起来了。


六、三分钟定位表

现象位置动作
message 含「无可用渠道」网关不要改模型名
渠道分组不含令牌分组分组加分组或改令牌
渠道 models 没有请求名模型列表加进 models
令牌分组是 autoauto 配置检查是否启用
刚改过订阅套餐分组看日志实际 group
换默认模型能通单渠道只查这一个渠道

七、最小验证:一次只改一个地方

我后来给自己定了个规矩:排查这种问题,一次只动一个变量。

1. 用默认模型发一条请求,确认网关整体正常
2. 换成目标模型,复现 503
3. 只加分组,再请求一次
4. 还不行,再加 models
5. 通了之后,把这次改动写进配置备注

理由很实际:同时改五个地方,就算通了,你也不知道是哪一步修好的。下次换个环境,还是不会。


八、复盘:为什么这种问题会反复出现

那次之后我加了两条习惯:

一、新建渠道时,先写请求名,再写上游名。

顺序反了,很容易出现「mapping 有、models 没有」的状态,而且页面上看起来一切正常。

二、分组不要随手填。

令牌分组和渠道分组是两个独立的下拉框,但它们在能力表里是同一个维度。填之前先想清楚「这个令牌给谁用」,再决定分组。

还有一条:看到 503 先看 message,不要先看状态码。 这段响应里真正有价值的信息全在 message 里。


最后

「无可用渠道」本质是一个查找失败,不是调用失败。请求连上游都没到,所以排查范围其实很小。

三处按顺序过一遍:分组对上没有、models 里有没有、auto 配没配。

你们线上有没有遇到过类似的「配置看起来都对,但就是不生效」的问题?我最近在整理一份网关上线前的配置检查清单,想看看大家还会检查哪些项。


把这三行填一下发在评论区,我直接判是第几处:

令牌分组:____
渠道分组:____
渠道 models 里有没有这个请求名:有 / 没有