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 与代理层稳定可靠,具备可预测的容量上限。