线上Nginx频繁502,排查3小时发现是这个配置的问题

监控告警:Nginx 502错误率飙升到5%。

看了眼后端服务,运行正常,没有报错。重启Nginx,好了一会又开始502。

排查了3个小时,最后发现是upstream配置的问题。记录一下排查过程。


问题现象

监控数据

  • 502错误率:从0.1% → 5%
  • 后端服务:正常运行,无报错
  • CPU/内存:正常
  • 发生时间:流量高峰期

特点

  • 不是全部请求都502,大部分正常
  • 重启Nginx后短暂恢复,然后又出现
  • 后端服务日志没有异常

排查过程

Step 1:看Nginx错误日志

lua 复制代码
tail -f /var/log/nginx/error.log

发现大量这样的错误:

kotlin 复制代码
upstream timed out (110: Connection timed out) while connecting to upstream
upstream prematurely closed connection while reading response header

关键信息:是upstream连接的问题,不是后端服务本身的问题。

Step 2:检查后端服务状态

perl 复制代码
# 查看后端服务进程
ps aux | grep java

# 查看端口监听
ss -tlnp | grep 8080

# 直接测试后端
curl -I http://127.0.0.1:8080/health

后端服务正常,直接访问返回200。

Step 3:检查连接数

bash 复制代码
# 查看Nginx到后端的连接数
ss -ant | grep 8080 | wc -l

# 查看连接状态分布
ss -ant | grep 8080 | awk '{print $1}' | sort | uniq -c

发现问题了:

复制代码
850 ESTABLISHED
   120 TIME_WAIT
    50 SYN_SENT

有50个连接卡在SYN_SENT状态,说明Nginx到后端的新连接建立不上。

Step 4:检查后端连接队列

perl 复制代码
# 查看后端服务的accept队列
ss -lnt | grep 8080

输出:

css 复制代码
State    Recv-Q   Send-Q   Local Address:Port
LISTEN   129      128      0.0.0.0:8080

问题找到了! Recv-Q是129,Send-Q是128。

这说明accept队列满了(128是默认值),新连接无法被接受。


根因分析

什么是accept队列

perl 复制代码
客户端 → SYN → 服务端(半连接队列)
服务端 → SYN+ACK → 客户端
客户端 → ACK → 服务端(全连接队列/accept队列)
应用程序 accept() → 取出连接

当accept队列满了,新的完成三次握手的连接无法进入队列,客户端会收到超时或RST。

为什么队列满了

后端是Spring Boot应用,默认配置:

yaml 复制代码
server:
  tomcat:
    accept-count: 100  # Tomcat的accept队列

而系统层面的限制是net.core.somaxconn = 128,取两者较小值,所以实际accept队列只有128。

流量高峰时

  1. 请求量大,新连接多
  2. accept队列128不够用
  3. 新连接被拒绝
  4. Nginx收到超时,返回502

解决方案

方案一:调大系统参数

bash 复制代码
# 查看当前值
sysctl net.core.somaxconn

# 临时修改
sysctl -w net.core.somaxconn=65535

# 永久修改
echo "net.core.somaxconn = 65535" >> /etc/sysctl.conf
sysctl -p

方案二:调整Tomcat配置

yaml 复制代码
server:
  tomcat:
    accept-count: 1000      # accept队列大小
    max-connections: 10000  # 最大连接数
    threads:
      max: 500              # 最大工作线程数

方案三:Nginx upstream优化

ini 复制代码
upstream backend {
    server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
    
    keepalive 100;  # 保持连接数,减少新建连接
}

server {
    location / {
        proxy_pass http://backend;
        
        proxy_connect_timeout 5s;      # 连接超时
        proxy_read_timeout 60s;        # 读取超时
        proxy_send_timeout 60s;        # 发送超时
        
        proxy_http_version 1.1;        # 使用HTTP/1.1
        proxy_set_header Connection ""; # 配合keepalive
    }
}

方案四:多实例负载均衡

如果单实例撑不住,可以部署多实例:

ini 复制代码
upstream backend {
    least_conn;  # 最少连接数策略
    
    server 127.0.0.1:8080 weight=1;
    server 127.0.0.1:8081 weight=1;
    server 127.0.0.1:8082 weight=1;
    
    keepalive 100;
}

最终配置

系统参数(/etc/sysctl.conf):

ini 复制代码
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 65535

Spring Boot配置

yaml 复制代码
server:
  tomcat:
    accept-count: 2000
    max-connections: 20000
    threads:
      max: 500
      min-spare: 50

Nginx配置

ini 复制代码
upstream backend {
    server 127.0.0.1:8080;
    keepalive 200;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_connect_timeout 5s;
        proxy_read_timeout 60s;
    }
}

优化效果

指标 优化前 优化后
502错误率 5% 0.01%
accept队列溢出 频繁
连接建立时间 不稳定 稳定<5ms

排查命令汇总

perl 复制代码
# 查看Nginx错误日志
tail -f /var/log/nginx/error.log

# 查看连接状态
ss -ant | grep <端口>

# 查看监听队列
ss -lnt | grep <端口>

# 查看队列溢出统计
netstat -s | grep -i listen

# 查看系统参数
sysctl net.core.somaxconn

# 实时监控连接数
watch -n 1 'ss -ant | grep <端口> | wc -l'

经验总结

502原因 排查方向
upstream timed out 后端处理慢或连接队列满
connection refused 后端服务没启动
no live upstreams 所有后端都不可用
prematurely closed 后端主动断开连接

这次的坑:后端服务看起来正常,但accept队列满了,新连接进不来。

教训

  1. 系统默认的somaxconn=128太小,生产环境必须调大
  2. Nginx配置keepalive可以减少新建连接,降低队列压力
  3. 监控要加上连接队列指标
相关推荐
一步一个脚印一个坑7 小时前
用 APM 全链路追踪,29ms 内定位到 Docker 部署的 SSL 配置错误
javascript·后端·监控
aircrushin8 小时前
端到端AI决策架构如何重塑实时协作体验?
前端·javascript·后端
苦瓜小生8 小时前
【黑马点评学习笔记 | 实战篇 】| 6-Redis消息队列
redis·笔记·后端
yhole8 小时前
springboot 修复 Spring Framework 特定条件下目录遍历漏洞(CVE-2024-38819)
spring boot·后端·spring
BingoGo8 小时前
Laravel 13 正式发布 使用 Laravel AI 无缝平滑升级
后端·php
l软件定制开发工作室8 小时前
Spring开发系列教程(34)——打包Spring Boot应用
java·spring boot·后端·spring·springboot
随风,奔跑9 小时前
Spring MVC
java·后端·spring
美团技术团队9 小时前
美团 BI 在指标平台和分析引擎上的探索和实践
后端
JimmtButler9 小时前
我用 Claude Code 给 Claude Code 做了一个 DevTools
后端·claude
Java水解10 小时前
Java 中实现多租户架构:数据隔离策略与实践指南
java·后端