Skip to content

一次由 TCP TIME_WAIT 暴涨引发的故障排查 ​

现象 ​

压测高峰期,Nginx upstream 报错 connect() failed (99: Cannot assign requested address),页面偶发卡死。监控显示 Nginx 所在节点的 ss -s 中 TIME_WAIT 数量从平时的 2k 暴涨到 6w+。

环境 ​

  • OS: Ubuntu 22.04 LTS
  • 内核: 5.15.0
  • Nginx: 1.24.0
  • 后端服务: Go 1.22 HTTP API,无连接池

排查会用到 ss 命令,如果系统提示未安装,可按发行版安装:

sh
# 安装 ss 命令所在的 iproute2 包
sudo apt-get update && sudo apt-get install -y iproute2
sh
# 安装 ss 命令所在的 iproute 包
sudo yum install -y iproute

排查过程 ​

先看当前连接状态分布:

bash
ss -s

输出:

text
Total: 60012 (kernel 60018)
TCP:   60005 (estab 12, closed 59993, orphaned 0, synrecv 0, timewait 59993/0)

timewait 接近 6w,明显是本地端口段被占满。查看当前端口范围:

bash
sysctl net.ipv4.ip_local_port_range

输出:

text
net.ipv4.ip_local_port_range = 32768	60999

可用端口约 28k,但 TIME_WAIT 已经达到 6w,说明大量连接在极短时间内创建并关闭。

检查 Nginx 配置,发现 upstream 使用的是 HTTP/1.0 短连接:

nginx
upstream backend {
    server 127.0.0.1:8080;
}

server {
    listen 80;
    location /api/ {
        proxy_pass http://backend;
        proxy_http_version 1.0;
    }
}

proxy_http_version 1.0 且没有 Connection 相关配置,默认每次请求都新建 TCP 连接。

根因 ​

Nginx 与后端服务之间使用 HTTP/1.0 短连接,每个请求完成后由客户端(Nginx)主动关闭连接,产生大量 TIME_WAIT。压测 QPS 高时,TIME_WAIT 数量超过本地可用临时端口范围,导致 Cannot assign requested address。

修复 ​

将 Nginx upstream 改为 HTTP/1.1 长连接,并设置合理的 keepalive 连接池:

nginx
upstream backend {
    server 127.0.0.1:8080;
    keepalive 64;
}

server {
    listen 80;

    location /api/ {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

TIP

proxy_set_header Connection "" 必须加,否则 Nginx 会默认把客户端的 Connection: close 转发给后端,导致长连接失效。

同时,在 /etc/sysctl.conf 中开启 TIME_WAIT 快速回收(仅在内网、无 NAT 场景建议开启):

bash
echo 'net.ipv4.tcp_tw_reuse = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

修改后压测,ss -s 中 TIME_WAIT 稳定在 1k 以下,错误消失。

复盘 ​

  • 新增监控:node_exporter 采集 node_netstat_TcpExt_TCPTimeWaitOverflow 和 ss -s 中 TIME_WAIT 数量。
  • 所有 Nginx upstream 配置统一审查,强制使用 HTTP/1.1 + keepalive。
  • 压测方案补充连接状态指标,不再只看 QPS 和错误率。

用 VitePress 构建