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

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

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

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 在这里不是「系统错误」,而是「没有可用渠道」。


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

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

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

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

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

text 复制代码
令牌实际生效的 group + 请求里的 model

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

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


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

我们那次就是这个问题。

配置是这样的:

text 复制代码
令牌分组:default
渠道分组:vip
渠道 models:my-qwen
请求:my-qwen

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

修法只有两个:

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

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

一个容易踩的隐藏项

分组解析的优先级是:

text 复制代码
订阅套餐 UpgradeGroup > 令牌 Group > 用户分组

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

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


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

这个坑我见过不止一次:

text 复制代码
渠道 models:qwen3-max
请求:my-qwen
model_mapping:my-qwen → qwen3-max

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

原因很简单:

model_mapping 不会凭空创造模型。

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

所以正确的顺序是:

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

三个名字一定要分清:

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

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

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

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

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

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

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


六、三分钟定位表

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

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

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

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

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


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

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

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

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

二、分组不要随手填。

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

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


最后

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

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

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


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

text 复制代码
令牌分组:____
渠道分组:____
渠道 models 里有没有这个请求名:有 / 没有
相关推荐
LCG元9 分钟前
我让AI重写了这个老系统
后端
吴汐10 分钟前
【CRMEB 社区版】二开第一问:这个需求该进 driver,还是进 modules?
后端
新*15 分钟前
Elasticsearch 7.17 的 SQL 到底能查到什么?一次踩坑记录
后端·sql·elasticsearch·查询
卷无止境39 分钟前
SALib敏感性分析的python库学习笔记
后端·python
ServBay41 分钟前
从 Val Town 停服看,代码能下载不代表项目能搬家
后端·sqlite
我不会起名字3222 小时前
seq 空洞排查:Mongo 的 51 只是预留上界,Redis CURR 才是已消耗边界
redis·后端·mongodb·go·即时通讯·im·故障排查
特立独行的猫A2 小时前
少 3 个环节、少 1 次 ABI、少 3500 行代码:cj-tauri 与 Ionic/Capacitor 的鸿蒙桥接机制对比
java·后端·python
学长毕设2 小时前
基于SpringBoot的高校后勤报修管理系统-附源码
java·spring boot·后端
特立独行的猫A2 小时前
cj-tauri vs Rust/Tauri 同款播放器实测:编译 21 秒 vs 11 分钟,磁盘 12 MB vs 1.2 GB
java·后端·python