症状
固定的失败时长就是指纹——30、60 或恰好 100 秒意味着某个配置预算,不是随机故障。先认出应答者:524 加 cf-ray 表示 Cloudflare 在固定的 100 秒等不到源站;504 加 server: nginx 表示 nginx 的 proxy_read_timeout 先到期。504 是“某个代理宣布的截止时间”,不是“源站挂了”的证据——源站可能慢、可能健康但排队、甚至可能早已在客户端放弃之后完成。下一步跨层对时间戳:应用 95 秒完成而客户端 100 秒收到 524,证明源站慢而活;应用 40 秒完成而客户端 30 秒收到 504,则中间某层提前放弃。
诊断思路
先读失败响应的 server/cf-ray/via 头认出“放弃者”(它不等于“元凶”);再把观测到的失败时长与各层配置逐一比对,命中者即触发的预算,对不上的时长考虑应用层取消;最后用 trace-id 贯穿 CDN、LB、应用三层日志对时间戳。不同层先放弃会出现不同错误码与时长,这本身就是分层指纹。中间层重试在最小超时后面堆叠,会把一次慢变成多次慢。
根因
代理链上每个环节(CDN、WAF、LB、ingress)有自己的超时,最小者先赢并生成 504。应用合法地需要更久(慢报表、冷缓存)时,链路在源站完成之前就已向客户端宣布失败。
修复与验证
修时长或修预算二选一:工作确实超出用户侧预算就改形态——202 加轮询任务化;否则把预算向外单调对齐(应用 90s > ingress 100s > LB 110s > CDN),让最贴近工作的一层拥有截止权;慢路由做路由级覆盖(location /reports/ 里单独 300s),别让一条慢路径放宽整条链。验证时慢路由在专属预算内完成、快路由保留紧凑护栏、日志里出错的层与预期的截止权所有者一致。把所有超时抬到最慢路径的高度,会让一个慢端点变成整条链的容量黑洞。
代码示例
$ curl -sI https://app.example.com/slow | grep -iE "server|cf-ray"
HTTP/2 524
server: cloudflare
cf-ray: 8a1b2c3d4e5f
app internal deadline: 90s (the real work bound)
ingress timeout: 100s
LB timeout: 110s
CDN: 524-capable or bypassed for this route
POST /reports HTTP/1.1
...
HTTP/1.1 202 Accepted
Location: /reports/jobs/8812
location /reports/ {
proxy_read_timeout 300s; # scoped: only the slow route pays
}