1. nginx 架构
在总结了 nginx 配置以后,发现 nginx 可以做到平滑重启和升级,即在重启和升级的过程中服务不间断。于是怀着好奇的心情,开始了探索 nginx 的平滑重启和平滑升级是如何实现的。先解释下笔者对这两个名词的概念:
- 平滑重启:针对的是更新 nginx 的 *.conf 文件
- 平滑升级: 针对的是 nginx 服务的二进制文件更新,比如版本升级或者引入三方的模块重新编译
在正式理解这两个概念之前,需要先了解 nginx 的架构,架构如下图所示:
nginx 采用多进程的方式,包括一个 master 进程和多个子进程。其中 master 进程主要负责:加载配置项、启动工作进程及非停服务升级,worker 进程处理网络请求及响应( worker 进程基于异步及非阻塞的事件驱动模型提供服务)。
异步非阻塞的事件驱动模型单独解释
2. nignx 的 signal 支持
2.1 master 支持的 signal
TERM, INT | Quick shutdown |
---|---|
QUIT | Graceful shutdown |
KILL | Halts a stubborn process |
HUP | Configuration reloadStart the new worker processes with a new configurationGracefully shutdown the old worker processes |
USR1 | Reopen the log files |
USR2 | Upgrade Executable on the fly |
WINCH | Gracefully shutdown the worker processes |
2.2 worker 支持的 signal
There’s no need to control the worker processes yourself. However, they support some signals (意思是这些信号量无需手动处理,但是也能够被 worker 进程支持)
TERM, INT | Quick shutdown |
---|---|
QUIT | Graceful shutdown |
USR1 | Reopen the log files |
3. nginx 平滑重启
3.1 nginx -s reload 命令
nginx -s reload
命令等价于kill HUP $(cat /usr/local/nginx/logs/nginx.pid)
,该信号会触发 master 进程完成以下工作:
- 检查更新后的配置文件是否正确
- 如正确,创建新的 worker 进程,新 worker 进程创建成功后,发送信号给旧 woker 进程使之优雅关闭。
- 如错误,仍旧使用未更新之前的配置
笔者手动实验了一波,见下图:
- 第一次
ps -ef | grep nginx
可观察到当前有一个 master 进程和两个 worker 进程。 - 执行
nginx -s reload
命令后,第二次ps -ef | grep nginx
,观察到两个 woker 进程的进程 id 有变化。 - 更改
nginx.conf
文件后,执行nginx -s reload
命令后,第二次ps -ef | grep nginx
,观察到 worker 进程进程仅剩一个并且进程 id 有变化。
注: 笔者在首次执行 nginx -s reload
的时候,并未变更任何配置,但还是重新产生了两个进程,很好奇这里为什么不检查一下配置有无变更?(待解答,后续补充……)
3.2 ngx_http_dyups_module
nginx reload 操作会影响到长连接,使用 dyups 模块可以避免 reload 操作。dyups(dyups means dynamic upstreams) 是github上一个nginx相关的开源插件,可以在不reload nginx的情况下动态更新upstream。
笔者没用上述插件,具体如何使用可以查看我们客服系统中使用tengine+dyups+consul 解决自动扩容缩容的问题
4. nginx 平滑升级
笔者翻看了 nginx 的源码,发现对 upgrade 指令的定义如下:
build:
\$(MAKE) -f $NGX_MAKEFILE
install:
\$(MAKE) -f $NGX_MAKEFILE install
modules:
\$(MAKE) -f $NGX_MAKEFILE modules
upgrade:
$NGX_SBIN_PATH -t
kill -USR2 \`cat $NGX_PID_PATH\`
sleep 1
test -f $NGX_PID_PATH.oldbin
kill -QUIT \`cat $NGX_PID_PATH.oldbin\`
4.1 平滑升级执行步骤
-
更换新版本的二进制文件
-
发送 SIGUSR2 信号给 master 进程,信号触发执行重命名 .pid 为 .oldbin(e.g.
/usr/local/nginx/logs/nginx.pid.oldbin
),执行新的二进制文件,即重启新的 master 和 worker 进程
- 上图看到 2 组 nginx 实例在运行,共同处理接收到的请求,此时需要优雅停止旧 worker 进程,即发送 WINCH 信号给旧 master 进程
- 观察一段时间后,发现只有新 worker 进程在提供服务,旧 woker 进程已经终止
-
如上图所示,此时有两个 master 进程同时存在,笔者觉得 nginx 这里的设计很好,就好像铅笔后面的橡皮擦、购物后的七天无理由退货,给你足够的时间,让你有机会反悔。按照如下步骤执行
- 1—— 发送 HUP 信号给旧 master 进程,它将重启 worker 进程
- 2 —— 发送 QUIT 或者 TERM 或者 QUIT 给新 master 进程终止新 worker 进程
- 3 —— 最后旧 master 进程将会移除 .oldbin 后缀,一切又恢复到反悔之前的状态啦
-
如果升级的没有问题,发送 QUIT 信号给旧 master 进程,升级过程结束
5.思考
笔者的思考如下:
- nginx 作为知名开源代码,提供的三方开源插件是真的多,只有你想不到,没有人家没写过的。
- tengine 和 OpenResty 基于 nginx 开发,在 nginx 的基础上提供了更多因地制宜的定制化功能,比如上面的 dyups 已经合并到了 tengine 的代码仓库。
- nginx 的源码是真的多啊…………