排查一个 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。所以从网关角度看,这个模型「不存在」。
修法只有两个:
- 渠道分组里加上
default(多个分组用逗号分隔) - 令牌分组改成
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 |
令牌分组是 auto | auto 配置 | 检查是否启用 |
| 刚改过订阅套餐 | 分组 | 看日志实际 group |
| 换默认模型能通 | 单渠道 | 只查这一个渠道 |
七、最小验证:一次只改一个地方
我后来给自己定了个规矩:排查这种问题,一次只动一个变量。
1. 用默认模型发一条请求,确认网关整体正常
2. 换成目标模型,复现 503
3. 只加分组,再请求一次
4. 还不行,再加 models
5. 通了之后,把这次改动写进配置备注
理由很实际:同时改五个地方,就算通了,你也不知道是哪一步修好的。下次换个环境,还是不会。
八、复盘:为什么这种问题会反复出现
那次之后我加了两条习惯:
一、新建渠道时,先写请求名,再写上游名。
顺序反了,很容易出现「mapping 有、models 没有」的状态,而且页面上看起来一切正常。
二、分组不要随手填。
令牌分组和渠道分组是两个独立的下拉框,但它们在能力表里是同一个维度。填之前先想清楚「这个令牌给谁用」,再决定分组。
还有一条:看到 503 先看 message,不要先看状态码。 这段响应里真正有价值的信息全在 message 里。
最后
「无可用渠道」本质是一个查找失败,不是调用失败。请求连上游都没到,所以排查范围其实很小。
三处按顺序过一遍:分组对上没有、models 里有没有、auto 配没配。
你们线上有没有遇到过类似的「配置看起来都对,但就是不生效」的问题?我最近在整理一份网关上线前的配置检查清单,想看看大家还会检查哪些项。
把这三行填一下发在评论区,我直接判是第几处:
令牌分组:____
渠道分组:____
渠道 models 里有没有这个请求名:有 / 没有