bigywater.tech
微服务 · 39

优雅摘流与配置热加载:发布期间连接怎么排空,reload 时旧 worker 怎么退出

nginx reload 时旧 worker 怎么退出?长连接会让旧 worker 堆积吗?worker_shutdown_timeout 起什么作用?reload、quit、stop 对在途请求和新连接分别意味着什么?这篇用 nginx 1.31.6 逐项量出来。

3.01 s 对 1.63 s
reload 时在途的 3 s 请求:默认完成,worker_shutdown_timeout 1s 则在 1.63 s 被中断
4(3)
每轮带 2 条长连接 reload 3 次后的 worker 数(1 个新 worker 加 3 个 shutting down 的旧 worker);设 worker_shutdown_timeout 2s 后回到 1(0)
28 / 28 对 0 / 28
reload 与 quit 之后 3 秒内新连接请求成功的数量(quit 立刻关闭监听端口)

reload 与在途请求

实测 · 本机执行输出
  配置                         | 在途的 3 s 请求     | reload 之后的进程                                     | reload 之后的新请求 | 旧 worker 在 reload 之后多久退出
  ---------------------------+----------------+--------------------------------------------------+---------------+-------------------------
  默认                         | 200 · 3.01 s   | reload 后 0.3 s:2 个 worker(其中 1 个在 shutting down) | 200 · 1 ms    | 2.5 s                   
  worker_shutdown_timeout 1s | error · 1.63 s | reload 后 0.3 s:2 个 worker(其中 1 个在 shutting down) | 200 · 1 ms    | 1.2 s                   
  reload 不会打断在途的请求:旧 worker 把手上的请求处理完才退出,新 worker 马上开始接新连接;设了 worker_shutdown_timeout 之后,到点旧 worker 强行关闭还没结束的连接,在途请求会失败

旧 worker 把手上的请求处理完再退出;worker_shutdown_timeout 到点强行结束。

长连接与旧 worker

实测 · 本机执行输出
  配置                         | 每轮 reload 后 1 s 的 worker 数(括号里是 shutting down 的) | 3 轮之后再过 2.5 s | client 的长连接还活着的 | 关掉所有 client 之后 1 s
  ---------------------------+--------------------------------------------------+---------------+-----------------+-------------------
  默认                         | 2(1) → 3(2) → 4(3)                               | 4(3)          | 6 / 6           | 1(0)              
  worker_shutdown_timeout 2s | 2(1) → 2(1) → 2(1)                               | 1(0)          | 0 / 6           | 1(0)              
  每次 reload 产生一个新 worker,旧 worker 只要还有一条长连接就不会退出:不设 worker_shutdown_timeout 时旧 worker 一直堆积(每次发布多一个),直到 client 自己断开;设了超时,到点长连接被强行断开(client 读到 EOF),旧 worker 才退出

只要还有长连接,旧 worker 就不退出;每次发布多一个。

reload、quit、stop

实测 · 本机执行输出
  操作         | 在途的 2 s 请求     | 操作之后 3 s 里新请求的结果(状态码:个数)
  -----------+----------------+-------------------------
  reload     | 200 · 2.01 s   | {200: 28}               
  quit(优雅退出) | 200 · 2.01 s   | {'error': 28}           
  stop(快速退出) | error · 0.53 s | {'error': 29}           
  quit 让 master 立刻关闭监听端口、不再接新连接,等在途请求处理完再退出:在途请求成功,但新连接在 nginx 退出前后一直被拒绝(状态是 error 表示连接被拒或重置),在发布时应当先把流量摘走再 quit;stop 立刻结束,在途请求失败;reload 监听端口不关,新连接一直成功

reload 不关监听端口;quit 关监听端口但等在途请求;stop 立刻结束。

分步演示

优雅摘流与配置热加载
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。nginx 是 Homebrew 的 1.31.6(worker_processes 1);上游是本进程里的 Python 服务或原始 TCP 服务;worker 数用 ps 看 master 的子进程(旧 worker 的进程标题含 “shutting down”);“长连接是否还活着”按 client 能否在 1 s 内继续收到事件、还是读到 EOF 判断。我最初的判断是“能读到数据就活着”,会把缓冲区里剩下的旧数据误当成活着,所以改成排空缓冲后再看;所有数字都是单次测量
    • 没有验证:多个 worker 进程(worker_processes > 1)时的行为;reuseport 监听;nginx -s reload 失败(配置错误)时旧 worker 保持服务;SIGUSR2 热升级二进制;容器里 PID 1 对信号的处理(见 k8s 系列的 preStop 篇)
    京ICP备2026067502号-1京公网安备11010502063487号