multi-key-round-robin-client-side · ZH · 2026-10-27

在客户端轮询多把聚合 key:粘性会话与余额检查都在哪一步坏掉

本文探讨在客户端使用多把 LLM API 聚合 key 时常见的粘性会话丢失和余额检查失效问题,分析其发生环节并提供基于 key 轮询策略、会话状态管理与主动健康检查的解决方案。

为什么需要轮询多把 key

当单个 API key 的速率限制或余额成为瓶颈时,开发者常会在客户端维护一个 key 池,通过轮询将请求分散到多把 key 上。这听起来简单,但在实际实现中,粘性会话(sticky session)和余额检查(balance check)这两个环节最容易出错。

粘性会话:在哪一步坏掉

粘性会话是指同一对话或同一用户的多轮请求应该尽可能由同一把 key 处理,以保持上下文一致(尤其对于依赖会话状态的模型)以及避免因切换 key 导致的额外计费或行为差异。

常见破坏点

  1. 轮询策略过于简单:简单的 round-robin 或随机选择会导致同一会话的请求被分配到不同 key。
  2. 失败重试未绑定会话:当某把 key 失败后,重试逻辑随机选择另一把 key,破坏了会话连续性。
  3. 并发请求竞争:同一会话的多个并发请求可能被同时分配到不同 key。
  4. 会话状态未持久化:客户端未将会话与 key 的映射关系存储,导致进程重启或水平扩展后丢失。

解决方案

  • 基于会话 ID 的哈希路由:将会话 ID(如对话 ID、用户 ID)哈希后映射到 key 列表的固定位置,保证同一会话总是命中同一把 key。
  • 显式会话- key 绑定:在首次请求时将会话与 key 的对应关系存入共享存储(如 Redis),后续请求优先使用绑定的 key。
  • 重试时保持粘性:当绑定的 key 失败时,重试应优先在同一 key 上重试(可能瞬态错误),若失败再切换到备用 key,并更新绑定关系。

余额检查:在哪一步坏掉

余额检查的目的是避免将请求发送到余额不足的 key 上,导致调用失败。常见问题在于检查的时机和方式。

常见破坏点

  1. 仅在启动时检查:只在应用启动时查询一次余额,运行中余额变化无法感知。
  2. 检查与使用分离:检查余额后到实际发送请求之间,余额可能已被其他请求消耗。
  3. 被动失败后处理:依赖调用失败(如 402 或余额不足错误)后再标记 key,导致用户体验差且浪费请求。
  4. 未考虑并发消耗:多个并发请求同时检查同一 key 的余额,都认为充足,结果超支。

解决方案

  • 主动周期检查与事件驱动结合:定期(如每分钟)检查各 key 余额,同时在每次调用后根据返回的用量信息更新本地余额估计。
  • 预留机制:在发送请求前,基于预估 token 消耗在本地预留额度,请求完成后释放多余部分,避免并发超支。
  • 失败快速标记:当调用返回余额不足错误时,立即将 key 标记为不可用并触发异步余额刷新。
  • 阈值告警与自动降级:设置余额阈值,低于阈值时自动从可用池中移除,并通知运维。

key 退役与健康管理

除了余额,key 还可能因无效、被封禁、速率限制等原因需要退役。

  • 健康检查:定期发送轻量请求(如列出模型)以验证 key 有效性。
  • 错误分类:区分瞬态错误(如 429、5xx)和永久错误(如 401、403),对永久错误立即退役。
  • 平滑退役:对即将退役的 key,停止分配新会话,但允许现有会话完成。

实现要点总结

  • 使用一致性哈希或显式映射维护会话粘性,确保失败重试和并发控制不破坏绑定。
  • 余额检查需结合主动轮询、本地预留和失败反馈,避免检查与使用脱节。
  • 建立 key 健康状态机,统一处理余额不足、失效和速率限制。
  • 将会话- key 映射和 key 状态存储在共享存储中,支持水平扩展。

通过在上述环节采取针对性措施,可以构建一个稳定高效的多 key 轮询客户端。