请求处理链路
下面以 POST /v1/messages 为例说明一次请求如何完成。
1. 接收与鉴权
网关先限制请求体大小、生成请求 ID、记录标准化端点,然后校验 API Key。鉴权结果会把用户、API Key、分组和订阅快照放入请求上下文。
如果 API Key 未绑定分组,并且系统要求必须分组,请求会在访问上游前失败。
2. 解析协议
Handler 读取请求体并提取:
- 模型名。
- 是否流式响应。
- Messages、Responses 或 Chat Completions 的输入结构。
- Thinking、工具调用、图片和客户端 metadata。
- Claude Code、Codex 或其他客户端特征。
分组平台决定请求走原生转发还是协议桥接。例如,/v1/messages 绑定 OpenAI 分组时,可以转换为 OpenAI Responses 请求。
3. 前置策略检查
访问上游前会依次执行部分或全部检查:
- 模型是否允许。
- 客户端版本或客户端类型是否符合分组限制。
- 内容审核是否阻断。
- 用户并发槽位是否可获取。
- 等待并发槽位后,再次检查余额、订阅和平台配额。
“等待后二次检查”很重要:用户排队期间,余额或额度可能已经被其他请求消耗。
4. 生成会话标识
系统根据请求 metadata、客户端 IP、User-Agent 和 API Key 等信息生成会话 hash,并查询 Redis 中已有的账号绑定。
如果绑定账号仍然可用,调度器优先复用;否则进入普通候选选择。
5. 账号调度
调度器会排除:
- 禁用、过期或不可调度账号。
- 不属于当前分组的账号。
- 不支持请求模型的账号。
- 正在限流、过载或临时暂停的账号。
- 已经在本次故障切换中失败的账号。
候选账号再根据优先级、并发负载、会话绑定、账号能力和调度策略评分。选中后获取账号并发槽位。
6. 上游请求
平台 Service 会完成:
- 模型映射。
- 请求头过滤和构建。
- OAuth token 获取或刷新。
- Messages、Responses、Chat Completions 或 Gemini 格式转换。
- 代理、TLS 指纹和连接池选择。
- 发起 HTTP、SSE 或 WebSocket 请求。
7. 流式响应与故障切换
在响应内容尚未发送给客户端时,部分上游错误可以触发账号切换。如果 SSE 已经开始输出,通常不能透明切换,因为客户端已经收到前一个上游的事件序列。
系统会针对不同下游协议生成相应的错误 JSON 或流式错误事件,避免把不兼容的上游错误格式直接返回给客户端。
8. 用量与结算
请求结束后,系统解析上游 usage,计算输入、输出、缓存、图片或视频费用,并完成:
- 用户余额或订阅额度更新。
- API Key 与平台配额更新。
- 用量日志写入。
- 账号成本和渠道统计。
- 运维指标与错误归因。
部分记录通过后台 worker 异步写入,因此 PostgreSQL、Redis 和应用进程的稳定性都关系到计费一致性。
调试一条失败请求
建议按以下顺序排查:
- 用响应中的 Request ID 搜索日志。
- 确认 API Key 状态、分组和用户计费资格。
- 查看分组是否存在支持目标模型的可调度账号。
- 查看账号并发、限流和最近上游错误。
- 确认代理、DNS、TLS 和上游凭证有效。
- 最后再检查协议转换或客户端版本兼容问题。
完整时序
Client
│ 1. HTTP / SSE / WS request
▼
Global middleware
│ body limit / request id / CORS / security headers
▼
API Key middleware
│ L1 cache → Redis → PostgreSQL
│ user / key / group / subscription snapshot
▼
Protocol handler
│ parse model, stream, tools, metadata
│ content moderation / client restriction
▼
User concurrency
│ acquire or wait with keepalive
▼
Billing preflight
│ subscription → balance → API key quota → platform quota
▼
Session and scheduler
│ sticky lookup → candidates → filters → score → account slot
▼
Platform service
│ token refresh / request transform / proxy / TLS / connection pool
▼
Upstream AI service
│ response headers → events or body
▼
Response bridge
│ validate stream / translate events / filter headers
▼
Usage worker
│ parse usage → calculate price → deduplicate → persist and deduct
▼
Client receives completion路由如何决定 Handler
同一个 URL 不一定永远使用同一个实现。以 /v1/messages 为例:
| 分组平台 | Handler 行为 |
|---|---|
| Anthropic | 走通用 Gateway,选择 Anthropic/Bedrock 等账号 |
| Gemini/Antigravity | 走 Gateway,并按平台转换请求 |
| OpenAI | 走 OpenAI Gateway,把 Messages 转成 Responses |
| Grok | 走 OpenAI 兼容 Gateway,但能力受平台限制 |
因此排查问题时,URL 只是第一维,还要知道 API Key 绑定分组的平台。
API Key 鉴权的读取顺序
鉴权是高频路径,通常不会每次直接查 PostgreSQL。系统使用多层缓存:
- 进程内 L1 缓存。
- Redis L2 缓存。
- PostgreSQL Repository。
缓存快照包含用户、API Key、分组和部分权益信息。管理员修改关键对象时会失效缓存或递增版本。多实例环境中如果 Redis 失效通知异常,某个实例可能在短 TTL 内继续看到旧快照。
为什么先拿用户槽位再检查余额
如果先检查余额、然后排队,等待期间其他请求可能把余额用完。当前链路在获得用户并发槽位后重新做计费资格检查,以缩小并发超扣窗口。
账号槽位通常在选出具体账号后获取。某个账号满载时,调度器可以等待、重新选择或返回并发错误,取决于会话绑定和调度配置。
调度的过滤与评分
候选账号先做硬过滤:
平台/分组 → active/schedulable → 未过期 → 支持模型
→ 不在失败排除集 → 未被临时限流 → 符合账号类型/隐私要求再做软评分或排序,可能考虑:
- 账号优先级。
- 当前并发负载与等待队列。
- 最近错误率和首 Token 延迟。
- 配额余量与重置时间。
- sticky session 或 previous response 命中。
- 最近使用时间和 fallback selection mode。
硬过滤失败意味着账号不能用;软评分较低只是这次没有优先选中。
故障切换何时发生
通常只有在尚未向客户端写出有效响应时,才可以安全切换账号。可切换错误包括部分网络失败、上游过载、限流、token 失效和可配置状态码。
以下情况会限制切换:
- 已经输出 SSE/WS 事件。
- 错误被判断为客户端请求错误。
- 已达到最大账号切换次数。
- 粘性会话策略要求先重试绑定账号。
- 所有候选账号都已进入失败排除集。
failover_on_400 等配置会显著改变行为。错误地把客户端 400 当成账号故障,可能造成同一错误请求依次击穿整个账号池。
流式与非流式的差异
| 阶段 | 非流式 | 流式 |
|---|---|---|
| 响应体 | 完整读取,有最大字节限制 | 按事件/行持续读取 |
| 超时 | 主要受请求和响应头超时 | 还受数据间隔超时和 keepalive 影响 |
| 协议转换 | 完整 JSON 转换 | 增量事件状态机转换 |
| failover | 写出前相对容易 | 首个有效事件后通常不可切换 |
| usage | 常在最终 JSON | 常在结束事件或累计状态中 |
| 客户端断开 | 取消上游 context | 还要释放长连接与并发槽位 |
用量记录为什么异步
如果每次请求都等待用量日志、余额、订阅、统计和账号状态全部落库,客户端尾延迟会明显增加。项目使用有界 worker pool 异步执行 RecordUsage:
- 默认有固定队列上限,避免无界 goroutine。
- worker 可以自动扩缩。
- 默认队列满时同步执行,优先保证计费任务不被静默丢弃。
- 部分必须记录的 OpenAI 路径即使普通提交失败也会同步兜底。
这减少了正常请求延迟,但要求运维监控 waiting、dropped、sync fallback 和数据库写入失败。
请求结束时要释放什么
一次请求可能持有:
- 用户并发槽位。
- API Key 或账号并发槽位。
- 用户消息串行队列锁。
- 上游 HTTP/WS 连接或连接池引用。
- 图片生成进程内并发槽位。
Handler 会通过 defer 和 context cancellation 释放资源。反向代理强制杀连接、进程被 SIGKILL 或 Redis 不可用时,TTL 是防止槽位永久泄漏的最后保障。
典型失败发生在哪一段
| 响应现象 | 优先检查 |
|---|---|
| 401 | API Key 格式、状态、缓存、IP ACL |
| 403 | 用户/Key 状态、分组权限、客户端限制、风控 |
| 404 model not found | 模型列表、模型映射、账号模型范围 |
| 429 | 用户/API Key/账号并发、RPM、费用窗口、上游限流 |
| 402 或余额错误 | 订阅、余额、平台配额、缓存一致性 |
| 502/503 | 上游、代理、账号池耗尽、协议转换 |
| 流中途断开 | 代理超时、上游断流、SSE/WS 状态机 |
| 成功但无用量 | usage 解析、异步 worker、数据库写入、去重 |