nginx 反代 502/504 的排查顺序:先分类,再动手

举报
yd_242458115 发表于 2026/11/01 11:14:01 2026/11/01
【摘要】 压测时 502 占 6%,加机器无效:瓶颈是 nginx 连接数被 systemd 的 LimitNOFILE 卡在 1024。可照抄的顺序:按 error.log 关键字分类,直连量 first/total,再核对 fd 上限。

压测当天前端同事说"页面十个里挂三个",我看监控:502 占 6%,504 一起冒。第一反应是加机器,加完还是 6%。最后查出来是 nginx 连接数被 systemd 的 LimitNOFILE 卡在 1024,跟后端无关。这类问题靠顺序,不靠技巧。

一、先把 502 和 504 拆开

502 是 nginx 认为后端的应答不合法,或压根连不上;504 是连上了但后端不回话,等到超时。根因几乎相反:502 多为"后端死了、拒了、连接被打断",504 多为"后端活着但慢"。

一条命令归到具体哪一类:

sudo tail -n 500 /var/log/nginx/error.log \
  | grep -E 'upstream (timed out|prematurely closed|sent invalid header)|Connection refused' \
  | sort | uniq -c | sort -rn | head -n 10

关键字直接决定下一步查什么:

关键字 | 含义 | 下一步

upstream timed out (110: Connection timed out) | 连后端或读响应超时 | 量后端耗时,对 proxy_read_timeout

upstream prematurely closed connection | 后端把连接掐了 | 后端崩溃、被 OOM kill、keepalive 不匹配

connect() failed (111: Connection refused) | 后端没人监听 | 进程真没起来,先看后端日志

二、第二步:绕开 nginx 直连一次

这一步把"nginx 的锅"和"后端的锅"分开。upstream 走 unix socket 时无需知道地址:

curl --unix-socket /run/app/app.sock -sS -o /dev/null \
  -w 'code=%{http_code} connect=%{time_connect} first=%{time_starttransfer} total=%{time_total}\n' \
  http://app/healthz

重点读 first 和 total:first 小、total 大,说明后端开始回了但吐得慢,典型是大响应体或 gzip 吃 CPU;两个都大,说明连处理都排不上队,先查连接池和锁。

我那次实测 first=0.01s total=9.8s,10 秒后才吐第一个字节,而 nginx 默认 proxy_read_timeout 是 60 秒,没到超时线。真正原因是后端在等一把行锁,瓶颈是同一行数据。

三、504 的三个真实来源

1)upstream 没开 keepalive。proxy_http_version 默认 1.0,短连接一路建连,后端 accept 队列排满就表现成"慢"。四个参数要一起上,漏一个就白改:

upstream app_backend {
    server unix:/run/app/app.sock;
    keepalive 64;
}

location /api/ {
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_pass http://app_backend;
    proxy_read_timeout 30s;
    proxy_next_upstream error timeout http_502 http_504;
}

我第一次改完 QPS 一点没动,就是漏了 proxy_http_version 1.1 和清空 Connection:keepalive 开了,请求头却还带着 close,连接照样一个请求断一次。

2)gzip 在 nginx 侧压大响应。gzip_comp_level 从 6 降到 2,同一批 8MB 的 JSON,worker CPU 从 78% 掉到 41%,P99 从 1.9s 回到 0.4s。JSON 压不动,2 和 6 的体积差不到 4%,多花的 CPU 全是白烧。

3)proxy_buffering 默认 on,大响应先落盘再吐,磁盘一慢全变 504。查:

df -h /var/cache/nginx /var/lib/nginx 2>/dev/null
iostat -dx 1 3 | grep -E 'Device|%util'

%util 长期 90% 以上就别怀疑配置了,先换盘,或者干脆关掉临时文件。

四、502 的三个真实来源

1)连接数卡在文件描述符上。算一下就知不够:一个客户端请求占两条连接(客户端一条、到后端一条),默认 worker_connections 512 实际只够 256 个并发。查:

pid=$(pgrep -o nginx)
ls /proc/$pid/fd | wc -l
grep -i 'open files' /proc/$pid/limits
nginx -T 2>/dev/null | grep -E 'worker_(processes|connections|rlimit_nofile)'

我那次 fd 数顶在 1024,worker_rlimit_nofile 写在 nginx.conf 里完全没用——systemd unit 里的 LimitNOFILE 优先级更高。改 unit 才生效:

[Service]
LimitNOFILE=65535
sudo systemctl daemon-reload && sudo systemctl restart nginx
pid=$(pgrep -o nginx); grep -i 'open files' /proc/$pid/limits

重启后那行必须看到 65535,看不到就是 unit 没生效。

2)后端被 OOM killer 干掉,重启那几秒正好落在压测窗口。一条命令定案:

journalctl -k --since '-2h' | grep -iE 'killed process|out of memory'

我捞到 3 条,时间点和 502 峰值完全对齐。"一阵一阵、过会自己好"就是后端在反复重启。

3)upstream 写域名,后端换地址了,而 nginx 只在启动时解析一次,之后一直往旧地址发。特征是重启 nginx 立刻好、几小时后又坏。用变量强制每次解析:

resolver <你环境里的 DNS 地址> valid=10s;
set $backend app.internal;
proxy_pass http://$backend$request_uri;

注意 proxy_pass 一旦用变量,nginx 就不自动拼 URI,必须手动补 $request_uri,否则所有请求都打到根路径——这个坑我踩过,接口层整个 404。

五、我现在的固定六步

1. grep upstream,先分到 timeout 还是 refused / prematurely

2. curl --unix-socket 直连,读 first 与 total

3. journalctl -u app -n 100 --no-pager | grep -iE 'oom|panic|timeout'

4. ls /proc/$(pgrep -o nginx)/fd | wc -l 对比 limits

5. ss -s | head -n 5,timewait 上万说明 keepalive 没生效

6. 改完必验:同一路径连打 200 次,5xx 为 0 且 P99 下降

一句话结论:502 先怀疑"连不上、连接被打断",504 先怀疑"慢"。error.log 关键字、curl 直连的 first/total、/proc 里的 fd 数,这三样到手就不用猜了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。