Linux Nginx 平滑重载时旧进程什么时候完全退出

发布时间:2026/10/4 11:16:38
Linux Nginx 平滑重载时旧进程什么时候完全退出
前言执行完nginx -s reload用ps一看怎么还是两组 worker 同时跑着再等一分钟旧的那组才慢慢消失。更糟的情况是灰度发布时改了upstream新 worker 已经起来旧 worker 却还攥着几十条连接不放导致流量两边都进配置看起来像是没生效。这不是 bug是reload的设计目标不丢任何一条已建立的连接。代价就是旧 worker 会赖着不走直到它手上的连接全部自然结束。很多人以为 reload 是原子的、瞬间完成的于是在 reload 之后立刻删文件、改权限、切数据库结果被旧 worker 拖出一堆诡异错误。本文讲清旧 worker 的退出条件、决定它赖多久的那几个超时参数以及worker_shutdown_timeout这个强制清场开关。文末给出一个能亲手复现旧 worker 被 keepalive 连接拖住的实验。以下基于 nginx 1.20 / 1.22 / 1.24Debian 12、RHEL 9worker_shutdown_timeout需要 nginx 1.11.11 及以上。请用nginx -v确认版本。一、reload 时 master 和 worker 各做了什么nginx -s reload的实质是给 master 进程发SIGHUP也可以直接kill -HUP $(cat /run/nginx.pid)。master 收到后按顺序做四件事重新读取并校验配置。语法或指令值有问题master 会往error.log写错误并放弃本次重载旧 worker 继续按旧配置跑。这是最容易被忽略的一步——reload 命令返回 0不代表新配置生效了。启动新的 worker 进程新 worker 按新配置初始化包括新打开的日志文件。向旧 worker 发送SIGQUIT优雅退出信号。master 继续持有监听套接字和 pid 文件自己不受影响。旧 worker 收到SIGQUIT后立即关闭自己的监听套接字副本不再 accept 新连接把手上的请求处理完正在读请求体的、正在等上游响应的都继续等待已经空闲的 keepalive 连接直到客户端主动关闭或keepalive_timeout到期上述条件全部满足后进程退出。二、旧 worker 到底会赖多久把上面的条件翻译成一个退出时刻的公式旧 worker 退出时间 ≈ max( 进行中请求的剩余处理时间, 空闲 keepalive 连接等到 keepalive_timeout 的时间, 正在等待上游的连接的 proxy_read_timeout 剩余时间 ) 封顶worker_shutdown_timeout如果配了几个关键数字参数默认值对退出的影响keepalive_timeout内置默认 75s最主要的原因。空闲长连接会一直占到超时为止proxy_read_timeout60s到上游的长连接如 WebSocket会一直占住client_body_timeout60s上传没传完的请求会拖住worker_shutdown_timeout未设置不封顶设了才会强制终止所以reload 之后旧 worker 还在最常见的解释是发行版自带的nginx.conf里通常写的是keepalive_timeout 65;加上一两个挂着的连接旧 worker 就会活上一分钟以上。这期间它不接新连接只是把手上那点活干完。三、worker_shutdown_timeout给优雅退出加上限如果旧 worker 因为一条长连接WebSocket、SSE、大文件下载迟迟不退出它会一直占着内存和文件描述符。worker_shutdown_timeout用来兜底# 写在 main 上下文与 worker_processes 同级不是 http 里 worker_processes auto; worker_shutdown_timeout 10s; events { worker_connections 1024; } http { # 缩短 keepalive 让 reload 更快完成代价是客户端复用连接的收益略降 keepalive_timeout 15s; # 内置默认 75s client_header_timeout 12s; client_body_timeout 12s; send_timeout 10s; }worker_shutdown_timeout的语义是worker 在收到退出信号后如果超过这个时间还有连接没结束master 会强行关闭剩余连接。它不是一个独立的定时器而是给整个优雅退出过程设了一个截止时间。需要权衡的是调小它会让 reload 更快完成、新旧 worker 并存时间更短但那些被强行掐断的连接客户端会看到连接重置。对绝大多数 HTTP 短连接业务keepalive_timeout调到 15~30 秒是合理的折中。四、动手复现一条 keepalive 连接如何拖住旧 worker准备一个最小站点然后开一个连接挂着不关。mkdir -p /srv/www printf hello\n /srv/www/index.html# /etc/nginx/conf.d/reload-demo.conf server { listen 8080; server_name _; root /srv/www; keepalive_timeout 120s; # 故意放大方便观察 }nginx -t nginx -s reload OLD_WORKER$(pgrep -f nginx: worker process | head -n 1) echo 观察中的 worker PID: $OLD_WORKER在另一个终端里用 bash 内建的/dev/tcp手工建立一条 keep-alive 连接并且故意不关闭不要退出这个 shellexec 3/dev/tcp/127.0.0.1/8080 printf GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n 3 head -n 8 3 # 到这里连接已经建立并保持打开保持这个终端不关回到第一个终端执行 reload然后反复观察nginx -s reload # 进程名会区分出新旧较新版本会把退出中的旧 worker 标成 shutting down ps -eo pid,ppid,etime,stat,cmd | grep [n]ginx # 看旧 worker 手里还剩什么连接 ss -tnp | grep -E pid$OLD_WORKER你会看到旧 worker 的进程仍然在列ss显示它名下还有一条 ESTABLISHED 连接。这条连接在 shell 里被exec 3打开着只有keepalive_timeout 120s到期或你主动exec 3-关闭它旧 worker 才会退出。在新版本的 Nginx 里处于退出流程的旧 worker 的进程标题会变成nginx: worker process is shutting down这让ps一眼可辨老版本标题不变只能靠 PID 和etime已运行时长区分请以你机器上的实际输出为准。五、发布流程里必须注意的几件事因为旧 worker 会继续存活一段时间滚动发布时如果直接删掉旧版本的代码目录、卸载旧的 PHP/Python 版本、或者 drop 掉旧数据库连接池旧 worker 上正在处理的请求就会炸。稳妥的顺序是# 1. 先确认新配置能过语法和逻辑校验 nginx -t # 2. 记录当前 worker重载 BEFORE$(pgrep -f nginx: worker process | sort | tr \n ) nginx -s reload # 3. 轮询等待旧 worker 全部消失再动旧资源 for i in $(seq 1 60); do STILL$(pgrep -f nginx: worker process | sort | tr \n ) [ $STILL $(pgrep -f nginx: worker process | sort | tr \n ) ] || true sleep 1 done ps -eo pid,etime,cmd | grep [n]ginx: worker真正要判断旧 worker 是否已退出靠的是把 reload 前的 worker PID 列表和当前的做差集而不是等固定秒数。给pgrep加-f是因为进程标题里带空格。常见坑点❌ 看到nginx: worker process由 1 个变成 3 个就以为配置文件写重复了、有多个实例在跑。 ✅ reload 期间新旧 worker 并存是正常现象用ps -eo pid,ppid,etime看启动时间就能区分新旧。❌ 以为nginx -s reload之后新配置立刻 100% 生效。 ✅ 新连接走新配置但旧 worker 手上的连接仍按旧配置处理直到它们结束。需要立刻彻底生效只能nginx -s quit再启动但那会断开所有连接。❌ reload 之后马上删除旧版本的代码目录或旧 upstream 后端主机。 ✅ 旧 worker 还在为旧连接服务。要么等旧 worker 全部退出用 PID 差集判断要么在发布流程里留一个优雅退出窗口。❌ 把worker_shutdown_timeout写在http块里。 ✅ 它是 main 上下文的指令与worker_processes、worker_rlimit_nofile同级写错位置会导致nginx -t报directive is not allowed here。❌ 配置写错后执行nginx -s reload看到命令没报错就以为生效了。 ✅ reload 的失败信息只写进error.log。养成 nginx -t先过、再看error.log尾部 的习惯。❌ 用nginx -s reload来实现重启服务以释放内存泄漏。 ✅ reload 不会清掉旧 worker 的常驻内存新旧并存期间内存反而更高。真正要复位进程得 stop 再 start。❌ 用kill -9处理赖着不走的旧 worker。 ✅kill -9会直接重置它手上的所有连接客户端报连接被重置。要么等超时要么用worker_shutdown_timeout让 master 在可控时间内收尾。总结问题答案依据reload 做了什么读新配置 → 起新 worker → 给旧 worker 发SIGQUITnginx -s reload等价于kill -HUP旧 worker 何时停止接新连接收到SIGQUIT的瞬间立即关闭监听套接字副本旧 worker 何时退出手上所有连接结束后进行中的请求 keepalive 空闲连接最常见的拖时间因素keepalive_timeout内置默认 75s发行版常见 65s如何强制设上限worker_shutdown_timeoutmain 上下文需要 nginx 1.11.11服务是否会中断不会监听套接字由 master 持有请求不丢如何确认退干净对比 reload 前后的 worker PID 差集ps -eo pid,etime,cmdnginx -s reload是零丢弃的重载因此旧 worker 的退出时机由它自己的连接决定而不是由 reload 命令决定。理解这一点就能明白为什么keepalive_timeout和worker_shutdown_timeout这两个参数会直接影响发布速度也就能在滚动发布里正确安排何时才能清理旧资源。