随着 Nginx 越来越流行,Nginx 的版本迭代也越来越频繁,当我们需要使用某些新版本的特性或者修复某个旧版本的 BUG 时,就要对 Nginx 进行升级。然而线上业务大多是 7*24 小时不间断运行的,我们需要在升级的时候保证不影响在线用户的访问。Nginx 的热升级功能可以解决上述问题,它允许新老版本灰度地平滑过渡,这受益于 Nginx 的多进程架构。
Nginx 多进程架构
- master 是 Nginx 的主进程,master 进程负责启动 worker 进程,接收来自外界的信号,管理 worker 进程。
- worker 进程负责处理客户端的连接请求。
配置了缓存功能后,Nginx 就会启动 Cache Manager 进程和 Cache Loader 进程。
- Cache Manager 是一个常驻进程,它周期性地运行来淘汰过期缓存或者强制删除某些缓存文件来释放磁盘空间。在两次缓存管理器启动的间隔,缓存的数据量可能短暂超过配置的大小。
- Cache Loader 进程只在启动时运行一次,读取对应目录中存在的缓存文件,在内存中生成对应的文件元数据。
操作系统规定,每一个进程都必须由另一个进程启动,这两个进程就称为父子进程,其中,子进程自动继承父进程已经申请到的资源,比如监听的 80 端口。在Linux中,子进程是由 fork 函数创建的,最初它只是父进程的副本。比如在生产环境中启动 Nginx 时(即 master_process on),Nginx 会在绑定 80 端口后再用 fork 函数生成 worker 子进程(注意,Nginx 会自动将父进程名字改为 nginx: master process),这样,worker 进程也可以通过 80 端口与客户端建立 TCP 连接。当然,多个 worker 进程同时监听 80 端口时,系统内核会有一套算法决定某个连接由哪个 worker 进程处理(可以参考Linux 3.9 内核版本后提供的SO_REUSEPORT选项),从而均衡多个 worker 子进程间的负载。
Nginx 支持的信号
# master进程支持的信号 TERM,INT: 立刻退出,相当于 nginx -s stop。 QUIT: 等待工作进程结束后再退出,优雅地退出,相当于 nginx -s quit。 HUP: 重新加载配置文件,使用新的配置启动工作进程,并逐步关闭旧进程。相当于 nginx -s reload 命令。 USR1: 重新打开日志文件,相当于 nginx -s reopen。 USR2: 启动新的主进程,实现热升级。 WINCH: 逐步关闭工作进程 #worker进程支持的信号 TERM,INT: 立刻退出 QUIT: 等待请求处理结束后再退出 USR1: 重新打开日志文件
热升级主要用到了 USR2 和 WINCH/QUIT 信号。
worker_processes 2; #启动2个worker进程 user nginx; #worker用户为nginx events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; #监听80端口 server_name localhost; location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
使用以下命令启动 Nginx:
sbin/nginx
此时 Nginx 服务已经启动并且可以正常访问:那么,既然 master 与 worker 可以绑定同一端口,那么升级新版本 Nginx 时,也由现在的老 master 进程启动(子进程默认是父进程的副本,但通过 exec 函数可以载入新版本的 Nginx 程序。这样,新 master 进程就是老 master 进程的子进程,可以共享老版本 Nginx 已经打开的、包括端口在内的各类资源。至此,两个版本的 Nginx 同时运行并接收请求,然后只要老版本的 Nginx 停止建立新连接,内核自然只会将新的连接交给新版本的 Nginx 处理,等到老版本 Nginx 处理完现存的客户请求后可令其退出,这就完成了平滑升级。
平滑升级 Nginx 通常会经历 3 个阶段:
- 1.仅老 Nginx 进程在运行,此时先备份 Nginx 二进制文件,再用新版本的 Nginx 二进制文件覆盖原位置,然后通过 kill 向老 master 进程发送 USR2 信号。这样老 master 进程就会生成新的子进程,同时用 exec 函数载入新版本 Nginx 的二进制文件,并将进程改名为nginx: master process。
- 2.新老 Nginx 进程同时并存,此时需要通过信号 QUIT 命令老 master 进程优雅退出。
- 3.当处理完所有请求后,老版本的 worker 和 master 进程依次退出。当老版本的 master、worker 进程都退出后,根据 Linux 内核的规则,pid 为 1 的系统守护进程将成为新 master 的父进程。此时平滑升级完毕。
备份并替换旧版本 Nginx 二进制文件
旧版本的 Nginx 为 1.14.2 版本:
[root@nginx-plus1 nginx]# /usr/local/nginx/sbin/nginx -v nginx version: nginx/1.14.2
备份二进制文件:
cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old
下载新版本 Nginx 安装包,版本为 1.19.0,编译生成二进制文件:
tar -xzvf nginx-1.19.0.tar.gz /root/nginx-1.19.0 ./configure make #make这一步骤其实就已经生成二进制文件了,后面不需要make install了,make install其实就是把文件拷贝到指定目录
替换旧版本的二进制文件:
cp /root/nginx-1.19.0/objs/nginx /usr/local/nginx/sbin/nginx -f
通过 kill 命令向老 master 进程发送 USR2 信号,让老 master 生成新的子进程(新 master 进程),同时用 exec 函数载入新版本的 Nginx 二进制文件。
kill -USR2 14912
拉起新 Nginx 进程
可以看到新 master 的父进程是老 master 进程。并且 80 端口还是由老 master 进程在监听,由于新 master 进程和新 worker 进程会继承老 master 进程的资源,因此它们也能监听 80 端口。