nginx平滑重载需保障长连接优雅释放:设worker_shutdown_timeout、收紧keepalive_timeout、保持proxy_ignore_client_abort off;验证旧worker退出及socket回收;优化upstream复用;压测中验证无rst/fin。

平滑重载(nginx -s reload)本身不中断已有连接,但长连接挂起(如长轮询、WebSocket、HTTP/1.1 keepalive 空闲连接)若配置不当,会在重载时被异常关闭或卡在旧 worker 进程中,导致请求丢失、连接堆积、后端重试等问题。关键不是“避免挂起”,而是让挂起连接能被新旧 worker 协同管理、平稳过渡。
确保长连接可被优雅释放而非强制终止
挂起连接不等于无效连接,Nginx 需允许其自然完成生命周期,而不是在 reload 时粗暴切断:
-
显式设置
worker_shutdown_timeout:在主配置块中添加,例如worker_shutdown_timeout 30s;,明确告诉旧 worker 最多等待 30 秒让挂起连接自行结束(如长轮询返回、心跳超时),避免无限滞留 -
收紧
keepalive_timeout:设为略高于业务最长空闲等待时间(如 30–45 秒),避免默认 75 秒导致大量连接无意义挂起,挤占 worker_connections 并拖慢旧进程退出 -
禁用
proxy_ignore_client_abort off(默认即 off):保持该设置,确保客户端断连时 Nginx 能及时通知后端取消处理,防止后端持续写入而旧 worker 无法释放 socket
验证旧 worker 是否真正释放挂起连接
reload 后不能只看新进程起来,要确认旧 worker 是否按预期退出、socket 是否归还:
- 执行
ps aux | grep nginx | grep 'worker.*[0-9]',记录旧 worker PID;几秒后再次执行,观察该 PID 是否消失;若 30 秒后仍存在,说明有连接未释放 - 对残留旧 PID 执行
lsof -p [PID] | grep -i 'sock\|tcp\|udp',检查是否仍持有大量 ESTABLISHED 或 CLOSE_WAIT 状态的 socket;若有,重点排查对应 upstream 的proxy_read_timeout是否过长,或后端未响应 - 用
ss -s对比 reload 前后 “TCP: inuse” 和 “orphan” 数值——若 orphan 显著上升且不回落,说明挂起连接未被回收
配合 upstream 长连接复用减少挂起压力
后端连接复用不到位,会迫使每个长轮询都新建后端连接,加剧旧 worker 持有连接数,延长退出时间:
- upstream 中启用
keepalive 32;(数值按后端节点数 × 平均并发 × 1.2 估算),并配proxy_http_version 1.1;和proxy_set_header Connection "";,确保与后端稳定复用 TCP - 禁用
ip_hash:长连接生命周期长,固定 IP 绑定易造成单节点连接堆积,改用least_conn;让新请求优先打向连接数更少的后端,减轻局部压力 - 设置合理超时:如
proxy_read_timeout 45s;(略大于业务最长等待),避免 Nginx 在后端未返回时无限等待,导致连接长期挂起
压测中注入 reload 观察真实挂起行为
仅靠配置检查不够,必须在真实长连接流量下验证:
- 用 wrk 模拟长轮询:如
wrk -t4 -c1000 -d120s --latency -s longpoll.lua http://test.com/api(脚本中维持 Connection: keep-alive 并循环发请求) - 在压测运行 90 秒后执行
nginx -s reload,同时用curl http://127.0.0.1/nginx_status(需开启 stub_status)监控 Active connections 曲线——应平缓过渡,无骤降再回升 - 抓包验证:reload 前后对比 TCP 流,确认已有 keepalive 连接未出现 RST/FIN 包;若有,说明旧 worker 强制关闭了挂起连接











