别做那种一见 502 就去调大超时的人
一句话总结
502表示“收到了无效的响应”,504表示“在规定时间内没有收到响应”,如果把这两个混在一起,只会不断延长超时时间,浪费几个小时。
为什么这是问题?
在应对障碍中耗费最多时间的不是找不到原因,而是确信错误的原因。如果把502定性为“后端死了”,那么就重新启动后端,如果不行,就增加超时时间,如果还是不行,就增加服务器。这段时间一直没有动过真正的原因,即响应头大小或连接提前终止。
如果有数字,就会以这种确信为基础而改变。在访问日志中,从每个URL的响应时间排序和5xx分布中提取出来,在nginx错误日志中单独统计connection refused和upstream timed out,502和504就会自动消失。计算这两个数字所需的时间是1分钟。
502和504是另一回事
| 代码 | 含义 | nginx 发这个的时候 | 常见原因 |
|---|---|---|---|
| 502 Bad Gateway | 上游收到了无效的响应或拒绝连接 | 拒绝连接、解析响应失败、连接提前终止 | 后端故障、队列饱和、响应头大于缓冲区 |
| 504 Gateway Timeout | 上行端在规定时间内没有响应 | proxy_read_timeout超出 |
慢查询,外部联动延迟,锁定等待 |
| 503 Service Unavailable | 无可用上游 | 所有服务器都被排除在故障处理之外 | 全部故障、健康判断错误 |
在这里最常出错的地方就是这个。 把502定性为“上游死了”。
规范所说的502是“网关收到了来自上游的无效响应” 不是“上游没有回应”。虽然有回应,但也有502是因为代理处理不了的情况。 代表性的情况是响应头比代理缓冲区大的情况。
这种情况如此糟糕的原因:上游的健康检查一直正常。 因为健康检查响应的头衔很小。所以“服务器没问题,但只有特定用户出现502”。 那个特定用户一般通过SSO登录,是cookie很大的用户。
proxy_buffer_size 16k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
还有一定要记住的:502会发生,延长超时时间没有任何效果。 超时是504的故事。把这两个混在一起的话会浪费几个小时。
诊断梯子——curl五个步骤
症状模糊时按照这个顺序缩小范围。特别是第5步将搜索空间缩小一半。
# 1. 이름이 풀리는가
getent hosts api.example.com
# 2. 포트가 열려 있는가 (ping 은 이 환경에서 안 되니 쓰지 않는다)
curl -s -o /dev/null -w '%{http_code}\n' --connect-timeout 3 http://api.example.com/
# 3. 상태코드와 헤더
curl -sSI https://api.example.com/health
# 4. 시간 분해
curl -s -o /dev/null -w 'dns=%{time_namelookup} conn=%{time_connect} \
tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://api.example.com/
# 5. ★ 프록시를 건너뛰고 업스트림에 직접, 원래 Host 헤더를 유지한 채
curl -sSI -H 'Host: api.example.com' http://127.0.0.1:8080/health
5次结果的解释是核心。
- 上游直接呼叫正常→问题在代理和上游之间(设置、头衔、缓冲区、超时)
- 上游直接呼叫也失败→代理无罪。需要看应用程序或DB。
**-H 'Host: ...'除去这个的话,这个比较毫无意义。**如果存在虚拟主机路由的情况
因为Host不同的话,就会直接去其他应用程序。
4次的时间分解也很有用。
time_appconnect本身很长→TLS握手。认证证书链验证或OCSP查询怀疑time_appconnect哇time_starttransfer关系疏远→应用程序/DB。 不是网络
至少读取GC日志
[2026-08-19T02:14:33.221+0900][12.334s] GC(41) Pause Full (System.gc()) 486M->402M(512M) 812.443ms
在这一行中要读的内容。
Pause Full— Full GC。如果经常的话就成问题了。486M->402M(512M)— 回收前→回收后(整体屁股)。回收后接近整体的话 实际上是内存不足。这是和容易恢复但经常丢失的问题不同。812.443ms— 在这段时间内,应用程序停止。响应时间延迟的身份经常是这样。
判断标准不是绝对值,而是趋势和模式。
- Full GC后使用量也继续上升→怀疑内存泄漏→内存溢出
- Full GC很少见,但Young GC非常频繁→短期对象过度生成→代码问题
- 只聚集在特定时间(部署时间) → 那个部署是犯人
OOM出现的时候
java.lang.OutOfMemoryError根据种类,措施完全不同。
| 信息 | 意思 | 第一次措施 |
|---|---|---|
Java heap space |
臀部不足或漏水 | 臀部堆放分析。随便增加-Xmx的话,漏水就照样 |
Metaspace |
类元数据不足 | 通过重复部署怀疑类加载器泄漏。重新启动后观察 |
GC overhead limit exceeded |
大部分时间都花在GC上,但几乎无法回收 | 实际上是漏水。垃圾处理 |
unable to create native thread |
线程生成失败 | 不是Hype,而是OS的限制/线程泄漏。线程dump |
最后一句特别让人困惑。如果是OOM,但不是臀部问题。
-Xmx如果上传的话反而会变差(臀部变大的话线程用内存会减少)。
而且只有在垃圾场爆炸的那一刻才能收到。
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<경로>如果不提前放进去的话
如果能重现的话,到时候见吧”,但一般情况下不会重现。
重新启动之前留下证据
应对障碍的原则是优先恢复。但是,在按下重新启动按钮前1~2分钟的话 这三个是可以确定的。
PID=$(pgrep -f tomcat | head -1)
jcmd $PID Thread.print > /tmp/threads_$(date +%H%M%S).txt
jcmd $PID GC.heap_info > /tmp/heap_$(date +%H%M%S).txt
cp /opt/tomcat/logs/catalina.out /tmp/catalina_$(date +%H%M%S).out
如果珍惜这1~2分钟,就永远找不到原因。 如果“先重新启动就行了”重复三次,第四次即使重新启动也行不了, 那时没有任何证据。
在日志中实际要抽取的东西
从访问日志中立即提取的四个。
- 状态代码分布 — 5xx 从什么时候开始增加的?
- 响应时间上位URL — 哪里慢?
- 按上游分列的错误分布—只有特定服务器有问题吗?
- 分堂请求次数——流量急剧增加是原因还是结果?
4号很重要。虽然可能是因障碍而请求增加的原因, 也有因为用户不行,所以反复刷新而增加的结果的情况很多。 仔细看开始时间就能区分开来。
在现场相遇的样子
最糟糕的形式是**“服务器没问题,但只有特定用户收到502”**。健康检查响应因为头衔很小,所以总是通过,只有部分用户的会话cookie很大,才会通过代理缓冲区收到502。这时监控仪表板都是绿色的,用户咨询是唯一的信号。
而且在重新启动之前需要养成留下证据的习惯。Hip dump和Thread dump在重新启动的瞬间消失,直到同样的故障再次出现之前无法知道原因。