一次 nginx 服务停止不当引起的问题

后端存储 Alex

昨天下午的时候,线上一台机器的一个 ngx_lua 服务突然出现了一波 HTTP 500,从错误日志打印的堆栈来看,是不久前新发布的版本里添加的一个 Lua table 不存在,而有代码向其进行索引导致的。这令人百思不得其解,如果是版本回退导致的,那么为什么使用这个 Lua table 的代码没有被回退,偏偏定义这个 table 的代码被回退了呢?

后来运维老大说该机器有重启过,之后主管去查看了该服务的启动和关闭脚本,并注意到不久前新发布的版本有过 nginx 二进制的热更新,值得一提的是,旧的 nginx master 进程在当时还运行着,没有退出。

之后排查到问题是由于关闭 nginx 的操作不当导致的。关闭脚本只是简单的使用了下面这条命令:

/path/to/nginx -s stop

这直接导致业务出现了问题。

场景复现

下面我将使用一个原生的 nginx,在我的安装了 fedora26 的虚拟机上复现这个过程,我使用的 nginx 版本是目前最新的 1.13.4

首先启动 nginx

# [email protected]: ~/bin_install/nginx
ζ ./sbin/nginx
# [email protected]: ~/bin_install/nginx
ζ ps auxf | grep nginx
alex      6174  0.0  0.0  28876   428 ?        Ss   14:35   0:00 nginx: master process ./sbin/nginx
alex      6175  0.0  0.2  29364  2060 ?        S    14:35   0:00  _ nginx: worker process

可以看到 master 和 worker 都已经在运行。

接着我们向 master 发送一个 SIGUSR2
信号,当 nginx 核心收到这个信号后,就会触发热更新。需要注意的是,当热更新完成之后, nginx.pid
文件里记录将是最新的 master 进程的 pid,也就是这里的 6209 进程,旧 master 的 pid 文件则被移到了 nginx.pid.oldbin

# [email protected]: ~/bin_install/nginx                                                                                                                                                                (14:35:15)
ζ kill -USR2 6174
# [email protected]: ~/bin_install/nginx
ζ ps auxf | grep nginx
alex      6174  0.0  0.1  28876  1996 ?        Ss   14:35   0:00 nginx: master process ./sbin/nginx
alex      6175  0.0  0.2  29364  2060 ?        S    14:35   0:00  _ nginx: worker process
alex      6209  0.0  0.2  28876  2804 ?        S    14:37   0:00  _ nginx: master process ./sbin/nginx
alex      6213  0.0  0.1  29364  2004 ?        S    14:37   0:00      _ nginx: worker process
alex      6214  0.0  0.1  29060  1800 ?        S    14:37   0:00

可以看到新的 master 和该 master fork 出来的 worker 已经在运行了,此时我们接着向旧 master 发送一个 SIGWINCH
信号,旧 master 收到这个信号后,会向它的 worker 发送 SIGQUIT
,于是旧 master 的 worker 进程就会退出:

# [email protected]: ~/bin_install/nginx
ζ kill -WINCH 6174
# [email protected]: ~/bin_install/nginx
ζ ps auxf | grep nginx
alex      6174  0.0  0.1  28876  1996 ?        Ss   14:35   0:00 nginx: master process ./sbin/nginx
alex      6209  0.0  0.2  28876  2804 ?        S    14:37   0:00  _ nginx: master process ./sbin/nginx
alex      6213  0.0  0.1  29364  2004 ?        S    14:37   0:00      _ nginx: worker process

此时只剩下旧的 master、新的 master 和新 master 的 worker 在运行,这和当时线上运行的情况类似。

接着我们使用 stop 命令:

# [email protected]: ~/bin_install/nginx                                                                                                                                                                
ζ ./sbin/nginx -s stop
# [email protected]: ~/bin_install/nginx                                                                                                                                                               
ζ ps auxf | grep nginx
alex      6174  0.0  0.1  28876  1996 ?        Ss   14:35   0:00 nginx: master process ./sbin/nginx
alex      6301  0.0  0.2  29364  2124 ?        S    14:49   0:00  _ nginx: worker process

我们发现,新的 master 和它的 worker 都已经退出,而旧的 master 还在运行,并产生了 worker 出来。这就是当时线上的情况了。

原因

更不巧的是,我们上面提到的这个 Lua table,定义它的 Lua 文件早在 init_by_lua 的时候,就已经被 LuaJIT 加载到内存并编译成字节码了,那么显然旧的 master 必然没有这个 Lua table 的信息,因为它加载那部分 Lua 代码是旧的。

而索引该 table 的 Lua 代码并没有在 init_by_lua 的时候使用到,这些代码都是在 worker 进程里被加载起来的,当然这时候项目目录里的代码都是最新的,所以 worker 加载的都是最新的代码,而当时那台机器流量也并没有切干净,于是就出现了一些 Lua 运行时错误,外部表现则是对应的 HTTP 500。

吸收了这个教训之后,我们意识到需要更加合理地关闭我们的 nginx 服务。我们总是应该先查看下是否有 nginx.pid.oldbin
这个文件,或者总是应该判断下 master 进程的父进程是否是 init 进程,如果旧 master 还存在,我们应该向旧 master 发送 SIGQUIT
信号让它退出,确保它退出后,再使用 nginx -s stop
,此时服务便可安全地退出了。当然,我们再做这些操作前,更应该确保这台机器的流量已经被切干净。

Alex稿源:Alex (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 后端存储 » 一次 nginx 服务停止不当引起的问题

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录