平滑升级的主要过程包括一下步骤:
1. 新代码make执行完后,这里不用在make install 了,接下来重名/sbin/nginx为nginx.old
# mv /usr/local/nginx/sbin/nginx/usr/local/nginx/sbin/nginx.old
2. 复制编译后objs目录下的nginx文件到nginx的安装目录sbin/下
# cp objs/nginx /usr/local/nginx/sbin/
3. 让nginx把nginx.pid文件修改成nginx.pid.oldbin,随即启动nginx,实现不间断
# kill -USR2 `cat/usr/local/nginx/nginx.pid`
# kill -QUIT `cat/usr/local/nginx/nginx.pid.oldbin`
当然,官方上有个更加详细的过程,包括了取消升级、新旧代码同时保留等操作,这里我们主要看上述给出简单升级的实现。
http://wiki.nginx.org/ChsCommandLine
这里我们主要看步骤3:
nginx里面讲USR2信号定义成NGX_CHANGEBIN_SIGNAL。
#define NGX_CHANGEBIN_SIGNAL USR2
它的处理函数,跟其他的一样信号一样,使用了ngx_signal_handler。这个函数在配置热加载的那篇文章里已经接触过了,我们在执行kill -USR2 `cat /usr/local/nginx/nginx.pid`时,master进程会得到信号,因为pid文件中记录了master进程的pid。
新实例的产生在ngx_master_process_cycle函数里处理,这个函数大家应该很熟悉,它主要是在一个for循环里来监控各种信号和状态,来控制系统和工作进程。
在函数ngx_exec_new_binary里面,主要的工作就是将listen的fd,放到环境变量中,格式为:NGINX=fd1;fd2;fd3……(这个后面讲!),然后将nginx.pid改成为nginx.pid.oldbin,最后就是通过ngx_execute,其实就是execve来启动新实例。。。
这里有个地方要提一下,就是上面提到的listen fd,它被放到了环境变量中,目的就是在新实例启动的过程中,通过ngx_add_inherited_sockets来拿到之前的listen fd,重新使用,之所以要这样,是因为新的实例是通过fork+execve来产生,不是跟简单fork之后子进程那样直接就可以拿到这个父进程的listen fd来用。
在步骤3中,我们通过发送QUIT信号将旧的进程来关闭。涉及的流程如下:
这里简单说下worker进程在接收到父进程通知的NGX_SHUTDOWN_SIGNAL会,将ngx_quit置1,当然对于ngx_quit的处理,在worker和master是不一样的,需要区分,这也就是ngx_process类型的作用啦。后面worker进程会停止关闭继承的listenfd,同时accpet新连接的行为也消失了,然后呢,进程会等到已经”hold”的连接全部处理之后再退出,做到善始善终!