Files
tamabox/outputs/proxy-image-timeout-2026-09-02.md
T
2026-09-08 10:10:44 +08:00

4.5 KiB
Raw Blame History

proxy-image 回源超时事故处置报告

  • 日期:2026-09-02
  • 影响:/proxy-image 拉取 R2 图片稳定超时 502(后台 ProxyImage fetch failed);分享卡生成存在同根因隐患
  • 状态:代码侧已修复并全量验证;待服务器部署验证

一、症状

ERRO[0013] ProxyImage fetch failed  error="Get \"https://r2.337845818.best/tamabox/picture/...\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)"
2026-09-02 23:51:00 Logger: Completed method=GET path="/proxy-image?url=..." status=502 duration=10.57s
  • 同一 URL 每次请求稳定 10.5s 超时(旧 client Timeout: 10s + 开销)
  • 用户在浏览器直接访问该 R2 链接正常

二、证据链(根因判定过程)

证据 结论
报错文本 "awaiting headers" TCP+TLS 已完成、HTTP 已发出,等不到响应头(非 DNS/dial/TLS 失败)
r2.337845818.best 解析结果 Cloudflare anycast:104.21.47.52 / 172.67.144.141 + 2 IPv6 → 多边缘 IP
本地对照:走 https_proxy 代理 ✅ 正常响应
本地对照:强制直连两个 CF IPv4 ❌ code=000,TLS 未建立即断 → 裸连 CF 存在劣化边缘
服务器 curl 裸连 ✅ 200 但耗时 6.46s → 链路"慢而通",非完全不通
服务器 env | grep -i proxy 无代理 → 排除"程序绕过代理"假设

代码侧根因(与 curl 的行为差异):

  1. route/proxy.go 旧 proxyImageDialContext 解析后固定拨第一个 IP——覆盖了标准库的多 IP 容错。anycast 恰好抽中被劣化的边缘 → 稳定等满超时;curl 默认多 IP 尝试所以能成功。
  2. 超时预算过紧:Timeout: 10s(curl 成功都要 6.46s)、TLS 5s、读头阶段无独立兜底。

三、修复内容

1. 共享多 IP 并发拨号(新增)

internal/shared/multidial.go → DialFirstReachable(ctx, dialer, network, ips, port, maxCandidates)

  • 对候选 IP 并发拨号取最先成功(happy-eyeballs 风格)
  • IP 字面量直连、无二次解析 → DNS rebinding 免疫(SSRF 防护不降级)
  • 成功路径取消其余拨号、关闭落选连接(防泄漏);空候选/ctx 取消/截断均有防御

2. /proxy-image(route/proxy.go)

项 旧 新
DialContext 固定单 IP shared.DialFirstReachable(上限 6 并发)
Client.Timeout 10s 25s
TLSHandshakeTimeout 5s 8s
ResponseHeaderTimeout 无 8s(兜底"响应头迟到"黑洞)
超时类错误 不重试 自动重试一次(fetchProxyImageWithRetry)

3. 分享卡生成(route/question/share.go)——排查发现的同根因复发点

  • shareDialContext 同款固定单 IP → 改调共享 helper
  • 超时放宽:Timeout 10→20s、TLS 5→8s、读头 10→8s(图源同为 R2,宁等勿缺)
  • 全仓 DialContext 扫查:仅上述两处,无第三处同类

4. 测试

  • 新增 internal/shared/multidial_test.go(5 项):坏 IP 在前仍选通 / 全败报错 / ctx 取消即返回 / 空候选 / maxCandidates 截断
  • 原 route/proxy_test.go 随实现迁移删除

四、验证矩阵

项 结果
gofmt 无差异
overlay go build ./...(全模块) exit 0
go vet(route + internal/shared) 干净
全量安全相关测试(12 包:templates/cache/conf/form/i18n/imagecheck/internal-route/autoban/censor/shared/internal-template/route-vote) 全 PASS
新增回归测试 5/5 PASS

五、部署验证清单(服务器侧)

  1. 重新编译部署(Linux 补跑 go build ./...,webp/CGO 惯例确认)
  2. 观察日志:ProxyImage fetch failed 应消失
  3. 刷新提问箱页:头像/背景图正常出图
  4. 触发一次分享卡生成(任意提问箱分享链接),确认头像/背景/社交图标完整
  5. 判断标准:链路本身 ~6s 属正常(curl 实测),重点是不再 10s 稳定超时

六、补充诊断(可选)

服务器 curl 成功也要 6.46s,建议部署后定位慢在哪个阶段:

curl -sI --max-time 15 -o /dev/null -w \
"dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} total=%{time_total}\n" \
"https://r2.337845818.best/tamabox/picture/2026/05/13/f95ba0476f90331e190baa65052a03"
  • 若 dns 占大头 → 服务器 DNS 解析慢(可换公共 DNS / 加 hosts)
  • 若 tls 占大头 → Cloudflare 边缘 TLS 劣化(多 IP 并发已缓解,可考虑图源域名走非代理直连评估)
  • 若 connect 占大头 → 链路 TCP 劣化