Files
pikasTech-unidesk/docs/MDTODO/details/sub2api-upstream-reliability/R2.2.24_Task_Report.md
T

4.2 KiB
Raw Blame History

R2.2.24 任务报告

结论

错误 #17511(请求 f7ff0efe-64d3-4dd7-8716-20bdfc3a2f90)不是内部 monitor 请求,也不是账号池没有候选。它由用户 1 的 main API KeyID 20)发起,来源 IP 152.53.229.148,分组“自用”(ID 3),流式 /responses

账号 https://sub2.pokexiao.com plus 0.03ID 31)等待 63,238 ms 后返回上游 502Sub2API 在 1 ms 内记录 openai.upstream_failover_switching,随后选号立即以 context canceled 失败,最终 63,239 ms 返回 502。因此准确表述是:切号已触发,但切号开始时请求 context 已取消,备用账号没有被选取和调用。

同一 Key、同一来源 IP 的请求 0503e439-ee9b-4954-bc2d-3087ec3a1d5e 在此前 6 分钟出现相同链路:账号 31 等待 75,965 ms 后返回 504,切号同毫秒触发,选号立即 context canceled。这证明不是单次调度抖动。

运行面证据

  • “自用”分组在窗口内有 9 个账号,其中 7 个可用,当前 18/218 并发,队列 0。
  • #17511 选号失败时仅排除账号 31,不存在“所有备用账号均被排除”。
  • 账号 31 最近窗口 286 次尝试、277 成功、9 失败,失败率 3.15%,质量分 89/B9 次 failover 中 2 次最终客户错误。
  • OpenAI 专用响应头超时当前为 disabled,来源为进程环境。
  • 成功流式请求 TTFT P99 约 61.6 秒、最大约 92.9 秒;因此不能未经验证直接把全局响应头超时压到 45–60 秒。

v0.1.155 源码归因

openai_gateway_handler.go 的 failover 循环:

  1. 选号使用 c.Request.Context()
  2. 上游转发进入 Forward(c.Request.Context(), ...)
  3. Forward 构建上游请求前调用 detachUpstreamContext(ctx)
  4. detachUpstreamContext 返回 context.WithoutCancel(ctx),官方测试 TestDetachUpstreamContextIgnoresClientCancel 明确验证客户端取消不会取消上游请求。
  5. 首轮上游最终返回可 failover 的 502/504 后,handler 回到循环,再次用原始且已取消的 c.Request.Context() 选号,于是立即失败。

这是“首轮上游去取消、后续选号仍绑定下游取消”的生命周期不一致。它允许客户端断开后首轮上游继续消耗时间,但不保留后续 failover 的执行窗口。

版本与配置判断

  • v0.1.153、v0.1.152、v0.1.151、v0.1.150、v0.1.149 均存在相同 context.WithoutCancel 实现及调用,回退到这些版本不能修复该问题。
  • 公网 Caddy owning YAML 的 responseHeaderTimeoutSeconds 为 600 秒,明显长于本次 63 秒,不支持“Caddy 先截断”的假设。
  • 简单降低全局 gateway.openai_response_header_timeout 能让首轮更早失败并为切号留预算,但当前成功 TTFT 长尾已超过 60 秒,未经按请求类型/账号评估会误杀有效请求。
  • 精确匹配账号 31 的原始错误短语 error code: 502 并临时不可调度可减少后续请求再次命中,但不能挽救触发规则的首个请求,也不能修复 context 生命周期。

建议

  1. 源码根治:统一上游请求与 failover 循环的生命周期。至少在进入下一轮前检测原始 context;若业务要求客户端断开后停止工作,则不要对上游使用 WithoutCancel。若业务要求内部继续完成 failover,则选号、转发和最终写回必须使用一个有界的内部 deadline,并在客户端已断开时停止无意义的继续写回。
  2. 增加 failover reserve:每次首字节等待不能耗尽整体预算,应根据请求类型(普通/compact)和剩余预算设置 per-hop deadline;日志需记录 client deadline、hop deadline、remaining budget 和 cancel source。
  3. 配置缓解前先用 CLI 按普通/compact、账号统计 TTFT 与错误等待时长,再选择高于正常长尾且低于客户端 deadline 的阈值;当前证据不足以安全给出单一秒数。
  4. 对账号 31 的稳定原始 502 短语使用通用非 auth 临时不可调度模板,只作为减少连续命中的缓解,不宣称根治。

官方 main 核对

2026-07-14 只读抓取官方 Wei-Shaw/sub2api main 后,detachUpstreamContext 仍返回 context.WithoutCancel(ctx)Responses failover 选号仍使用 c.Request.Context();官方最新主线尚未修复该生命周期不一致。