文章目录
- 配置热更新流程
- main
- ngx_get_options
- main
- ngx_signal_process
- ngx_os_signal_process
- master 进程处理 SIGHUP 信号
- ngx_signal_worker_processes
- ngx_channel_handler
- ngx_worker_process_cycle
- ngx_worker_process_exit
- master 进程处理 worker 进程退出后产生的 SIGCHLD 信号
配置热更新流程
当修改了 nginx 的配置文件时,执行 nginx -s reload
命令就可以使新的配置生效(业务不会中断),而不需要关闭当前的进程再重新启动。这就是所谓的配置热更新。这一节让我们看看 nginx 是如何实现配置热更新的。
先看看执行 nginx -s reload
命令命令后发生了什么。看这节内容时,需要先看前一节内容 nginx 启动流程。
main
在 main
函数中,会先调用 ngx_get_options
函数处理命令行参数:
if (ngx_get_options(argc, argv) != NGX_OK) {
return 1;
}
ngx_get_options
在 ngx_get_options
函数中,对应的处理 -s 选项的代码如下:
case 's':
if (*p) {
ngx_signal = (char *) p;
} else if (argv[++i]) {
ngx_signal = argv[i];
} else {
ngx_log_stderr(0, "option \"-s\" requires parameter");
return NGX_ERROR;
}
if (ngx_strcmp(ngx_signal, "stop") == 0
|| ngx_strcmp(ngx_signal, "quit") == 0
|| ngx_strcmp(ngx_signal, "reopen") == 0
|| ngx_strcmp(ngx_signal, "reload") == 0)
{
ngx_process = NGX_PROCESS_SIGNALLER;
goto next;
}
ngx_log_stderr(0, "invalid option: \"-s %s\"", ngx_signal);
return NGX_ERROR;
ngx_signal
全局变量指向了 -s 选项后面的字符串,也就是 “reload”,然后下面的 if 语句判断 ngx_signal
是否是 -s 支持的4个参数——“stop”,“quit”,“reopen”,“reload是的”之一, 如果是的话将 ngx_process
全局变量赋值为 NGX_PROCESS_SIGNALLER
。
main
之后,main
函数流程在调用 ngx_init_cycle
函数处理完所有的配置项后,会经过如下的 if 语句:
if (ngx_signal) {
return ngx_signal_process(cycle, ngx_signal);
}
此时 ngx_signal
指向字符串 “reload”,满足 if 条件,main
函数直接调用 ngx_signal_process
函数,然后进程就退出了。
ngx_signal_process
ngx_signal_process
函数从配置文件中获取 nginx 进程 pid 文件路径,然后读取文件里的 pid ,最后调用 ngx_os_signal_process
函数:
ngx_int_t
ngx_signal_process(ngx_cycle_t *cycle, char *sig)
{
...
pid = ngx_atoi(buf, ++n);
if (pid == (ngx_pid_t) NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, cycle->log, 0,
"invalid PID number \"%*s\" in \"%s\"",
n, buf, file.name.data);
return 1;
}
return ngx_os_signal_process(cycle, sig, pid);
}
ngx_os_signal_process
ngx_os_signal_process
函数是与操作系统相关的函数,我们一般都是使用 linux 系统,这个版本的函数代码如下:
ngx_int_t
ngx_os_signal_process(ngx_cycle_t *cycle, char *name, ngx_pid_t pid)
{
ngx_signal_t *sig;
for (sig = signals; sig->signo != 0; sig++) {
if (ngx_strcmp(name, sig->name) == 0) {
if (kill(pid, sig->signo) != -1) {
return 0;
}
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"kill(%P, %d) failed", pid, sig->signo);
}
}
return 1;
}
它将 ngx_signal
的值与全局数组 signals
中各项比较,如果名字相同,就调用 kill
向正在运行的 nginx master 进程发送特定的信号。signals
数组的定义如下:
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 },
{ 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 },
{ ngx_signal_value(NGX_SHUTDOWN_SIGNAL),
"SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),
"quit",
ngx_signal_handler },
{ 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 }
};
我们找到 “reload” 对应的一项如下:
{ ngx_signal_value(NGX_RECONFIGURE_SIGNAL),
"SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),
"reload",
ngx_signal_handler },
这个数组每一项是 ngx_signal_t
结构:
typedef struct {
int signo;
char *signame;
char *name;
void (*handler)(int signo, siginfo_t *siginfo, void *ucontext);
} ngx_signal_t;
signo 是信号的值,signame 是信号的名称,name 是信号对应于 -s 选项的名称,ngx_signal_handler
是进程对这个信号的处理函数。根据以下几个宏的定义:
#define NGX_RECONFIGURE_SIGNAL HUP
#define ngx_signal_helper(n) SIG##n
#define ngx_signal_value(n) ngx_signal_helper(n)
我们就知道了,原来 nginx -s reload 命令做的工作基本上就是对正在运行的 nginx master 进程发送 SIGHUP 信号。
master 进程处理 SIGHUP 信号
master 进程在收到 SIGHUP 信号后,会由信号处理函数 ngx_signal_handler
函数处理。对于这个信号的处理很简单:
case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
ngx_reconfigure = 1; /* SIGHUP */
action = ", reconfiguring";
break;
就是将 ngx_reconfigure
全局变量置 1 。
在处理完信号后,master 进程从 ngx_master_process_cycle
函数中调用 sigsuspend
函数的地方恢复执行,它先调用 ngx_time_update
更新系统时间,然后判断各个全局变量是否置 1,如果 ngx_reconfigure
全局变量的值是 1 ,那么它的处理流程如下:
if (ngx_reconfigure) {
ngx_reconfigure = 0;
if (ngx_new_binary) {
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
ngx_start_cache_manager_processes(cycle, 0);
ngx_start_privileged_agent_processes(cycle, 0);
ngx_noaccepting = 0;
continue;
}
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
cycle = ngx_init_cycle(cycle);
if (cycle == NULL) {
cycle = (ngx_cycle_t *) ngx_cycle;
continue;
}
ngx_cycle = cycle;
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
ngx_core_module);
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_JUST_RESPAWN);
ngx_start_cache_manager_processes(cycle, 1);
ngx_start_privileged_agent_processes(cycle, 1);
/* allow new processes to start */
ngx_msleep(100);
live = 1;
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
}
它先调用 ngx_init_cycle
,ngx_init_cycle
函数中会按新的配置文件处理配置项,若成功则返回一个新的 ngx_cycle_t
结构,我们将它保存在局部变量 cycle 和全局变量 ngx_cycle
中。
接着再调用 ngx_start_worker_processes
函数创建新的 worker 进程,传入的参数是新的 cycle,这样新的 wroker 进程就会以新的配置工作。
最后调用 ngx_signal_worker_processes
通知当前所有的 worker 进程要求它们优雅地退出。
ngx_signal_worker_processes
让我们来看看 master 进程如何通知 worker 进程退出:
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;
}
ch.fd = -1;
for (i = 0; i < ngx_last_process; i++) {
ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"child: %i pid:%P exiting:%d exited:%d detached:%d respawn:%d just_spawn:%d",
i,
ngx_processes[i].pid,
ngx_processes[i].exiting,
ngx_processes[i].exited,
ngx_processes[i].detached,
ngx_processes[i].respawn,
ngx_processes[i].just_spawn);
if (ngx_processes[i].detached || ngx_processes[i].pid == -1) { /* 忽略不是 woker 进程的进程或已退出的 wroker 进程 */
continue;
}
if (ngx_processes[i].just_spawn) { /* 忽略刚启动的 worker 进程 */
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;
}
}
ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"kill (%P, %d)", ngx_processes[i].pid, signo);
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;
}
}
}
上面 ngx_signal_worker_processes
代码很明显地展示了,master 进程是通过管道发送消息给 worker 进程要求它退出的。消息由 ngx_channel_t
结构体表示:
typedef struct {
ngx_uint_t command;
ngx_pid_t pid;
ngx_int_t slot;
ngx_fd_t fd;
} ngx_channel_t;
对于发送要进程退出的消息,则 command 成员的值为 NGX_CMD_QUIT
。
worker 进程收到这个消息后,调用 ngx_channel_handler
函数处理。
ngx_channel_handler
这个函数循环调用 ngx_read_channel
函数读取消息,如果消息中的 command 成员的值为 NGX_CMD_QUIT
则会设置全局变量 ngx_quit
的值为 1 :
case NGX_CMD_QUIT:
ngx_quit = 1;
break;
ngx_worker_process_cycle
上一节内容我们说明 worker 进程在 ngx_worker_process_cycle
函数中循环处理事件:
for ( ;; ) {
if (ngx_exiting) { /* 进程正在退出 */
if (ngx_event_no_timers_left() == NGX_OK) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
ngx_worker_process_exit(cycle);
}
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
ngx_process_events_and_timers(cycle);
if (ngx_terminate) { /* 立即退出进程 */
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
ngx_worker_process_exit(cycle);
}
if (ngx_quit) { /* 等待请求处理结束后再退出,优雅地退出进程 */
ngx_quit = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
"gracefully shutting down");
/* 修改进程名称为 worker process is shutting down */
ngx_setproctitle("worker process is shutting down");
if (!ngx_exiting) {
ngx_exiting = 1; /* 设置进程正在退出标志 */
ngx_set_shutdown_timer(cycle);
ngx_close_listening_sockets(cycle);
ngx_close_idle_connections(cycle);
}
}
if (ngx_reopen) {
ngx_reopen = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
ngx_reopen_files(cycle, -1);
}
}
当它发现全局变量 ngx_quit
的值为 1 时,就会修改进程名为 “worker process is shutting down”,然后设置 ngx_exiting
的值为 1,并调用 ngx_set_shutdown_timer
函数设置一个定时器。这个函数的代码如下:
void
ngx_set_shutdown_timer(ngx_cycle_t *cycle)
{
ngx_core_conf_t *ccf;
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
if (ccf->shutdown_timeout) {
ngx_shutdown_event.handler = ngx_shutdown_timer_handler;
ngx_shutdown_event.data = cycle;
ngx_shutdown_event.log = cycle->log;
ngx_shutdown_event.cancelable = 1;
ngx_add_timer(&ngx_shutdown_event, ccf->shutdown_timeout);
}
}
这个定时器的值由配置项 worker_shutdown_timeout 决定,默认无这个配置项,也就不会启用这个定时器。如果使用了这个配置了项,则当定时器超时时,它的处理函数 ngx_shutdown_timer_handler
会强制断开所有的连接。
然后调用 ngx_close_listening_sockets
将所有的 listening socket 描述符从 epoll 中移除,并关闭 socket 。这样,woker 进程就不会处理所有的新建连接请求。它的代码如下:
/* 被 ngx_worker_process_cycle 等函数调用*/
void
ngx_close_listening_sockets(ngx_cycle_t *cycle)
{
ngx_uint_t i;
ngx_listening_t *ls;
ngx_connection_t *c;
if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
return;
}
ngx_accept_mutex_held = 0;
ngx_use_accept_mutex = 0;
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
c = ls[i].connection;
if (c) {
if (c->read->active) {
if (ngx_event_flags & NGX_USE_EPOLL_EVENT) {
/*
* it seems that Linux-2.6.x OpenVZ sends events
* for closed shared listening sockets unless
* the events was explicitly deleted
*/
ngx_del_event(c->read, NGX_READ_EVENT, 0);
} else {
ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT);
}
}
ngx_free_connection(c);
c->fd = (ngx_socket_t) -1;
}
ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"close listening %V #%d ", &ls[i].addr_text, ls[i].fd);
if (ngx_close_socket(ls[i].fd) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
ngx_close_socket_n " %V failed", &ls[i].addr_text);
}
#if (NGX_HAVE_UNIX_DOMAIN)
if (ls[i].sockaddr->sa_family == AF_UNIX
&& ngx_process <= NGX_PROCESS_MASTER
&& ngx_new_binary == 0)
{
u_char *name = ls[i].addr_text.data + sizeof("unix:") - 1;
if (ngx_delete_file(name) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
ngx_delete_file_n " %s failed", name);
}
}
#endif
ls[i].fd = (ngx_socket_t) -1;
}
cycle->listening.nelts = 0;
}
然后调用 ngx_close_idle_connections
关闭空闲的连接。空闲的连接是指这个连接上没有正在处理的 http 请求。它的代码如下:
void
ngx_close_idle_connections(ngx_cycle_t *cycle)
{
ngx_uint_t i;
ngx_connection_t *c;
c = cycle->connections;
for (i = 0; i < cycle->connection_n; i++) {
/* THREAD: lock */
if (c[i].fd != (ngx_socket_t) -1 && c[i].idle) {
c[i].close = 1; /* 置上 close 标志,read event handler 里会关闭连接 */
c[i].read->handler(c[i].read);
}
}
}
接着在 for 循环开始处,由于 ngx_exiting
已被置 1 ,将执行 ngx_event_no_timers_left
函数。这个函数只有当前所有的定时器是可取消的才返回 NGX_OK
。这意味,如果有活跃的连接,那么它不会返回 NGX_OK
,这样 worker 进程就不会退出,直到所有的连接正常关闭。如果活跃的连接数很多,那么旧的 worker 进程就会占用大量的内存。因此才有 worker_shutdown_timeout 配置项,使得 worker 进程超时后能够强制退出。当它返回 NGX_OK
时,会调用 ngx_worker_process_exit
函数。
ngx_worker_process_exit
ngx_worker_process_exit
函数先调用各个模块的 exit_process 回调函数,然后调用 exit
系统调用退出进程:
static void
ngx_worker_process_exit(ngx_cycle_t *cycle)
{
ngx_uint_t i;
ngx_connection_t *c;
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->exit_process) {
cycle->modules[i]->exit_process(cycle);
}
}
if (ngx_exiting) {
c = cycle->connections;
for (i = 0; i < cycle->connection_n; i++) {
if (c[i].fd != -1
&& c[i].read
&& !c[i].read->accept
&& !c[i].read->channel
&& !c[i].read->resolver)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"*%uA open socket #%d left in connection %ui",
c[i].number, c[i].fd, i);
ngx_debug_quit = 1;
}
}
if (ngx_debug_quit) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "aborting");
ngx_debug_point();
}
}
/*
* Copy ngx_cycle->log related data to the special static exit cycle,
* log, and log file structures enough to allow a signal handler to log.
* The handler may be called when standard ngx_cycle->log allocated from
* ngx_cycle->pool is already destroyed.
*/
ngx_exit_log = *ngx_log_get_file_log(ngx_cycle->log);
ngx_exit_log_file.fd = ngx_exit_log.file->fd;
ngx_exit_log.file = &ngx_exit_log_file;
ngx_exit_log.next = NULL;
ngx_exit_log.writer = NULL;
ngx_exit_cycle.log = &ngx_exit_log;
ngx_exit_cycle.files = ngx_cycle->files;
ngx_exit_cycle.files_n = ngx_cycle->files_n;
ngx_cycle = &ngx_exit_cycle;
ngx_destroy_pool(cycle->pool);
ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, "exit");
exit(0);
}
master 进程处理 worker 进程退出后产生的 SIGCHLD 信号
当 worker 进程退出后,master 进程会收到 SIGCHLD
信号,信号处理函数 ngx_signal_handler
中会将 ngx_reap
全局变量置 1 ,然后调用 ngx_process_get_status
函数:
static void
ngx_process_get_status(void)
{
int status;
char *process;
ngx_pid_t pid;
ngx_err_t err;
ngx_int_t i;
ngx_uint_t one;
one = 0;
for ( ;; ) {
pid = waitpid(-1, &status, WNOHANG);
if (pid == 0) { /* 有可能 worker 进程被暂停 */
return;
}
if (pid == -1) {
err = ngx_errno;
if (err == NGX_EINTR) {
continue;
}
if (err == NGX_ECHILD && one) {
return;
}
/*
* Solaris always calls the signal handler for each exited process
* despite waitpid() may be already called for this process.
*
* When several processes exit at the same time FreeBSD may
* erroneously call the signal handler for exited process
* despite waitpid() may be already called for this process.
*/
if (err == NGX_ECHILD) {
ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, err,
"waitpid() failed");
return;
}
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,
"waitpid() failed");
return;
}
one = 1;
process = "unknown process";
/* 更新已退出的进程状态 */
for (i = 0; i < ngx_last_process; i++) {
if (ngx_processes[i].pid == pid) {
ngx_processes[i].status = status;
ngx_processes[i].exited = 1; /* 进程已经退出 */
process = ngx_processes[i].name;
break;
}
}
if (WTERMSIG(status)) {
#ifdef WCOREDUMP
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
"%s %P exited on signal %d%s",
process, pid, WTERMSIG(status),
WCOREDUMP(status) ? " (core dumped)" : "");
#else
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
"%s %P exited on signal %d",
process, pid, WTERMSIG(status));
#endif
} else {
ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
"%s %P exited with code %d",
process, pid, WEXITSTATUS(status));
}
if (WEXITSTATUS(status) == 2 && ngx_processes[i].respawn) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
"%s %P exited with fatal code %d "
"and cannot be respawned",
process, pid, WEXITSTATUS(status));
ngx_processes[i].respawn = 0;
}
ngx_unlock_mutexes(pid);
}
}
这个函数获取 worker 进程退出时的状态,将保存该进程状态的 ngx_process_t
结构中的 exited 标志置 1 ,表示该槽位所在的进程已经退出。
处理完信号后,又回到 ngx_master_process_cycle
函数中。
if (ngx_reap) { /* 处理 worker 进程退出 */
ngx_reap = 0;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
live = ngx_reap_children(cycle);
}
在知道 ngx_reap
置 1 时,它调用 ngx_reap_children
函数。
这个函数主要就是通知其他的 worker 进程关闭已退出的 worker 进程的管道描述符。并将保存该进程状态的 ngx_process_t
结构中的 pid 赋值为 -1 ,表明这个结构现在没有 worker 进程占用。下次创建新的 worker 进程就会重用这个结构。
至此,整个配置热更新流程结束。