-
Notifications
You must be signed in to change notification settings - Fork 213
Pool Mechanism
Sliverkiss edited this page Sep 14, 2026
·
1 revision
账号池是网关的核心状态机:选号 → 在途租约 → 冷却 / 熔断 → 持久化,外加会话粘性与成本账本。
请求 → 会话粘性路由(命中则直取绑定号)→ 池选号 → Acquire 占在途 → 转发上游
↑
成功:NoteSuccess + NoteModelCost(实测扣费)
失败:按错误分类 Cooldown / Disable / 熔断计数
健康候选中,每个账号按权重抽签:
weight = credits 比例 × 10 + 闲置补偿 + 成功率 × 3
- credits 比例 = 该号积分 / 候选集内最大积分(量纲归一,防积分绝对值爆炸);
-
闲置补偿 =
min(距上次使用小时数 × idle_weight_per_hour, idle_weight_max),从未使用给满分(idle_weight_max),饥渴设备优先被选; -
成功率 =
successCount / (successCount + errTotal),无请求记录给 1.5(中性偏信任)。
先按权重降序取前 5 名,再在候选内按同一权重加权随机抽签——打散热点,避免永远打同一个账号(权重相同的按 uid 稳定排序兜底)。
同一账号 minPickGap(100ms)窗口内不重复被选中(除非 Top5 全部刚被用过,此时按 LRU 选最近最少使用的一个),迫使高并发请求发散,而不是全部撞同一高分账号。
请求带模型时(PickExcludingForModel),候选按该模型的实测扣费分三层,只保留最优层:
| 层 | 含义 | 说明 |
|---|---|---|
| 0 | 已实测免费 | 限免期 / 夜间免费的号,最强偏好 |
| 1 | 无观测(含过期) | 新号的限免状态只能靠实测发现,排在已实测收费之前 |
| 2 | 已实测收费 | 收费号不会挤掉未知 / 免费号 |
硬过滤(而非仅排序):收费号无法在免费候选存在时被抽中。
无任何 healthy 候选时 → 选「最早到期」的冷却账号重试(排除 disabled 与硬冷却号,见 Error-Handling)。
-
pool.max_in_flight(默认 3):单账号同时处理的在途请求上限; -
Acquire/Release用 CAS 原子增减,0= 不限制; - 选号与可变函数都会过滤
inFlightFull的账号;/status的in_flight字段透出当前在途数。
按错误分类进入不同冷却(对照 Error-Handling 全表),持久化域内的关键状态:
-
cool_kind:hard_credit(402,至次日 04:00)/soft_rate(429 退避); -
soft_streak:连续软冷却次数(指数退避指数,持久化——重启不回到基数),成功或签到解冻归零; -
modelCooldowns(6004 模型级):运行态(不持久化),每模型独立计时。
冷却入口同时也是熔断失败信号(双轨独立,见 Error-Handling)。
见 Error-Handling 熔断器段。归并:阈值 3、指数退避 30m→1h→2h→6h、成功清零。
- 同一会话(
metadata.conversation_id→metadata.conversationId→metadata.user_id→ 顶层conversation_id/conversationId,按序提取)尽量绑定同一账号,保证多轮上下文不跳号; - 绑定 TTL 30m,访问滚动续期;后台 GC 5m 清理过期绑定;
- 双相位分配:先尝试命中已有空闲绑定,未命中再全局轮换;
- 失败自动解绑 → 回落普通轮换,请求级
tried累计避免反复撞同一坏号; -
按模型判定可用性:绑定账号对请求模型被 6004 限额时(
healthyForModel),立即解绑并重新分配——模型被限的号不再被持续选中; - Redis 启用时绑定镜像到 Redis(7 天 TTL),重启不丢。
/status 的 rate_limited_models 字段逐账号暴露仍在限额的模型:
"rate_limited_models": [
{ "model": "deepseek-r1", "until": "...", "reset_at": "...", "reason": "..." }
]- 每行一个被 6004 限流的模型,多模型同时限流全部展示,随冷却到期自然消失;
-
until= 截断后的冷却截止(受soft_rate_max封顶);reset_at= 上游「将在 … 重置」的原始墙钟(未截断),与until相等时省略; - 运维据此看到「账号 A 的模型 X 还在限额中,预计何时恢复」。
- 每次成功请求按
usage.credit / tokens × 1000折算每千 token 成本,写入(账号, 模型)账本; - EMA 平滑(α=0.3,约 5 次观测收敛),单价低 / 免费优先;
-
6h 有效期(
modelCostTTL):既覆盖「夜间免费」时段性优惠单次会话,又避免昨天的价格决定今天的选号;到期视为无观测; - 仅内存态:重启重新学习(成本随上游活动变化,持久化旧值反而是脏数据)。
| 事件 | 效果 |
|---|---|
| 成功请求 |
successCount++,清 fails / retryCount / breakerUntil / softStreak / sessionDeadFails;不碰 modelCooldowns
|
| 任意错误 |
errTotal++,熔断失败计数 +1 |
| 连续 3 次 12153 | 禁用(12153 session dead),需重登 + 手工复活 |
| 签到成功且余额 > 0 | 解冻冷却(只清 until / reason / softStreak / modelCooldowns,不清熔断——熔断证明 chat 通道问题,签到只证明余额恢复) |
| 手工复活 / 重启 | disabled 清除 / 运行态清零 |
- 落盘文件:
state.json({accounts: {uid: {...}}}),每 5s 脏标记原子写入 + 退出前Flush; - 启动择新恢复:本地文件 vs Redis 快照(
saved_at)取较新者; - 运行态(in-flight、熔断、modelCooldowns、modelCost、sessionDeadFails)重启清零。
- 双账号池共享同一
byUID结构,Realm()谓词过滤选号与计数; -
AvailableUIDsForModel/PickByUIDForModel按模型 + realm 双重健康判定; -
/status的realm_totals、/healthz的realm_servable分别暴露 CN / global 各维可用性(见 API-Reference)。