Nginx + .NET 压测与连接问题排查总结

Nginx + .NET 压测与连接问题排查总结

本文是一次完整的 线上问题排查 + 架构修复 + 压力测试验证 的工程级总结,可作为 事故复盘、容量评估报告、运维 Runbook 使用。


一、问题背景与现象

初始问题

  • Nginx 错误日志频繁出现:

    upstream timed out (110: Connection timed out) while connecting to upstream
  • 请求多为:

    OPTIONS /api/Ads/resolve HTTP/2.0
  • 上游地址:<code>127.0.0.1:5085</code>(.NET Kestrel)

同时观测到的内核异常

  • <code>listen queue overflow</code>
  • <code>SYNs to LISTEN sockets dropped</code>
  • <code>SYN cookies sent</code>
  • <code>TCPTimeWaitOverflow</code>

直接影响

  • Nginx 无法连接本机上游
  • API 出现大量失败
  • 高并发下服务不可用

二、根因分析(结论级)

1️⃣ Nginx → Kestrel 连接模型错误(核心根因

在 <code>location /</code> 中错误地配置了:

proxy_set_header Connection "upgrade";

导致:

  • 所有普通 HTTP 请求被迫使用 <code>Connection: upgrade</code>
  • Keepalive 失效
  • 每个请求都建立/断开 TCP 连接

👉 直接引发短连接风暴


2️⃣ OPTIONS 预检请求被放大

  • 外站(如豆瓣)触发大量跨域请求
  • 每个请求都会先发 <code>OPTIONS</code>
  • <code>OPTIONS</code> 被转发到上游应用

在短连接模型下形成 连接洪峰


3️⃣ 内核层连锁反应

  • SYN backlog 被打满
  • accept queue 溢出
  • 内核开始丢 SYN / 启用 SYN cookies
  • Nginx 连接上游直接超时(110)

三、关键修复措施(工程级)

1️⃣ 引入 upstream keepalive(最关键修复

在 <code>http {}</code> 作用域中新增:

upstream eaglex_upstream {
    server 127.0.0.1:5085;
    keepalive 256;
}

统一使用:

proxy_pass http://eaglex_upstream;
proxy_http_version 1.1;
proxy_set_header Connection "";

2️⃣ 修正 Connection 头使用方式(非常关键

  • ❌ 禁止在普通 HTTP 路径中使用:

    Connection: upgrade
  • ✅ 只在 WebSocket 专用 location 中使用 upgrade

3️⃣ 在 Nginx 层直接短路 OPTIONS 预检

location = /api/Ads/resolve {
    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin $http_origin always;
        add_header Access-Control-Allow-Methods "GET,POST,OPTIONS" always;
        add_header Access-Control-Allow-Headers "Content-Type,Authorization" always;
        add_header Access-Control-Max-Age 86400 always;
        return 204;
    }
    proxy_pass http://eaglex_upstream;
}

👉 彻底消除预检请求对上游的压力放大


4️⃣ 缩短 proxy_connect_timeout(防止雪崩)

proxy_connect_timeout 2s;

避免 Nginx worker 在连接失败时长时间阻塞。


四、压测阶段的验证结论

压测过程中观测到的健康状态

  • <code>.NET</code> CPU:≈ 260%–300%(4C 机器,尚有余量)
  • Nginx CPU:≈ 10%–20%
  • <code>ksoftirqd</code>:极低
  • TCP 状态:

    estab ≈ 3000
    timewait ≈ 30
    orphan = 0
  • 内核累计计数在压测前后 完全无增长

    • listen queue overflow
    • SYN dropped
    • TCPTimeWaitOverflow

明确结论

连接层 / Nginx / 内核 TCP 已完全稳定,系统瓶颈成功转移至 .NET 应用 CPU 层。


五、错误日志的两类问题区分(重要)

A. <code>110 Connection timed out</code>

while connecting to upstream
  • 含义:连接队列 / accept / backlog 问题
  • 根因:连接模型错误
  • 已在本次修复中解决

B. <code>111 Connection refused</code>

connect() failed (111: Connection refused)
  • 含义:上游端口当时 没有进程在监听
  • 可能原因:

    • dotnet 进程崩溃
    • 被 OOM killer 杀死
    • 重启 / 发布

👉 属于 上游进程稳定性问题,与 backlog 无关。


六、压测与日常排障常用监控命令清单

1️⃣ TCP 全局状态(必看)

ss -s

关注:<code>estab / timewait / orphan</code>


2️⃣ 上游端口连接数(Nginx → Kestrel)

ss -ant | awk '$4=="127.0.0.1:5085"{c++} END{print c}'

3️⃣ 上游连接状态分布

ss -ant | awk '$4=="127.0.0.1:5085"{print $1}' | sort | uniq -c | sort -nr

4️⃣ 内核是否再次被打爆(差分判断)

netstat -s | egrep -i 'listen queue|SYNs to LISTEN|SYN cookies|TCPTimeWaitOverflow'

必须用 前后对比 判断是否仍在发生。


5️⃣ Nginx 错误日志关键字

tail -n 200 /www/wwwlogs/eaglex_ads_api.error.log | egrep -i 'timed out|refused|502|504'

6️⃣ 上游进程是否存活 / 是否重启

ps -p <dotnet_pid> -o pid,etimes,%cpu,%mem,cmd
ss -lntp | grep 5085

7️⃣ OOM / 内核杀进程检查

dmesg -T | egrep -i 'oom|killed process'
journalctl -k | egrep -i 'oom|killed'

8️⃣ .NET 运行时指标(强烈推荐)

dotnet-counters monitor System.Runtime --process-id <pid>

关注:

  • CPU Usage
  • ThreadPool Queue Length
  • % Time in GC

七、最终工程级结论(可直接写入文档)

本次问题的根因是 Nginx → Kestrel 连接模型错误(短连接 + OPTIONS 预检放大),导致 TCP listen queue 溢出与 upstream connect timeout。

通过引入 upstream keepalive、修正 Connection 头、在 Nginx 层短路 CORS 预检,并配合合理的超时配置,连接层问题已完全消除。

压测结果表明:系统当前瓶颈已转移至 .NET 应用 CPU 层,TCP 与代理层稳定可靠,具备可预测的容量上限。