账号状态机与恢复语义
Account 是否可用不是一个布尔值。Sub2API 会同时检查持久状态、人工调度开关、到期时间、全局限流、模型限流、过载冷却、临时摘除和实时并发。
理解这些状态后,才能正确修改错误策略、调度器和后台恢复按钮。
调度资格公式
可以把基础资格简化成:
eligible =
status == active
AND schedulable == true
AND expires_at 未到期
AND rate_limit_reset_at <= now
AND overload_until <= now
AND temp_unschedulable_until <= now
AND 支持目标 Group / Model / Protocol
AND 仍有并发或可等待容量后续还可能应用优先级、粘性、账号类型偏好、订阅优先、会话窗口和高级调度评分。后台看到 active 不代表请求一定能选中它。
状态维度
| 维度 | 代表字段/状态 | 持久位置 | 典型写入者 |
|---|---|---|---|
| 生命周期 | active、error、disabled/deleted | PostgreSQL Account | 管理操作、不可恢复错误策略 |
| 人工调度 | schedulable | PostgreSQL Account | 管理员摘流/恢复 |
| 到期 | expires_at | PostgreSQL Account | 账号配置、到期任务 |
| 全局限流 | rate_limited_at、rate_limit_reset_at | PostgreSQL + 调度快照 | 上游 429 处理 |
| 过载 | overload_until | PostgreSQL + 调度快照 | 529/过载策略 |
| 临时摘除 | temp_unschedulable_until/reason | PostgreSQL + Redis 缓存 | 401/403、刷新失败、自定义规则 |
| 模型限流 | extra.model_rate_limits | PostgreSQL JSONB/缓存 | 模型级 429 |
| 配额窗口 | extra 中平台窗口状态 | PostgreSQL/缓存 | 上游响应头、quota watcher |
| 实时负载 | 并发 ZSET、队列和 session | Redis | 每次请求获取/释放 |
这些状态的生命周期不同。人工摘流没有自动到期时间;临时摘除和过载通常会在时间到达后重新进入候选;error 需要修复根因并明确恢复。
典型状态转换
管理员关闭 schedulable
┌─────────────────────────────────────────┐
│ ▼
active + schedulable ──上游 429────▶ rate limited / model limited
│ │
│ 上游 529 └── reset 到期或清理 ──┐
├──────────────▶ overloaded │
│ │ │
│ 401/403/刷新失败 └── cooldown 到期 ─────┤
├──────────────▶ temp unschedulable │
│ │ │
│ 不可恢复错误 └── 修复并恢复 ─────────┤
└──────────────▶ status=error │
▼
active + schedulable同一个账号可能同时存在多个阻断原因。只清掉其中一个,仍可能不可用。
错误怎样决定状态
上游错误处理不只看 HTTP status,还会结合平台、账号类型、响应 body/header 和配置规则:
- 429 通常设置全局或模型级 reset 时间,并触发 failover。
- 529/部分 5xx 可以进入 overload cooldown。
- OAuth 401 可能触发刷新、计数或临时摘除。
- OpenAI 403 有独立计数与临时摘除逻辑。
- Antigravity/Gemini 可使用自定义 error policy。
- 明确不可恢复的凭证/配置错误可以把账号置为
error。
新增错误规则时,必须同时回答:是否切换账号、状态保存多久、作用于账号还是模型、是否自动恢复、是否写 Ops 错误以及客户端最终得到哪个错误。
后台动作不是同义词
Test Account
账号测试会向上游发送真实测试请求。只有测试成功后,才调用 RecoverAccountAfterSuccessfulTest:
status=error时清除 error。- 存在可恢复运行状态时清除 rate limit、overload、temp unschedulable、模型限流和 Antigravity quota scopes。
- 清理 OpenAI 403 计数与相关通知状态。
它不会自动把人工设置的 schedulable=false 改回 true,也不能证明所有模型、协议和生产 Proxy 都正常。
Recover State
统一恢复接口与成功测试使用同一恢复核心,但额外要求 OAuth token cache 失效:
- 清除持久 error。
- 清除全局/模型限流、overload、temp unschedulable。
- 清理相关 Redis 临时标记和计数。
- OAuth 账号重新从数据库读取或刷新 token。
它适合“根因已经修好,但多个旧状态叠加”的场景。它不会生成新的上游额度,也不会修复错误的 Key、Base URL、Proxy 或模型权限。
Clear Error
只把 Account status 恢复为 active 并清空 error_message,同时同步调度快照。OAuth 账号还会失效 token cache。
它不会清除人工摘流;也不应被当成忽略 429、过载和临时摘除的快捷方式。
Clear Rate Limit
当前实现的范围比按钮名称更广:
- 清除
rate_limited_at和rate_limit_reset_at。 - 清除
overload_until。 - 清除 Antigravity quota scopes 和模型级限流。
- 一并清除 temp unschedulable 及其 Redis 缓存。
- 重置 OpenAI 403 counter。
因此这是一个较强的运行时恢复动作。上游真实窗口未恢复时,清理后会立即再次失败并制造请求风暴。
Clear Temp Unschedulable
清除临时摘除字段与 Redis 标记,同时清除模型级限流。它不会清理 status=error、人工 schedulable=false 或全局 rate limit。
Refresh / Re-Auth
Refresh 使用现有 refresh token 换取新 token,并持久化凭证;普通 API Key 不适用。完整 Re-Auth 是重新走用户授权后,用专用接口原子更新 OAuth 凭证和部分 extra,再清错并失效 token cache。
修改 OAuth 逻辑时必须保留非 token 配置,不能用一次全量 JSON 覆盖丢掉 quota、privacy、RPM 等 Account extra。
恢复顺序
推荐流程是:
先摘流
→ 保存首次错误证据
→ 修复 Key / OAuth / Base URL / Proxy / 模型权限
→ Refresh 或 Re-Auth(如需要)
→ Test Account
→ 必要时 Recover State
→ 检查所有阻断维度
→ 恢复 schedulable
→ 测试 Group 小流量验证如果先清状态再修根因,自动错误策略会立刻把账号写回原状态,反而丢失首个错误现场。
调度快照的一致性
状态写入 PostgreSQL 后,Repository 会写 scheduler outbox 并同步当前快照。多实例还依赖 outbox 消费、Redis 快照和周期重建传播变化。
修改状态写路径时不能只更新 Account 表。至少要检查:
- 是否写 outbox。
- 是否更新或失效 scheduler snapshot。
- Redis 临时 key 是否清理。
- 模型级与账号级状态是否一致。
- Ops realtime 是否显示新状态。
否则会出现“数据库已恢复,但网关仍不选它”或“页面显示正常,旧副本仍在使用”的问题。
错误码与状态不要混为一谈
- 客户端请求的模型不存在或分组根本不支持时,通常应返回模型相关 404。
- 目标模型存在,但当前没有可调度账号时,属于临时容量问题,通常是 503。
- 客户端 Sub2API Key 无效是入口 401,不应污染上游 Account。
- 上游 Account 401/403 才进入账号凭证错误策略。
状态机的目的不是把所有失败都变成“换一个账号”。客户端参数错误不应消耗整个账号池,平台故障也不应被伪装成永久模型不存在。
修改状态机时的测试矩阵
至少覆盖:
- 单一阻断状态的过滤与恢复。
- error + rate limit 等多个状态叠加。
- cooldown 已到期与未到期边界。
- 全局与模型级限流并存。
- 清理后 outbox、快照和 Redis 一致。
- 多实例读取旧快照时的传播。
- 流式响应已经开始后不能随意 failover。
- 人工
schedulable=false不被自动恢复覆盖。 - OAuth token cache 在 Re-Auth/Recover 后失效。
- 客户端错误不会错误标记 Account。
运维操作版流程见上游账号池运维和401、403 与 OAuth 失效。