nginx多进程模型之热代码平滑升级

来源:互联网 发布:淘宝赔付基金赔付规则 编辑:程序博客网 时间:2024/06/05 15:12

平滑升级的主要过程包括一下步骤:

 

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。

 

[cpp] view plaincopyprint?
  1. // ngx_process这个变量记录了接收到信号的进程类型,这里很明显是master process(因为通过master的pid发出的信号)  
  2. switch (ngx_process) {  
  3.   
  4.     case NGX_PROCESS_MASTER:  
  5.     case NGX_PROCESS_SINGLE:  
  6.         switch (signo) {  
  7.     …  
  8.         case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):  
  9.             /* 这里给出了详细的注释,更通俗一点来讲,就是说,进程现在是一个 
  10.          * master(新的master进程),但是当他的父进程old master还在运行的话, 
  11.              * 这时收到了USR2信号,我们就忽略它,不然就成了新master里又要生成 
  12.              * master。。。另外一种情况就是,old master已经开始了生成新master的过程 
  13.              * 中,这时如果又有USR2信号到来,那么也要忽略掉。。。(不知道够不够通俗=.=) 
  14.              */  
  15.             if (getppid() > 1 || ngx_new_binary > 0) {  
  16.   
  17.                 /* 
  18.                  * Ignore the signal in the new binary if its parent is 
  19.                  * not the init process, i.e. the old binary's process 
  20.                  * is still running.  Or ignore the signal in the old binary's 
  21.                  * process if the new binary's process is already running. 
  22.                  */  
  23.   
  24.                 action = ", ignoring";  
  25.                 ignore = 1;  
  26.                 break;  
  27.             }  
  28.             // 最重要的操作在这里,通过将ngx_change_binary置为1,来告诉master  
  29.             // 应该去启动一个新的实例了,也就是说,master里面在检测到这个变量为// 1时,就会做新nginx实例的生成工作了  
  30.             ngx_change_binary = 1;  
  31.             action = ", changing binary";  
  32.             break;      …  
  33.        }  
  34. }  

 

新实例的产生在ngx_master_process_cycle函数里处理,这个函数大家应该很熟悉,它主要是在一个for循环里来监控各种信号和状态,来控制系统和工作进程。

[cpp] view plaincopyprint?
  1. for ( ;; ) {  
  2.   
  3.     …  
  4.   
  5.     //上面已经提过,USR2的信号处理里面,会将ngx_change_binary置为1,这样在master循环里,就可以做处理了。  
  6.   
  7.     if(ngx_change_binary) {  
  8.   
  9.         ngx_change_binary = 0;  
  10.   
  11.         ngx_new_binary = ngx_exec_new_binary(cycle,ngx_argv);  
  12.   
  13.     }  
  14.     …  
  15. }  

       在函数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信号将旧的进程来关闭。涉及的流程如下:

[cpp] view plaincopyprint?
  1. // 收到SIGCHLD,意味着子进程(即worker 进程),会将ngx_reap置1,这样在master中可以  
[cpp] view plaincopyprint?
  1. // 检测到这个变量的值,让后重启子进程。。。  
  2.   
  3. if (ngx_reap) {  
  4.   
  5.     ngx_reap = 0;  
  6.   
  7.     live = ngx_reap_children(cycle);  
  8.   
  9. }  
  10.   
  11. // live为1表示有子进程在运行,为0代表,所有子进程都退出了,这是如果有退出之类的// 信号,那么就直接exit退出了。  
  12.   
  13. if (!live && (ngx_terminate ||ngx_quit)) {  
  14.   
  15.     ngx_master_process_exit(cycle);  
  16.   
  17. }  
  18.   
  19. …  
  20.   
  21. // 正式处理QUIT信号  
  22.   
  23. if (ngx_quit) {  
  24.   
  25.     //通知worker进程,处理完自己的事务之后自动退出。  
  26.   
  27.     ngx_signal_worker_processes(cycle,ngx_signal_value(NGX_SHUTDOWN_SIGNAL));  
  28.   
  29.     // 之后的处理就是关闭listenfd,不在监听连接。  
  30.     …  
  31. }  

       这里简单说下worker进程在接收到父进程通知的NGX_SHUTDOWN_SIGNAL会,将ngx_quit置1,当然对于ngx_quit的处理,在worker和master是不一样的,需要区分,这也就是ngx_process类型的作用啦。后面worker进程会停止关闭继承的listenfd,同时accpet新连接的行为也消失了,然后呢,进程会等到已经”hold”的连接全部处理之后再退出,做到善始善终!

 

0 0