nginx热升级的可靠性源于execve进程替换与信号协作,而非文件原子替换;通过usr2启动新master复用监听socket,winch令旧worker优雅退出,quit终结旧master,全程不修改运行中二进制。

nginx 热升级过程中二进制文件替换本身并不具备原子性,真正保障服务连续性的关键在于进程替换机制(execve)与信号协作的组合设计,而非文件系统层面的“原子替换”。源码层面看,整个流程规避了对正在运行的旧二进制文件的修改,而是让新主进程通过 execve() 加载新二进制,并借助 kill -USR2 / kill -WINCH / kill -QUIT 三步信号协同完成平滑过渡。
热升级不修改旧二进制,只启动新进程
源码中(src/os/unix/ngx_process.c 及 src/core/ngx_cycle.c),执行 nginx -s upgrade 实际触发的是向当前 master 进程发送 USR2 信号。master 收到后:
- 不做任何文件覆盖操作,不调用
rename()或cp替换自身磁盘上的可执行文件; - 直接调用
fork() + execve()启动一个全新子进程,传入的是新路径下的 nginx 二进制(如/usr/sbin/nginx.new); - 新进程在
execve()成功后,从头加载新代码、重读配置、初始化 cycle,但复用旧 master 的监听 socket(通过SO_REUSEPORT或SCM_RIGHTS传递 fd)。
监听 socket 复用是零中断的核心
旧 master 进程不会关闭监听 socket,新 master 通过以下方式继承:
- 在 fork 前,旧 master 调用
ngx_pass_open_socket()(位于src/os/unix/ngx_process_cycle.c),将监听 fd 封装为 Unix domain socket 消息,通过sendmsg()发送给新进程; - 新进程在
ngx_add_inherited_sockets()中接收并重建 socket,设置SO_REUSEADDR和SO_REUSEPORT(若内核支持),确保新旧 master 可同时 accept 同一端口连接; - 此时连接请求由内核负载分发给任一 master,业务请求不受影响。
优雅退出依赖父子进程状态同步与信号时序
原子性感知来自用户视角的“无感知切换”,其实现靠三阶段信号控制:
-
kill -USR2 $old_pid:启动新 master,双 master 共存; -
kill -WINCH $old_pid:旧 master 指示所有 worker 进程ngx_worker_process_exit(),逐步关闭监听、处理完已有请求后退出; -
kill -QUIT $old_pid:旧 master 自身退出,仅当所有 worker 已终止才真正结束。
整个过程没有竞态写文件,也没有对运行中二进制镜像的修改——Linux 下已加载的进程其 text segment 被内核锁定,即使原文件被删除或覆盖,进程仍按原始 inode 执行,这是热升级可行的底层基础。
验证原子性边界:文件替换 ≠ 进程替换
你可以手动验证该机制:
- 运行 nginx 后执行
ls -li /proc/$(pidof nginx)/exe,记录 inode 号; - 替换磁盘上 nginx 二进制(如
mv nginx.new nginx && chmod +x nginx); - 再查
/proc/.../exe,inode 已变,但旧进程仍在运行旧代码——说明进程生命周期与磁盘文件解耦; - 只有新启动的 master 才加载新 inode 对应的代码。
因此,“二进制替换的原子性”是误称;准确说是进程演进的可控性与 socket 生命周期的延续性共同构成热升级的可靠性。










