Skip to content

Pool Mechanism

Sliverkiss edited this page Sep 14, 2026 · 1 revision

账号池机制

账号池是网关的核心状态机:选号 → 在途租约 → 冷却 / 熔断 → 持久化,外加会话粘性与成本账本。

请求 → 会话粘性路由(命中则直取绑定号)→ 池选号 → Acquire 占在途 → 转发上游
                                            ↑
                            成功:NoteSuccess + NoteModelCost(实测扣费)
                            失败:按错误分类 Cooldown / Disable / 熔断计数

选号(Pick)

三因子加权

健康候选中,每个账号按权重抽签:

weight = credits 比例 × 10 + 闲置补偿 + 成功率 × 3
  • credits 比例 = 该号积分 / 候选集内最大积分(量纲归一,防积分绝对值爆炸);
  • 闲置补偿 = min(距上次使用小时数 × idle_weight_per_hour, idle_weight_max),从未使用给满分(idle_weight_max),饥渴设备优先被选;
  • 成功率 = successCount / (successCount + errTotal),无请求记录给 1.5(中性偏信任)。

Top-5 短名单

先按权重降序取前 5 名,再在候选内按同一权重加权随机抽签——打散热点,避免永远打同一个账号(权重相同的按 uid 稳定排序兜底)。

防惊群

同一账号 minPickGap(100ms)窗口内不重复被选中(除非 Top5 全部刚被用过,此时按 LRU 选最近最少使用的一个),迫使高并发请求发散,而不是全部撞同一高分账号。

成本分层

请求带模型时(PickExcludingForModel),候选按该模型的实测扣费分三层,只保留最优层:

含义 说明
0 已实测免费 限免期 / 夜间免费的号,最强偏好
1 无观测(含过期) 新号的限免状态只能靠实测发现,排在已实测收费之前
2 已实测收费 收费号不会挤掉未知 / 免费号

硬过滤(而非仅排序):收费号无法在免费候选存在时被抽中。

全冷却兜底

无任何 healthy 候选时 → 选「最早到期」的冷却账号重试(排除 disabled 与硬冷却号,见 Error-Handling)。

在途租约(In-Flight Lease)

  • pool.max_in_flight(默认 3):单账号同时处理的在途请求上限;
  • Acquire / Release 用 CAS 原子增减,0 = 不限制;
  • 选号与可变函数都会过滤 inFlightFull 的账号;/statusin_flight 字段透出当前在途数。

冷却(Cooldown)

按错误分类进入不同冷却(对照 Error-Handling 全表),持久化域内的关键状态:

  • cool_kindhard_credit(402,至次日 04:00)/ soft_rate(429 退避);
  • soft_streak:连续软冷却次数(指数退避指数,持久化——重启不回到基数),成功或签到解冻归零;
  • modelCooldowns(6004 模型级):运行态(不持久化),每模型独立计时。

冷却入口同时也是熔断失败信号(双轨独立,见 Error-Handling)。

熔断(Circuit Breaker)

Error-Handling 熔断器段。归并:阈值 3、指数退避 30m→1h→2h→6h、成功清零。

会话粘性(Session Sticky)

  • 同一会话(metadata.conversation_idmetadata.conversationIdmetadata.user_id → 顶层 conversation_id / conversationId,按序提取)尽量绑定同一账号,保证多轮上下文不跳号;
  • 绑定 TTL 30m,访问滚动续期;后台 GC 5m 清理过期绑定;
  • 双相位分配:先尝试命中已有空闲绑定,未命中再全局轮换;
  • 失败自动解绑 → 回落普通轮换,请求级 tried 累计避免反复撞同一坏号;
  • 按模型判定可用性:绑定账号对请求模型被 6004 限额时(healthyForModel),立即解绑并重新分配——模型被限的号不再被持续选中;
  • Redis 启用时绑定镜像到 Redis(7 天 TTL),重启不丢。

限额台账(Rate-Limit Ledger)

/statusrate_limited_models 字段逐账号暴露仍在限额的模型:

"rate_limited_models": [
  { "model": "deepseek-r1", "until": "...", "reset_at": "...", "reason": "..." }
]
  • 每行一个被 6004 限流的模型,多模型同时限流全部展示,随冷却到期自然消失
  • until = 截断后的冷却截止(受 soft_rate_max 封顶);reset_at = 上游「将在 … 重置」的原始墙钟(未截断),与 until 相等时省略;
  • 运维据此看到「账号 A 的模型 X 还在限额中,预计何时恢复」。

实测扣费账本(Model Cost Ledger)

  • 每次成功请求按 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)重启清零。

池的 realm 维度

  • 双账号池共享同一 byUID 结构,Realm() 谓词过滤选号与计数;
  • AvailableUIDsForModel / PickByUIDForModel 按模型 + realm 双重健康判定;
  • /statusrealm_totals/healthzrealm_servable 分别暴露 CN / global 各维可用性(见 API-Reference)。

Clone this wiki locally