Skip to content

请求处理链路

下面以 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. 前置策略检查

访问上游前会依次执行部分或全部检查:

  1. 模型是否允许。
  2. 客户端版本或客户端类型是否符合分组限制。
  3. 内容审核是否阻断。
  4. 用户并发槽位是否可获取。
  5. 等待并发槽位后,再次检查余额、订阅和平台配额。

“等待后二次检查”很重要:用户排队期间,余额或额度可能已经被其他请求消耗。

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 和应用进程的稳定性都关系到计费一致性。

调试一条失败请求

建议按以下顺序排查:

  1. 用响应中的 Request ID 搜索日志。
  2. 确认 API Key 状态、分组和用户计费资格。
  3. 查看分组是否存在支持目标模型的可调度账号。
  4. 查看账号并发、限流和最近上游错误。
  5. 确认代理、DNS、TLS 和上游凭证有效。
  6. 最后再检查协议转换或客户端版本兼容问题。

完整时序

text
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。系统使用多层缓存:

  1. 进程内 L1 缓存。
  2. Redis L2 缓存。
  3. PostgreSQL Repository。

缓存快照包含用户、API Key、分组和部分权益信息。管理员修改关键对象时会失效缓存或递增版本。多实例环境中如果 Redis 失效通知异常,某个实例可能在短 TTL 内继续看到旧快照。

为什么先拿用户槽位再检查余额

如果先检查余额、然后排队,等待期间其他请求可能把余额用完。当前链路在获得用户并发槽位后重新做计费资格检查,以缩小并发超扣窗口。

账号槽位通常在选出具体账号后获取。某个账号满载时,调度器可以等待、重新选择或返回并发错误,取决于会话绑定和调度配置。

调度的过滤与评分

候选账号先做硬过滤:

text
平台/分组 → 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 是防止槽位永久泄漏的最后保障。

典型失败发生在哪一段

响应现象优先检查
401API Key 格式、状态、缓存、IP ACL
403用户/Key 状态、分组权限、客户端限制、风控
404 model not found模型列表、模型映射、账号模型范围
429用户/API Key/账号并发、RPM、费用窗口、上游限流
402 或余额错误订阅、余额、平台配额、缓存一致性
502/503上游、代理、账号池耗尽、协议转换
流中途断开代理超时、上游断流、SSE/WS 状态机
成功但无用量usage 解析、异步 worker、数据库写入、去重

专题阅读

本文档用于帮助你理解、部署和运维 Sub2API。使用前请确认上游服务条款与当地法律要求。