Nginx 反向代理偶发 502?upstream keepalive 超时竞态排查与配置对齐

举报
yd_243555368 发表于 2026/11/09 04:08:30 2026/11/09
【摘要】 排查低频 502:从 error.log 动词区分 connect failed 与 prematurely closed,定位 upstream keepalive 超时竞态;给出 keepalive_timeout 与后端 IdleTimeout/SLB 对齐规则、必须配置的 proxy_http_version 1.1 与 Connection 清空、proxy_next_upstream

问题现象:低频 502,日志里全是 "upstream prematurely closed connection"

上周一个网关层的 Nginx 每天稳定出现几十条 502,量不大但很扎眼:白天业务高峰反而不报,凌晨批量任务时才零星出现,重启 Nginx 能压一两天。典型现象如下:

2026/11/06 02:14:07 [error] 31244#0: *8821331 upstream prematurely closed connection while reading response header from upstream,
client: 10.20.31.7, server: api.example.com, request: "POST /v1/batch/import HTTP/1.1",
upstream: "http://10.30.4.18:8080/v1/batch/import", host: "api.example.com"

关键信息三点:错误是读 response header 阶段被后端断开、只发生在 POST 批量接口、后端是 Nginx 到业务服务的 keepalive 长连接。这三条基本把方向锁死在“连接复用”上,而不是后端崩了。

第一步:先分清 502 是谁给的

502 有两种来源,处理方式完全不同:一种是后端真的拒绝了(connect refused / reset),一种是 Nginx 复用了后端已经准备关闭的 keepalive 连接。看 error.log 里的动词就够了:

# 后端连不上:connect() failed
grep -c "connect() failed" /var/log/nginx/error.log
# 后端断开(连接复用被踢):prematurely closed / reset by peer
grep -c "prematurely closed\|reset by peer" /var/log/nginx/error.log

实测结果:connect() failed 为 0,prematurely closed 占 100%。说明 TCP 三次握手成功,问题出在复用后的第一个请求上——这是经典的 keepalive 竞态:后端在 Nginx 拿到连接后、发出请求前刚好关闭了这个空闲连接。为什么凌晨容易撞上?因为那时连接空闲时间长,正好卡在后端 keepalive timeout 的边界。

第二步:对齐三方的超时时间

竞态的根因是超时时间没对齐。Nginx 会缓存空闲连接 keepalive_timeout 这么久,如果这个值大于等于后端(或后端前面的 SLB)的空闲回收时间,就必然出现“我以为是活的,其实已经死了”。

检查 Nginx 侧:

nginx -T 2>/dev/null | grep -nE "keepalive_timeout|keepalive_requests|upstream|proxy_http_version|proxy_set_header Connection"
upstream api_backend {
    server 10.30.4.18:8080;
    server 10.30.4.19:8080;
    keepalive 64;            # 每个 worker 缓存的空闲连接数
    keepalive_timeout 65s;   # 关键:空闲多久后自己丢掉
    keepalive_requests 1000;
}
server {
    location /v1/ {
        proxy_http_version 1.1;                 # 必须 1.1,否则 keepalive 无效
        proxy_set_header Connection "";         # 必须清空,否则发 close
        proxy_connect_timeout 3s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}

这里有两个高频坑:漏了 proxy_http_version 1.1 和 没清空 Connection 头。少任何一个,upstream keepalive 都不生效,每请求一个短连接,性能直接掉一半,同时还会因 TIME_WAIT 堆积引发端口问题。

再看后端侧(Java/Tomcat、Go http.Server、SLB)的空闲回收:

# 后端是 SLB 或云负载均衡时,查它的空闲超时(常见 15s / 60s)
# 后端是自己服务时:
ss -tnp state established '( sport = :8080 )' | head
# Go 服务里应满足 IdleTimeout < Nginx keepalive_timeout
grep -rn "IdleTimeout\|ReadHeaderTimeout" main.go

我们实测的配置是:Nginx keepalive_timeout 65s、SLB 空闲 60s、后端 Tomcat connectionTimeout 20000。三个数字互相错开且 Nginx 最大,竞态窗口长达 45 秒。改法是让上游最小的那个再减 5~10 秒:

keepalive_timeout 45s;   # SLB 60s、Tomcat 20s → 取 20s 下面再小一档亦可

保守做法是直接取后端最小值减 10s。若后端 connectionTimeout=20s,Nginx 侧就设 10s,宁可多一次握手,也不要发到死连接上。

第三步:用重试兜住剩余的竞态

超时对齐能把 99% 的竞态消掉,但网络抖动、滚动发布、后端主动 close 依然会漏。对幂等接口加一次重试代价极低:

proxy_next_upstream error timeout http_502 non_idempotent;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 5s;

注意三点:一是 non_idempotent 只对明确可重放的写接口开启,POST 导入类接口若不加幂等键,重试会造成重复入库,我这里只对 /v1/query 这类只读路径放开;二是重试会把请求打到另一台后端,若 session 粘在单机需要确认无状态;三是 proxy_next_upstream_tries 2 是总次数,别设成 5,否则故障时会成倍放大流量。

第四步:补上观测,别等下次再猜

光靠 error.log 只能事后追,加两个轻量指标就能提前发现:

# stub_status 里的连接状态(Active / Reading / Writing / Waiting)
curl -s http://127.0.0.1:8081/nginx_status
# 空闲连接数应该长期接近 keepalive 上限的 30%~70%
# Waiting 长期为 0 → keepalive 没生效(多半是 proxy_http_version 没写)

再补一条 502 速率告警:

- record: nginx:502_rate
  expr: sum(rate(nginx_http_requests_total{status="502"}[5m])) by (upstream_addr)
        / sum(rate(nginx_http_requests_total[5m])) by (upstream_addr) > 0.001

结果与经验

改完 keepalive_timeout 对齐(65s → 10s)、补上 proxy_http_version 1.1 与清空 Connection、对只读接口加单次重试后,nginx -t 通过并 nginx -s reload 平滑生效。观察 3 天,502 从日均 40+ 条降到 0,stub_status 的 Waiting 从个位数涨到 200 左右,说明连接真正被复用了,后端 TIME_WAIT 也同步下降。

一句话总结:502 先看错误动词定位是“连不上”还是“连接复用被踢”,后者一律先对齐超时,再谈重试和观测。随手把 keepalive_timeout 设得比后端还大,是最常见也最隐蔽的坑。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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