Repository navigation
Conversation
Mag1cFall
reviewed
Oct 31, 2026
Mag1cFall
left a comment
Owner
There was a problem hiding this comment.
CPA是按客户端自带的会话id或者对话开头内容的哈希来区分会话,并不是API key,这里逻辑有点诡谲
| return fmt.Errorf("ROUTING_STRATEGY 必须是 round-robin、fill-first 或 key-affinity") | ||
| } | ||
| if c.KeyAffinityTTL <= 0 { | ||
| return fmt.Errorf("KEY_AFFINITY_TTL 必须是正数时长") |
Owner
|
说实话逻辑并不是很直观,且从根本上由于缓存是跨账号的,这个PR很可能并不需要;我暂时不会合入,除非官网的策略变动 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
关联 Issue: #96
动机
现有两种策略都解决不了"多会话并发 + 上游缓存命中"的组合需求:
round-robin按请求轮转,同一客户端会话的连续请求散落到不同账户,上游 Playground 的隐式前缀缓存(cachedContentTokenCount)几乎打不中;fill-first把所有流量压到第一个可用账户,单账户 RPM 更早触顶,且日配额集中烧在一个号上。key-affinity用请求命中的 API key 作为会话标记,并在有效期内(KEY_AFFINITY_TTL,默认1h/ 60 分钟)记忆该 key 上一次实际命中的账户:行为
ROUTING_STRATEGY=key-affinity启用,默认仍为round-robin。KEY_AFFINITY_TTL=1h配置同一 API key 保持使用上一次命中账户的有效时长(默认60m/1h,要求正数时长,设置页修改后立即热生效)。StickySeed在stickyBySeed表中有未过期的绑定记录,将该账户排在候选首位,并在成功获取租约时刷新updatedAt;KEY_AFFINITY_TTL未活跃、或原绑定账户当前满载/冷却,按round-robin顺序从当前可用WarmReady/WarmAvailable账户(热池全满时按序唤醒StandbyReady)选取下一个账户,并将stickyBySeed[seed]更新为本次实际命中的账户。PROXY_API_KEY或请求未命中 key(StickySeed为空)时,自动按round-robin轮询分配。round-robin/fill-first的账户选择逻辑完全不变;等待队列粒度变化见下方"评审确认的行为变化"。generateWithRetry)生效(其余请求类型的亲和接线留作后续 PR)。实现
internal/config/config.go:Validate接受key-affinity;新增KeyAffinityTTL(环境变量KEY_AFFINITY_TTL,默认60m)。internal/api/middleware.go:认证中间件把命中 key 的 SHA-256 写入请求上下文,导出APIKeyAffinitySeed(ctx)。internal/aistudio/accounts.go+channel.go:AccountSelection新增StickySeed;AccountPool新增stickyBySeed映射与SetKeyAffinityTTL;selectionIndicesLocked先按round-robin旋转候选,再把有效期内绑定的粘性账户提到首位;tryAcquireLocked在成功获取租约时记录/更新stickyBySeed并惰性清理过期项。internal/app/runtime.go+admin.go+internal/api/admin.go:启动与管理端热更新同步设置SetRoutingStrategy与SetKeyAffinityTTL;生成调度重试循环注入种子;internal/app/dispatch.go:dispatchKey纳入种子,不同种子的等待队列分开。key_affinity_ttl输入框(选key-affinity时显示,立即生效)+ 7 种语言文案 + 后端错误文案国际化 +.env.example/README.md/README_en.md。测试
TestValidateRoutingStrategyKeyAffinity:校验key-affinity、默认60mTTL、非法值拒绝与持久化读写。TestAdminConfig:验证管理端保存与热更新key_affinity_ttl。TestAuthMiddlewareAPIKeyAffinitySeed:种子为命中 key 的 SHA-256,未配 key 为空。TestKeyAffinityDeterministic/SpreadsSeeds/FailoverSticksToNewAccount/TTLExpiration/WithinAllowedSubset/EmptySeedRotates/IgnoredUnderRoundRobin:覆盖有效期内粘性保持、新种子轮询分散、故障转移后留在新账户不回跳、TTL 过期后重新轮询、子集过滤、空种子轮转与策略隔离。TestDispatchKeyIncludesStickySeed:种子参与排队分组。go test -count=1 ./internal/...与前端vue-tsc --noEmit && vp build通过。评审确认的行为变化(显式接受)
dispatchKey纳入种子后对所有策略生效:round-robin/fill-first 下不同 API key 的等待请求也分成独立队列。账户轮转逻辑不变、wakeHeads逐队列唤醒队首、无死锁/饥饿;跨 key 严格 FIFO 变为按 key 分组 FIFO(各 key 内仍严格 FIFO)。评审确认良性且更合理,显式接受。已知范围边界(后续 PR)
generateWithRetry+acquireWarmLease)注入StickySeed,因为上游隐式前缀缓存在多轮对话生成中收益最大:embeddings/transcriptions/bidi虽然在internal/app层通过ClassifyCandidates+OrderCandidates构造候选列表,但其下游在internal/aistudio包内调用resolveAccountLease/pool.AcquireFor时会用不带StickySeed的新AccountSelection重新排序,需要改动OrderCandidates签名并在aistudio层的重试循环中透传种子才能端到端生效;CountTokens/GenerateTitle/GenerateImage/GenerateVideo则直接在internal/aistudio包内构造AccountSelection(受api → aistudio单向依赖限制无法直接读api包上下文)。X-Session-ID、previous_response_id、prompt_cache_key、metadata.user_id等多种子来源)可在APIKeyAffinitySeed处扩展,见关联 FR。本地验证说明
go test -count=1 ./internal/...全部通过。npm run build(含vue-tsc --noEmit与vp build)通过。