nginx常用的平滑升级指令
kill -USR2 <master_pid>
kill -WINCH <master_pid>
kill -QUIT <master_pid>
本文主要介绍当master进程接收到上述信号量时,执行了哪些操作,完成了nginx的平滑升级
信号量注册
上述的操作均使用kill指令,通过向进程发送信号量来通知master进程进行处理,在linux操作系统中,定义了很多种信号量,例如我们会经常使用kill -9 pid暴力杀死某个异常的进程,使用kill -l可以查看linux自带的信号量。
一旦有信号产生,进程会对信号存在下面几种处理方式
-
执行默认操作。操作系统对每种信号都规定了默认操作,例如kill -9 <pid>,当进程接受到该信号时,会强制终止并退出。
-
执行自定义操作。对于某些信号,如果不想让进程执行默认操作,linux提供了函数sigaction,用来对特定的信号注册相应的信号处理函数,当进程捕捉到信号时,会执行对应的回调函数。
-
忽略信号。当不希望处理某些信号的时候,可以忽略该信号
针对上面三种信号量,nginx通过调用sigaction自定义了信号量处理方式,回调函数为ngx_signal_handler
#define NGX_SHUTDOWN_SIGNAL QUIT
#define NGX_TERMINATE_SIGNAL TERM
#define NGX_NOACCEPT_SIGNAL WINCH
#define NGX_RECONFIGURE_SIGNAL HUP
#if (NGX_LINUXTHREADS)
#define NGX_REOPEN_SIGNAL INFO
#define NGX_CHANGEBIN_SIGNAL XCPU
#else
#define NGX_REOPEN_SIGNAL USR1
#define NGX_CHANGEBIN_SIGNAL USR2
#endif
ngx_signal_t signals[] = {
{ ngx_signal_value(NGX_RECONFIGURE_SIGNAL),
"SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),
"reload",
ngx_signal_handler },
{ ngx_signal_value(NGX_REOPEN_SIGNAL),
"SIG" ngx_value(NGX_REOPEN_SIGNAL),
"reopen",
ngx_signal_handler },
// WINCH
{ ngx_signal_value(NGX_NOACCEPT_SIGNAL),
"SIG" ngx_value(NGX_NOACCEPT_SIGNAL),
"",
ngx_signal_handler },
{ ngx_signal_value(NGX_TERMINATE_SIGNAL),
"SIG" ngx_value(NGX_TERMINATE_SIGNAL),
"stop",
ngx_signal_handler },
// QUIT
{ ngx_signal_value(NGX_SHUTDOWN_SIGNAL),
"SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),
"quit",
ngx_signal_handler },
// US2回调函数
{ ngx_signal_value(NGX_CHANGEBIN_SIGNAL),
"SIG" ngx_value(NGX_CHANGEBIN_SIGNAL),
"",
ngx_signal_handler },
{ SIGALRM, "SIGALRM", "", ngx_signal_handler },
{ SIGINT, "SIGINT", "", ngx_signal_handler },
{ SIGIO, "SIGIO", "", ngx_signal_handler },
{ SIGCHLD, "SIGCHLD", "", ngx_signal_handler },
{ SIGSYS, "SIGSYS, SIG_IGN", "", NULL },
{ SIGPIPE, "SIGPIPE, SIG_IGN", "", NULL },
{ 0, NULL, "", NULL }
};
信号量注册
ngx_int_t
ngx_init_signals(ngx_log_t *log)
{
ngx_signal_t *sig;
struct sigaction sa;
for (sig = signals; sig->signo != 0; sig++) {
ngx_memzero(&sa, sizeof(struct sigaction));
if (sig->handler) {
sa.sa_sigaction = sig->handler; // 回调函数
sa.sa_flags = SA_SIGINFO;
} else {
sa.sa_handler = SIG_IGN;
}
sigemptyset(&sa.sa_mask);
// 注册信号量回调函数
if (sigaction(sig->signo, &sa, NULL) == -1) {
return NGX_ERROR;
}
}
return NGX_OK;
}
信号处理
ngx_signal_handler回调函数是一个通用信号处理函数,内部针对不同的信号量进行了不同的处理,重点关注上面三种信号量的处理,其余信号量在这里不再叙述,感兴趣的可以自行走读源码
static void
ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
{
char *action;
ngx_int_t ignore;
ngx_err_t err;
ngx_signal_t *sig;
ignore = 0;
err = ngx_errno;
for (sig = signals; sig->signo != 0; sig++) {
if (sig->signo == signo) {
break;
}
}
ngx_time_sigsafe_update();
action = "";
switch (ngx_process) {
case NGX_PROCESS_MASTER:
case NGX_PROCESS_SINGLE:
switch (signo) {
case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
ngx_quit = 1;
action = ", shutting down";
break;
case ngx_signal_value(NGX_TERMINATE_SIGNAL):
case SIGINT:
ngx_terminate = 1;
action = ", exiting";
break;
case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
if (ngx_daemonized) {
ngx_noaccept = 1;
action = ", stop accepting connections";
}
break;
case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
if (ngx_process == NGX_PROCESS_SINGLE) {
ngx_terminate = 1;
action = ", exiting";
} else {
ngx_reconfigure = 1;
action = ", reconfiguring";
}
break;
case ngx_signal_value(NGX_REOPEN_SIGNAL):
ngx_reopen = 1;
action = ", reopening logs";
break;
case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
if (ngx_getppid() == ngx_parent || ngx_new_binary > 0) {
/*
* Ignore the signal in the new binary if its parent is
* not changed, i.e. the old binary's process is still
* running. Or ignore the signal in the old binary's
* process if the new binary's process is already running.
*/
action = ", ignoring";
ignore = 1;
break;
}
ngx_change_binary = 1;
action = ", changing binary";
break;
case SIGALRM:
ngx_sigalrm = 1;
break;
case SIGIO:
ngx_sigio = 1;
break;
case SIGCHLD:
ngx_reap = 1;
break;
}
break;
case NGX_PROCESS_WORKER:
case NGX_PROCESS_HELPER:
switch (signo) {
case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
if (!ngx_daemonized) {
break;
}
ngx_debug_quit = 1;
/* fall through */
case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
ngx_quit = 1;
action = ", shutting down";
break;
case ngx_signal_value(NGX_TERMINATE_SIGNAL):
case SIGINT:
ngx_terminate = 1;
action = ", exiting";
break;
case ngx_signal_value(NGX_REOPEN_SIGNAL):
ngx_reopen = 1;
action = ", reopening logs";
break;
case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
case SIGIO:
action = ", ignoring";
break;
}
break;
}
if (siginfo && siginfo->si_pid) {
ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
"signal %d (%s) received from %P%s",
signo, sig->signame, siginfo->si_pid, action);
} else {
ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
"signal %d (%s) received%s",
signo, sig->signame, action);
}
if (ignore) {
ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, 0,
"the changing binary signal is ignored: "
"you should shutdown or terminate "
"before either old or new binary's process");
}
if (signo == SIGCHLD) {
ngx_process_get_status();
}
ngx_set_errno(err);
}
当接受到USR2信号量时,子进程不做处理,master进程会将ngx_change_binary这个变量置为1
case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
// 子进程不做处理 或者 ngx_new_binary 大于0不处理
if (ngx_getppid() == ngx_parent || ngx_new_binary > 0) {
/*
* Ignore the signal in the new binary if its parent is
* not changed, i.e. the old binary's process is still
* running. Or ignore the signal in the old binary's
* process if the new binary's process is already running.
*/
action = ", ignoring";
ignore = 1;
break;
}
// master进程处理逻辑
ngx_change_binary = 1;
action = ", changing binary";
break;
当接受到WINCH信号量,将 ngx_noaccept置为1
case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
if (ngx_daemonized) {
ngx_noaccept = 1;
action = ", stop accepting connections";
}
break;
当接受QUIT信号量时,将ngx_quit置为1
case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
ngx_quit = 1;
action = ", shutting down";
break;
nginx master的循环任务中,会根据上述变量的变化,执行对应的操作。
master平滑升级流程
nginx的master进程启动后,会进入ngx_master_process_cycle函数,该函数首先会进行一系列初始化操作,创建work进程,设置信号处理机制等,最后进入循环状态,不断检查和处理各种事件。
void
ngx_master_process_cycle(ngx_cycle_t *cycle)
{
...
ngx_new_binary = 0;
delay = 0;
sigio = 0;
live = 1;
// 主进程循环任务
for ( ;; ) {
if (delay) {
if (ngx_sigalrm) {
sigio = 0;
delay *= 2;
ngx_sigalrm = 0;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"termination cycle: %M", delay);
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 0;
itv.it_value.tv_sec = delay / 1000;
itv.it_value.tv_usec = (delay % 1000 ) * 1000;
if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setitimer() failed");
}
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
sigsuspend(&set);
ngx_time_update();
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"wake up, sigio %i", sigio);
if (ngx_reap) {
ngx_reap = 0;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
live = ngx_reap_children(cycle);
}
if (!live && (ngx_terminate || ngx_quit)) {
ngx_master_process_exit(cycle);
}
if (ngx_terminate) {
if (delay == 0) {
delay = 50;
}
if (sigio) {
sigio--;
continue;
}
sigio = ccf->worker_processes + 2 /* cache processes */;
if (delay > 1000) {
ngx_signal_worker_processes(cycle, SIGKILL);
} else {
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_TERMINATE_SIGNAL));
}
continue;
}
if (ngx_quit) {
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
ngx_close_listening_sockets(cycle);
continue;
}
......
if (ngx_change_binary) {
ngx_change_binary = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
}
if (ngx_noaccept) {
ngx_noaccept = 0;
ngx_noaccepting = 1;
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
}
}
}
当master进程接收到USR2信号量时,信号回调函数会将ngx_change_binary设置为1。在master循环任务中,ngx_exec_new_binary为1时,会调用ngx_exec_new_binary函数,在该函数内,会基于新的二进制文件创建一个新的master进程,同时新的master进程也会创建新的work进程,新的work进程会承担原先的work职责,监听客户端请求
if (ngx_change_binary) {
ngx_change_binary = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
}
随后,old master进程接收到WINCH信号量,回调函数将ngx_noaccept置为1。在old master的循环任务中,当检测到ngx_noaccept为1时,会向master的子进程发送NGX_SHUTDOWN_SIGNAL信号量(new master进程除外),work子进程收到NGX_SHUTDOWN_SIGNAL信号量后,结束进程任务
if (ngx_noaccept) {
ngx_noaccept = 0;
ngx_noaccepting = 1;
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
}
master进程通过执行ngx_signal_worker_processes向work进程发送信号量
static void
ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo)
{
ngx_int_t i;
ngx_err_t err;
ngx_channel_t ch;
ngx_memzero(&ch, sizeof(ngx_channel_t));
switch (signo) {
case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
ch.command = NGX_CMD_QUIT;
break;
case ngx_signal_value(NGX_TERMINATE_SIGNAL):
ch.command = NGX_CMD_TERMINATE;
break;
case ngx_signal_value(NGX_REOPEN_SIGNAL):
ch.command = NGX_CMD_REOPEN;
break;
default:
ch.command = 0;
}
#endif
ch.fd = -1;
for (i = 0; i < ngx_last_process; i++) {
// new master的detached为1,old master 不会向new master发送kill信号量
if (ngx_processes[i].detached || ngx_processes[i].pid == -1) {
continue;
}
if (ngx_processes[i].just_spawn) {
ngx_processes[i].just_spawn = 0;
continue;
}
if (ngx_processes[i].exiting
&& signo == ngx_signal_value(NGX_SHUTDOWN_SIGNAL))
{
continue;
}
if (ch.command) {
if (ngx_write_channel(ngx_processes[i].channel[0],
&ch, sizeof(ngx_channel_t), cycle->log)
== NGX_OK)
{
if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
ngx_processes[i].exiting = 1;
}
continue;
}
}
// master进程向work进程发送NGX_SHUTDOWN_SIGNAL信号量,通知子进程退出
if (kill(ngx_processes[i].pid, signo) == -1) {
err = ngx_errno;
ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
"kill(%P, %d) failed", ngx_processes[i].pid, signo);
if (err == NGX_ESRCH) {
ngx_processes[i].exited = 1;
ngx_processes[i].exiting = 0;
ngx_reap = 1;
}
continue;
}
if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
ngx_processes[i].exiting = 1;
}
}
}
子进程退出后会向父进程发送SIGCHLD信号量通知父进程退出,父进程调用waitpid获取子进程的状态,当work进程(除new master进程外)全部退出后,live变量置为0
最后,向old master进程发送QUIT信号量,将ngx_quit变量置为1,当ngx_quit为1且无子进程存活(live = 0)后,执行ngx_master_process_exit,老master进程退出
if (!live && (ngx_terminate || ngx_quit)) {
ngx_master_process_exit(cycle);
}