nginx的进程模型
2012-09-14 15:55
429 查看
nginx的进程模式
1 进程间的通信方式
使用 UNIX域套接字 socketpair() 异步通讯机制: nginx在创建worker前将先调用 socketpair(int channel[2]) 然后将 channel[0-1]设置为异步通知方式,并注册evnet事件,父进程使用channel[0],子进程使用channel[1]实现双方的通讯.
1.1 创建子进程时用到的标示
2 都有哪些通信?
2.1 创建worker进程时
2.2 执行 nginx -s reload 时
2.3 执行 nginx -s stop 时
2.4 执行 nginx -s quit 时
2.5 执行 nginx -s reopen 时
2.6 某worker异常退出时
2.7 概要
SIGHUP NGX_CMD_QUIT|SIGQUIT
./nginx -s reload --------------> master (ngx_reload=1) -------------------------> worker|cache_manager|cache_loader (ngx_quit=1, 处理完待读socket再退出)
SIGTERM|SIGINT NGX_CMD_TERMINATE|SIGTERM
./nginx -s stop -----------------------> master (ngx_terminate=1,延迟退出时间) ----------------------------> worker|cache_manager|cache_loader (ngx_terminate=1,直接退出)
SIGQUIT NGX_CMD_QUIT|SIGQUIT
./nginx -s quit --------------> master (ngx_quit=1) ------------------------> worker|cache_manager|cache_loader (ngx_quit=1, 处理完待读socket再退出)
SIGUSER1 NGX_CMD_REOPEN|SIGUSER1
./nginx -s reopen -----------------> master (ngx_reopen=1) -------------------------> worker|cache_manager|cache_loader (ngx_reopen=1)
3 通信的内容是什么?
4 创建worker进程时 是如何通信的?
master(父进程):
1> 在进程池 ngx_processes 中为 新worker进程 分配一个位置,如果进程池已满,也就是创建的进程数超过了最大值1024 nginx报错但不退出;
2> 调用 socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) 创建UNIX域套接字对.并设置为异步模式且execve时关闭;
3> 将 新worker进程 的{NGX_CMD_OPEN_CHANNEL, channel[0], 在进程池中的位置, PID} 通过 UNIX域套接字 告知已存在的子进程.
新worker进程:
1> 关闭进程池中其他子进程的 channel[1];
2> 关闭自身的 channel[0];
3> 将自身的 channel[1] 注册event读事件: ngx_channel_handler().
自身可以通过 进程池中其他子进程的 channel[0] 收发 其他子进程 的信息.
自身可以通过 自身的 channel[1] 收发 master进程 的信息.
其他子进程:
此时 其他子进程的 UNIX域套接字 收到 master进程的 {NGX_CMD_OPEN_CHANNEL, channel[0], 在进程池中的位置, PID}; 读事件处理函数 ngx_channel_handler()被调用;
1> 获得 新子进程 在进程池中的位置;
2> 保存 新子进程 的 channel[0];
2> 保存 新子进程 的 PID.
5 执行 nginx -s reload 时
执行 nginx -s reload 的进程:
从main函数开始一直执行到
master:
1> 接收到 reload 信号, 信号处理函数 ngx_signal_handler 中将 ngx_reconfigure=1;
2> 进入 ngx_reconfigure 状态; 调用 ngx_init_cycle() 初始化环境,重新加载配置文件, 重新创建 worker进程, cache管理进程, cache加载进程(热代码忽略);
3> 使用 UNIX域套接字 通知老的worker进程, cache管理进程(cache加载进程此时可能存在,也可能已经退出,因为在ngx_cache_loader_process_handler() {exit(0)}), NGX_CMD_QUIT;
这里不通知的进程有: 刚刚创建的worker进程, cache管理进程, cache加载进程, 正在退出中且本次发送信号为 shutdown的进程, 热代码加载中的进程.
4> 如果使用 UNIX域套接字发送 NGX_CMD_QUIT 失败 ,则使用 kill 发送 SIGQUIT, 将这些进程的属性 exiting=1; 如果发送信号时某进程已经退出,则将其属性 exited=1;
5> 过N久之后, 这些被发送信号的进程会退出, master进程会收到内核发送来的 SIGCHLD,将 ngx_reap=1; 并调用 ngx_process_get_status()回收退出的子进程;
6> 回收子进程 ngx_process_get_status();
7> reload状态,也会进入 ngx_reap 状态, 这个状态是否会重新创建 那些老的可再生的子进程 呢?
首先, 重新创建 老的可再生的子进程的条件是:
(ngx_processes[i].exited && ngx_processes[i].respawn && !ngx_processes[i].exiting && !ngx_terminate && !ngx_quit)
reload状态,可以通过观察进程属性 respawn, exiting, exited 的值来判断是否会重新创建 那些老的可再生的子进程. 除了 cache loader 进程的respawn=0, 其他进程 respawn =1. 故只有 exited=1 && exiting=0 时才会重建.
初始值 exiting=0, exited=0;
reload状态, 通知老的子进程退出有三种情况:
<A> 使用 UNIX域套接字 发送 NGX_CMD_QUIT, 发送成功将 exiting=1, 此时 exited 还是 0. [不会重建]
<B> 情况<A> 失败, 使用 kill 发送 SIGQUIT, 发送成功, 此时子进程还未退出, 将 exiting=1, 此时 exited 还是 0. [不会重建]
<C> 如果此时子进程已经退出, master 还未来得及回收,则将 exited=1, exiting=0, ngx_reap=1. [可能重建]
我们来看这种 [可能重建] 的情况.
master的for(;;)逻辑:
如果情况 <C> 出现, 说明子进程已经退出, master还未回收, 等本次 if (ngx_reconfigure) {} 会回到for(;;), sigsuspend();时会收到 SIGCHLD,进入 ngx_signal_handler() ngx_reap=1; 进入 ngx_reap 状态, 此时 exited=1, exiting=0, 会导致:
(1) 这个进程会被创建
(2) 这个老的进程占用的master的进程数组的位置不会释放. 也就是 pid 不会等于1, 而且该进程的属性始终 exited=1, exiting=0,
(3) 由于上面的 exited=1, exiting=0, 所以下次进入 ngx_reap状态 时,还会被创建, 下下次进入 ngx_reap状态 时还会被创建,如果不断的进入 ngx_reap 状态就会导致master的进程数组用完.
最终的现象是: 你本来定义4个worker进程, 如果这种情况发生, 每reload一次,就会增加一个worker进程. 直到nginx 退出. 我觉得这是nginx的一个bug.
worker:
1> 接收 master发送来的 SIGQUIT 信号, 信号处理函数 ngx_signal_handler 中将 ngx_quit=1; 或者 UNIX域套接字收到 NGX_CMD_QUIT 将 ngx_quit=1;
2> 主逻辑进入 ngx_quit 状态, 将其修改进程标题为"worker process is shutting down"; 关闭监听socket,转入 ngx_exiting 状态
3> ngx_exiting 状态中 调用连接池中所有连接的处理函数,并将其 close=1;
4> 退出. 在 ngx_exiting 状态中 监听socket 已经关闭,意味着不会再有新的请求注册到 事件处理器 中, 如果 事件处理器 中没有任何事件了,此时worker进程就可以退出了.
cache_manager:
1> 接收 master发送来的 SIGQUIT 信号, 信号处理函数 ngx_signal_handler 将 ngx_quit=1;
2> 进入 ngx_quit 状态, 进程直接 exit(0);
cache_loader:
和 cache_manager进程一样, 因为cache加载进程加载完毕后就自动退出了, 只有在加载完毕前执行 nginx -s stop 才会执行这里的逻辑.
6 执行 nginx -s stop 时
执行 nginx -s stop 的进程:
从main函数开始一直执行到
master:
1> 接收 stop (SIGINT 或 SIGTERM)信号, 信号处理函数 ngx_signal_handler 将 ngx_terminate=1;
2> 进入 ngx_terminate 状态; 向子进程通过 UNIX域套接字 发送 NGX_CMD_TERMINATE, 如果发送失败,则通过 信号机制 发送 SIGTERM 信号.
3> master为通过 setitimer() 每个子进程预留50毫秒(最大1000毫秒)的延迟退出时间, 如果一个子进程50毫秒未退出,则它的延迟退出时间增加一倍,直到进程的累计延迟退出时间超过1000毫秒,则向还未退出的子进程发送 SIGKILL 信号
4> 回收退出的子进程. 收到 SIGCHLD, 将 ngx_reap=1; 进入 ngx_process_get_status(), exited=1, 此时 exiting=0;
5> 进入 ngx_reap 状态, 所有回收的子进程都不会被重建, 因为 ngx_terminate=1; 这里将 pid=-1; 返回live=0
6> 此时没有存活的子进程(即 ngx_reap 状态返回live=0) 或 ngx_terminate=1 或者 ngx_quit=1, 则master 进入 退出 状态 ngx_master_process_exit(); 调用所有模块的 exit_master() 钩子函数, 关闭 监听socket, 然后 exit(0);
worker:
1> 接收 master发送来的 SIGTERM 信号, 信号处理函数 ngx_signal_handler 中将 ngx_terminate=1; 或者 UNIX域套接字收到 NGX_CMD_TERMINATE, 将 ngx_terminate=1;
2> 主逻辑进入 ngx_terminate 状态, 执行 ngx_worker_process_exit(), 调用 exit_process 钩子函数, 然后exit(0);
3> 如果1000毫秒后还未退出,会收到master发来的 SIGKILL 信号,当时就退出了.
7 执行 nginx -s quit 时
执行 nginx -s quit 的进程:
从main函数开始一直执行到
从nginx.pid文件中解析出当前的 master 进程PID, 向其发送 quit 信号
master:
1> 接收 quit (SIGQUIT)信号, 信号处理函数 ngx_signal_handler 将 ngx_quit=1;
2> 进入 ngx_quit 状态, 向子进程通过 UNIX域套接字 发送 NGX_CMD_QUIT, 如果发送失败,则通过 信号机制 发送 SIGQUIT 信号.
3> 关闭 监听socket.
4> 回收退出的子进程. 收到 SIGCHLD, 将 ngx_reap=1; 进入 ngx_process_get_status(), exited=1, 此时 exiting=0;
5> 进入 ngx_reap 状态, 所有回收的子进程都不会被重建, 因为 ngx_terminate=1; 这里将 pid=-1;
6> 此时没有存活的子进程(即 ngx_reap 状态返回live=0) 或 ngx_terminate=1 或者 ngx_quit=1, 则master 进入退出状态 ngx_master_process_exit(); 调用所有模块的 exit_master() 钩子函数, 关闭 监听socket, 然后 exit(0);
worker:
1> 接收 master发送来的 SIGQUIT 信号, 信号处理函数 ngx_signal_handler 中将 ngx_quit=1; 或者 UNIX域套接字收到 NGX_CMD_QUIT 将 ngx_quit=1;
2> 主逻辑进入 ngx_quit 状态, 将其修改进程标题为"worker process is shutting down"; 关闭监听socket,转入 ngx_exiting 状态
3> ngx_exiting 状态中 调用连接池中所有连接的处理函数,并将其 close=1;
4> 退出. 在 ngx_exiting 状态中 监听socket 已经关闭,意味着不会再有新的请求注册到 事件处理器 中, 如果 事件处理器 中没有任何事件了,此时worker进程就可以退出了.
cache_loader:
和 cache_manager进程一样, 因为cache加载进程加载完毕后就自动退出了, 只有在加载完毕前执行 nginx -s stop 才会执行这里的逻辑.
8 执行 nginx -s reopen 时
执行 nginx -s reopen 的进程:
从main函数开始一直执行到
master:
1> 接收 reopen (SIGUSER1)信号, 信号处理函数 ngx_signal_handler 将 ngx_reopen=1;
2> 进入 ngx_reopen 状态, 向子进程通过 UNIX域套接字 发送 NGX_CMD_REOPEN, 如果发送失败,则通过 信号机制 发送 SIGUSER1 信号.
3> 进入 ngx_reopen_files() 重新打开 cycle->open_files 中的文件, 有: "logs/error.log", "logs/access.log".
worker:
1> 接收 master 发送来的 SIGUSER1 信号, 信号处理函数 ngx_signal_handler 中将 ngx_reopen=1; 或者 UNIX域套接字收到 NGX_CMD_REOPEN 将 ngx_reopen=1;
2> 进入 ngx_reopen 状态, 进入 ngx_reopen_files() 重新打开 cycle->open_files 中的文件, 有: "logs/error.log", "logs/access.log".
9 某worker异常退出时
master:
1> master收到内核发送来的 SIGCHLD 信号, 信号处理函数 ngx_signal_handler() 将 ngx_reap=1, 调用 ngx_process_get_status() 回收子进程,
2> ngx_process_get_status() 回收子进程, 将 exited=1, 此时 exiting=0;
3> 进入 ngx_reap 状态, ngx_reap_children(), 通知其他子进程该子进程已经退出,请关闭它的channel[0], 重新创建异常退出的worker进程. 重新创建 老的可再生的子进程的条件是:
(ngx_processes[i].exited && ngx_processes[i].respawn && !ngx_processes[i].exiting && !ngx_terminate && !ngx_quit)
这里除了 cache_loader 子进程(respawn=0)外, worker, cache_manager子进程(respawn=1)异常退出时都会被创建.
worker:
1> 接收ngx_reap_children()使用 UNIX域套接字 发送来的 NGX_CMD_CLOSE_CHANNEL;执行关闭对应进程的 UNIX域套接字,清理pid及所在进程池的位置.
2> 接收ngx_reap_children()创建新进程时, 使用UNIX域套接字发送来的 NGX_CMD_OPEN_CHANNEL;执行关闭对应进程的 UNIX域套接字,清理pid及所在进程池的位置.
cache_manager:
1> 接收ngx_reap_children()使用 UNIX域套接字 发送来的 NGX_CMD_CLOSE_CHANNEL;执行关闭对应进程的 UNIX域套接字,清理pid及所在进程池的位置.
2> 接收ngx_reap_children()创建新进程时, 使用UNIX域套接字发送来的 NGX_CMD_OPEN_CHANNEL;执行关闭对应进程的 UNIX域套接字,清理pid及所在进程池的位置.
cache_loader:
如果此时 cache_loader 还存在, 它也会收到如下:
1> 接收ngx_reap_children()使用 UNIX域套接字 发送来的 NGX_CMD_CLOSE_CHANNEL;执行关闭对应进程的 UNIX域套接字,清理pid及所在进程池的位置.
2> 接收ngx_reap_children()创建新进程时, 使用UNIX域套接字发送来的 NGX_CMD_OPEN_CHANNEL;执行关闭对应进程的 UNIX域套接字,清理pid及所在进程池的位置.
----end-----
From: GS
1 进程间的通信方式
使用 UNIX域套接字 socketpair() 异步通讯机制: nginx在创建worker前将先调用 socketpair(int channel[2]) 然后将 channel[0-1]设置为异步通知方式,并注册evnet事件,父进程使用channel[0],子进程使用channel[1]实现双方的通讯.
1.1 创建子进程时用到的标示
/* NGX_PROCESS_XXX 用来标示创建子进程的属性. NGX_PROCESS_NORESPAWN: 子进程退出时,父进程不会再次创建, 该标记用在创建 "cache loader process". NGX_PROCESS_JUST_SPAWN: 当 nginx -s reload 时, 如果还有未加载的 proxy_cache_path, 则需要再次创建 "cache loader process"加载,并用 NGX_PROCESS_JUST_SPAWN给这个进程做记号,防止 "master会向老的worker进程,老的cache manager进程,老的cache loader进程(如果存在)发送NGX_CMD_QUIT或SIGQUIT" 时,误以为这个进程是老的cache loader进程. NGX_PROCESS_RESPAWN: 子进程异常退出时,master会重新创建它, 如当worker或cache manager异常退出时,父进程会重新创建它. NGX_PROCESS_JUST_RESPAWN: 当 nginx -s reload 时, master会向老的worker进程,老的cache manager进程,老的cache loader进程(如果存在)发送 ngx_write_channel(NGX_CMD_QUIT)(如果失败则发送SIGQUIT信号); 该标记用来标记进程数组中哪些是新创建的子进程;其他的就是老的子进程. NGX_PROCESS_DETACHED: 热代码替换 */ #define NGX_PROCESS_NORESPAWN -1 #define NGX_PROCESS_JUST_SPAWN -2 #define NGX_PROCESS_RESPAWN -3 #define NGX_PROCESS_JUST_RESPAWN -4 #define NGX_PROCESS_DETACHED -5
2 都有哪些通信?
2.1 创建worker进程时
2.2 执行 nginx -s reload 时
2.3 执行 nginx -s stop 时
2.4 执行 nginx -s quit 时
2.5 执行 nginx -s reopen 时
2.6 某worker异常退出时
2.7 概要
SIGHUP NGX_CMD_QUIT|SIGQUIT
./nginx -s reload --------------> master (ngx_reload=1) -------------------------> worker|cache_manager|cache_loader (ngx_quit=1, 处理完待读socket再退出)
SIGTERM|SIGINT NGX_CMD_TERMINATE|SIGTERM
./nginx -s stop -----------------------> master (ngx_terminate=1,延迟退出时间) ----------------------------> worker|cache_manager|cache_loader (ngx_terminate=1,直接退出)
SIGQUIT NGX_CMD_QUIT|SIGQUIT
./nginx -s quit --------------> master (ngx_quit=1) ------------------------> worker|cache_manager|cache_loader (ngx_quit=1, 处理完待读socket再退出)
SIGUSER1 NGX_CMD_REOPEN|SIGUSER1
./nginx -s reopen -----------------> master (ngx_reopen=1) -------------------------> worker|cache_manager|cache_loader (ngx_reopen=1)
3 通信的内容是什么?
src/os/unix/ngx_process_cycle.h: #define NGX_CMD_OPEN_CHANNEL 1 #define NGX_CMD_CLOSE_CHANNEL 2 #define NGX_CMD_QUIT 3 #define NGX_CMD_TERMINATE 4 #define NGX_CMD_REOPEN 5
4 创建worker进程时 是如何通信的?
master(父进程):
1> 在进程池 ngx_processes 中为 新worker进程 分配一个位置,如果进程池已满,也就是创建的进程数超过了最大值1024 nginx报错但不退出;
2> 调用 socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) 创建UNIX域套接字对.并设置为异步模式且execve时关闭;
3> 将 新worker进程 的{NGX_CMD_OPEN_CHANNEL, channel[0], 在进程池中的位置, PID} 通过 UNIX域套接字 告知已存在的子进程.
{ ngx_start_worker_processes() { ch.command=NGX_CMD_OPEN_CHANNEL; for (i=0; i < n; i++) { //... ch.pid=ngx_processes[ngx_process_slot].pid; ch.slot=ngx_process_slot; ch.fd=ngx_processes[ngx_process_slot].channel[0]; ngx_pass_open_channel(cycle, &ch); } } }
新worker进程:
1> 关闭进程池中其他子进程的 channel[1];
2> 关闭自身的 channel[0];
3> 将自身的 channel[1] 注册event读事件: ngx_channel_handler().
{ ngx_worker_process_cycle() { //... ngx_worker_process_init() { //... for (n=0; n < ngx_last_process; n++) { if (ngx_processes .pid == -1) { continue; } if (n == ngx_process_slot) { continue; } if (ngx_processes .channel[1] == -1) { continue; } if (close(ngx_processes .channel[1]) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "close() channel failed"); } } if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) { } if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT, ngx_channel_handler) == NGX_ERROR) { /* fatal */ exit(2); } } } }
自身可以通过 进程池中其他子进程的 channel[0] 收发 其他子进程 的信息.
自身可以通过 自身的 channel[1] 收发 master进程 的信息.
其他子进程:
此时 其他子进程的 UNIX域套接字 收到 master进程的 {NGX_CMD_OPEN_CHANNEL, channel[0], 在进程池中的位置, PID}; 读事件处理函数 ngx_channel_handler()被调用;
1> 获得 新子进程 在进程池中的位置;
2> 保存 新子进程 的 channel[0];
2> 保存 新子进程 的 PID.
{ ngx_channel_handler() { for ( ;; ) { n=ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log); switch (ch.command) { case NGX_CMD_QUIT: ngx_quit=1; break; case NGX_CMD_TERMINATE: ngx_terminate=1; break; case NGX_CMD_REOPEN: ngx_reopen=1; break; case NGX_CMD_OPEN_CHANNEL: ngx_processes[ch.slot].pid=ch.pid; ngx_processes[ch.slot].channel[0]=ch.fd; break; case NGX_CMD_CLOSE_CHANNEL: if (close(ngx_processes[ch.slot].channel[0]) == -1) { } ngx_processes[ch.slot].channel[0]=-1; break; } } } }
5 执行 nginx -s reload 时
执行 nginx -s reload 的进程:
从main函数开始一直执行到
if (ngx_signal) { return ngx_signal_process(cycle, ngx_signal); }从nginx.pid文件中解析出当前的 master 进程PID, 向其发送 reload 信号
master:
1> 接收到 reload 信号, 信号处理函数 ngx_signal_handler 中将 ngx_reconfigure=1;
2> 进入 ngx_reconfigure 状态; 调用 ngx_init_cycle() 初始化环境,重新加载配置文件, 重新创建 worker进程, cache管理进程, cache加载进程(热代码忽略);
3> 使用 UNIX域套接字 通知老的worker进程, cache管理进程(cache加载进程此时可能存在,也可能已经退出,因为在ngx_cache_loader_process_handler() {exit(0)}), NGX_CMD_QUIT;
这里不通知的进程有: 刚刚创建的worker进程, cache管理进程, cache加载进程, 正在退出中且本次发送信号为 shutdown的进程, 热代码加载中的进程.
4> 如果使用 UNIX域套接字发送 NGX_CMD_QUIT 失败 ,则使用 kill 发送 SIGQUIT, 将这些进程的属性 exiting=1; 如果发送信号时某进程已经退出,则将其属性 exited=1;
5> 过N久之后, 这些被发送信号的进程会退出, master进程会收到内核发送来的 SIGCHLD,将 ngx_reap=1; 并调用 ngx_process_get_status()回收退出的子进程;
6> 回收子进程 ngx_process_get_status();
7> reload状态,也会进入 ngx_reap 状态, 这个状态是否会重新创建 那些老的可再生的子进程 呢?
首先, 重新创建 老的可再生的子进程的条件是:
(ngx_processes[i].exited && ngx_processes[i].respawn && !ngx_processes[i].exiting && !ngx_terminate && !ngx_quit)
reload状态,可以通过观察进程属性 respawn, exiting, exited 的值来判断是否会重新创建 那些老的可再生的子进程. 除了 cache loader 进程的respawn=0, 其他进程 respawn =1. 故只有 exited=1 && exiting=0 时才会重建.
初始值 exiting=0, exited=0;
reload状态, 通知老的子进程退出有三种情况:
<A> 使用 UNIX域套接字 发送 NGX_CMD_QUIT, 发送成功将 exiting=1, 此时 exited 还是 0. [不会重建]
<B> 情况<A> 失败, 使用 kill 发送 SIGQUIT, 发送成功, 此时子进程还未退出, 将 exiting=1, 此时 exited 还是 0. [不会重建]
<C> 如果此时子进程已经退出, master 还未来得及回收,则将 exited=1, exiting=0, ngx_reap=1. [可能重建]
我们来看这种 [可能重建] 的情况.
master的for(;;)逻辑:
for() { if (delay) {} sigsuspend(); ngx_time_update(); if (ngx_reap) {} if (!live && (ngx_terminate || ngx_quit)) {} if (ngx_terminate) {} if (ngx_quit) {} if (ngx_reconfigure) {} if (ngx_restart) {} if (ngx_reopen) {} if (ngx_change_binary) {} if (ngx_noaccept) {} }
如果情况 <C> 出现, 说明子进程已经退出, master还未回收, 等本次 if (ngx_reconfigure) {} 会回到for(;;), sigsuspend();时会收到 SIGCHLD,进入 ngx_signal_handler() ngx_reap=1; 进入 ngx_reap 状态, 此时 exited=1, exiting=0, 会导致:
(1) 这个进程会被创建
(2) 这个老的进程占用的master的进程数组的位置不会释放. 也就是 pid 不会等于1, 而且该进程的属性始终 exited=1, exiting=0,
(3) 由于上面的 exited=1, exiting=0, 所以下次进入 ngx_reap状态 时,还会被创建, 下下次进入 ngx_reap状态 时还会被创建,如果不断的进入 ngx_reap 状态就会导致master的进程数组用完.
最终的现象是: 你本来定义4个worker进程, 如果这种情况发生, 每reload一次,就会增加一个worker进程. 直到nginx 退出. 我觉得这是nginx的一个bug.
{ /* 接受到 reload 信号: 1 "SIGHUP" ngx_signal_handler "reload" */ ngx_signal_handler() { switch (ngx_process) { case NGX_PROCESS_MASTER: case NGX_PROCESS_SINGLE: switch (signo) { //... case ngx_signal_value(NGX_RECONFIGURE_SIGNAL): ngx_reconfigure=1; action=", reconfiguring"; break; case SIGCHLD: ngx_reap=1; break; //... } //... } } ngx_master_process_cycle() { //... for (;;) { if (ngx_reap) { ngx_reap=0; ngx_reap_children(cycle); } if (ngx_reconfigure) { ngx_reconfigure=0; //... /* 重新初始化,加载配置 */ cycle=ngx_init_cycle(cycle); ngx_cycle=cycle; ccf=(ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); /* 重新创建新的 worker进程 */ ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_JUST_RESPAWN); /* 重新创建新的 缓存池管理进程 和 缓存池加载进程 */ ngx_start_cache_manager_processes(cycle, 1); /* 通知 */ ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL)); { switch (signo) { case ngx_signal_value(NGX_SHUTDOWN_SIGNAL): ch.command=NGX_CMD_QUIT; break; default: ch.command=0; } for (i=0; i < ngx_last_process; i++) { /* 如果是 新创建的进程(NGX_PROCESS_JUST_RESPAWN), 正在退出的进程(ngx_processes[i]->exiting), 热代码加载的进程, 则不通知他们; 否则通过 UNIX域套接字 告知. */ 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; /* 将该进程的状态 exiting=1 */ } continue; } } if (kill(ngx_processes[i].pid, signo) == -1) { if (err == NGX_ESRCH) { /* 该子进程进程为僵死进程 */ ngx_processes[i].exited=1; ngx_processes[i].exiting=0; ngx_reap=1; } continue; } /* 除了 reopen 信号外, 其他信号都会使子进程退出, 这里将那些子进程的状态改为正在退出的状态.*/ if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) { ngx_processes[i].exiting=1; } } } } } } ngx_process_get_status() { /* 循环回收 */ for ( ;; ) { pid=waitpid(-1, &status, WNOHANG); /* 如果子进程持有 accept 锁, 则释放. */ if (ngx_accept_mutex_ptr) { ngx_atomic_cmp_set(ngx_accept_mutex_ptr, pid, 0); } /* 将该进程在master进程池中的属性 status=其退出码, exited=1, */ 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; } } /* 打印日志: 如果子进程被信号中断,则打印出信号编号, 否则打印出其退出码*/ //... } } ngx_reap_children() { ch.command=NGX_CMD_CLOSE_CHANNEL; for (i=0; i < ngx_last_process; i++) { if (ngx_processes[i].exited) { if (!ngx_processes[i].detached) { /* 清理子进程的 UNIX域套接字 资源, 通过 UNIX域套接字告知其他存活子进程 NGX_CMD_CLOSE_CHANNEL */ } //... } else if (ngx_processes[i].exiting || !ngx_processes[i].detached) { live=1; } } return live; } }
worker:
1> 接收 master发送来的 SIGQUIT 信号, 信号处理函数 ngx_signal_handler 中将 ngx_quit=1; 或者 UNIX域套接字收到 NGX_CMD_QUIT 将 ngx_quit=1;
2> 主逻辑进入 ngx_quit 状态, 将其修改进程标题为"worker process is shutting down"; 关闭监听socket,转入 ngx_exiting 状态
3> ngx_exiting 状态中 调用连接池中所有连接的处理函数,并将其 close=1;
4> 退出. 在 ngx_exiting 状态中 监听socket 已经关闭,意味着不会再有新的请求注册到 事件处理器 中, 如果 事件处理器 中没有任何事件了,此时worker进程就可以退出了.
{ ngx_channel_handler() { for ( ;; ) { n=ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log); switch (ch.command) { case NGX_CMD_QUIT: ngx_quit=1; break; } } } ngx_signal_handler() { switch (ngx_process) { //... case NGX_PROCESS_WORKER: case NGX_PROCESS_HELPER: switch (signo) { //... case ngx_signal_value(NGX_SHUTDOWN_SIGNAL): ngx_quit=1; action=", shutting down"; break; //... } //... break; } } ngx_worker_process_cycle() { if (ngx_exiting) { c=cycle->connections; for (cycle->connection) { if (c[i].fd != -1 && c[i].idle) { c[i].close=1; c[i].read->handler(c[i].read); } } if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel) { ngx_worker_process_exit(cycle); } } if (ngx_quit) { ngx_quit=0; ngx_setproctitle("worker process is shutting down"); if (!ngx_exiting) { ngx_close_listening_sockets(cycle); ngx_exiting=1; /* 将 ngx_exiting 置为有效. */ } } } }
cache_manager:
1> 接收 master发送来的 SIGQUIT 信号, 信号处理函数 ngx_signal_handler 将 ngx_quit=1;
2> 进入 ngx_quit 状态, 进程直接 exit(0);
{ ngx_cache_manager_process_cycle() { //... for (;;) { if (ngx_terminate || ngx_quit) { exit(0); } } } }
cache_loader:
和 cache_manager进程一样, 因为cache加载进程加载完毕后就自动退出了, 只有在加载完毕前执行 nginx -s stop 才会执行这里的逻辑.
6 执行 nginx -s stop 时
执行 nginx -s stop 的进程:
从main函数开始一直执行到
if (ngx_signal) { return ngx_signal_process(cycle, ngx_signal); }从nginx.pid文件中解析出当前的 master 进程PID, 向其发送 stop 信号
master:
1> 接收 stop (SIGINT 或 SIGTERM)信号, 信号处理函数 ngx_signal_handler 将 ngx_terminate=1;
2> 进入 ngx_terminate 状态; 向子进程通过 UNIX域套接字 发送 NGX_CMD_TERMINATE, 如果发送失败,则通过 信号机制 发送 SIGTERM 信号.
3> master为通过 setitimer() 每个子进程预留50毫秒(最大1000毫秒)的延迟退出时间, 如果一个子进程50毫秒未退出,则它的延迟退出时间增加一倍,直到进程的累计延迟退出时间超过1000毫秒,则向还未退出的子进程发送 SIGKILL 信号
4> 回收退出的子进程. 收到 SIGCHLD, 将 ngx_reap=1; 进入 ngx_process_get_status(), exited=1, 此时 exiting=0;
5> 进入 ngx_reap 状态, 所有回收的子进程都不会被重建, 因为 ngx_terminate=1; 这里将 pid=-1; 返回live=0
6> 此时没有存活的子进程(即 ngx_reap 状态返回live=0) 或 ngx_terminate=1 或者 ngx_quit=1, 则master 进入 退出 状态 ngx_master_process_exit(); 调用所有模块的 exit_master() 钩子函数, 关闭 监听socket, 然后 exit(0);
{ /* 接收信号 15 "SIGTERM" ngx_signal_handler "stop" */ ngx_signal_handler() { switch (ngx_process) { case NGX_PROCESS_MASTER: case NGX_PROCESS_SINGLE: switch (signo) { //... case ngx_signal_value(NGX_TERMINATE_SIGNAL): case SIGINT: ngx_terminate=1; break; case SIGALRM: ngx_sigalrm=1; break; //... } //... } } ngx_master_process_cycle() { for (;;) { if (delay) { if (ngx_sigalrm) { sigio=0; delay *= 2; ngx_sigalrm=0; } 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) { } } sigsuspend(&set); ngx_time_update(); 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); } /* 退出机制: nginx -s stop master 收到 SIGTERM 或者 SIGINT 时, 进入 ngx_terminate 状态, 然后给所有子进程通过 UNIX域套接字发送 发送 NGX_CMD_QUIT, 通过kill() SIGTERM 信号 给每个子进程预留 50 毫秒的退出时间, 使用setitmer()延迟 n*50 毫秒, 当 n*50 还有子进程未退出, 则延迟时间增加为 n*2*50 以此类推. 如果子进程在 1000 毫秒内还没有全部退出,则对还在运行的子进程发送 SIGKILL. SIGKILL 是不能屏蔽也不能注册信号处理函数的信号, 动作为是 终止. */ 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; } } } }
worker:
1> 接收 master发送来的 SIGTERM 信号, 信号处理函数 ngx_signal_handler 中将 ngx_terminate=1; 或者 UNIX域套接字收到 NGX_CMD_TERMINATE, 将 ngx_terminate=1;
2> 主逻辑进入 ngx_terminate 状态, 执行 ngx_worker_process_exit(), 调用 exit_process 钩子函数, 然后exit(0);
3> 如果1000毫秒后还未退出,会收到master发来的 SIGKILL 信号,当时就退出了.
{ ngx_channel_handler() { for ( ;; ) { n=ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log); switch (ch.command) { case NGX_CMD_TERMINATE: ngx_terminate=1; break; } } } ngx_signal_handler() { switch (ngx_process) { //... case NGX_PROCESS_WORKER: case NGX_PROCESS_HELPER: switch (signo) { //... case ngx_signal_value(NGX_TERMINATE_SIGNAL): ngx_terminate=1; action=", shutting down"; break; //... } //... } } ngx_worker_process_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); } } }
7 执行 nginx -s quit 时
执行 nginx -s quit 的进程:
从main函数开始一直执行到
if (ngx_signal) { return ngx_signal_process(cycle, ngx_signal); }
从nginx.pid文件中解析出当前的 master 进程PID, 向其发送 quit 信号
master:
1> 接收 quit (SIGQUIT)信号, 信号处理函数 ngx_signal_handler 将 ngx_quit=1;
2> 进入 ngx_quit 状态, 向子进程通过 UNIX域套接字 发送 NGX_CMD_QUIT, 如果发送失败,则通过 信号机制 发送 SIGQUIT 信号.
3> 关闭 监听socket.
4> 回收退出的子进程. 收到 SIGCHLD, 将 ngx_reap=1; 进入 ngx_process_get_status(), exited=1, 此时 exiting=0;
5> 进入 ngx_reap 状态, 所有回收的子进程都不会被重建, 因为 ngx_terminate=1; 这里将 pid=-1;
6> 此时没有存活的子进程(即 ngx_reap 状态返回live=0) 或 ngx_terminate=1 或者 ngx_quit=1, 则master 进入退出状态 ngx_master_process_exit(); 调用所有模块的 exit_master() 钩子函数, 关闭 监听socket, 然后 exit(0);
{ // 接收信号 3 "SIGQUIT" ngx_signal_handler "quit" ngx_signal_handler() { switch (ngx_process) { case NGX_PROCESS_MASTER: case NGX_PROCESS_SINGLE: switch (signo) { //... case ngx_signal_value(NGX_SHUTDOWN_SIGNAL): ngx_quit=1; break; //... } //... } } ngx_master_process_cycle() { for (;;) { //... sigsuspend(&set); ngx_time_update(); 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_quit) { ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL)); ls = cycle->listening.elts; for (n = 0; n < cycle->listening.nelts; n++) { if (ngx_close_socket(ls .fd) == -1) { ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, ngx_close_socket_n " %V failed", &ls .addr_text); } } cycle->listening.nelts = 0; continue; } } } }
worker:
1> 接收 master发送来的 SIGQUIT 信号, 信号处理函数 ngx_signal_handler 中将 ngx_quit=1; 或者 UNIX域套接字收到 NGX_CMD_QUIT 将 ngx_quit=1;
2> 主逻辑进入 ngx_quit 状态, 将其修改进程标题为"worker process is shutting down"; 关闭监听socket,转入 ngx_exiting 状态
3> ngx_exiting 状态中 调用连接池中所有连接的处理函数,并将其 close=1;
4> 退出. 在 ngx_exiting 状态中 监听socket 已经关闭,意味着不会再有新的请求注册到 事件处理器 中, 如果 事件处理器 中没有任何事件了,此时worker进程就可以退出了.
{ ngx_channel_handler() { for ( ;; ) { n=ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log); switch (ch.command) { case NGX_CMD_QUIT: ngx_quit=1; break; } } } ngx_signal_handler() { switch (ngx_process) { //... case NGX_PROCESS_WORKER: case NGX_PROCESS_HELPER: switch (signo) { //... case ngx_signal_value(NGX_SHUTDOWN_SIGNAL): ngx_quit=1; action=", shutting down"; break; //... } //... } } ngx_worker_process_cycle() { if (ngx_exiting) { c=cycle->connections; for (cycle->connection) { if (c[i].fd != -1 && c[i].idle) { c[i].close=1; c[i].read->handler(c[i].read); } } if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel) { ngx_worker_process_exit(cycle); } } if (ngx_quit) { ngx_quit=0; ngx_setproctitle("worker process is shutting down"); if (!ngx_exiting) { ngx_close_listening_sockets(cycle); ngx_exiting=1; /* 将 ngx_exiting 置为有效. */ } } } }cache_manager: 1> 接收 master发送来的 SIGQUIT 信号, 信号处理函数 ngx_signal_handler 将 ngx_quit=1; 2> 进入 ngx_quit 状态 ngx_worker_process_exit(), 调用 exit_process 钩子函数, 然后exit(0);
{ ngx_cache_manager_process_cycle() { //... for (;;) { if (ngx_terminate || ngx_quit) { exit(0); } } } }
cache_loader:
和 cache_manager进程一样, 因为cache加载进程加载完毕后就自动退出了, 只有在加载完毕前执行 nginx -s stop 才会执行这里的逻辑.
8 执行 nginx -s reopen 时
执行 nginx -s reopen 的进程:
从main函数开始一直执行到
if (ngx_signal) { return ngx_signal_process(cycle, ngx_signal); }从nginx.pid文件中解析出当前的 master 进程PID, 向其发送 reopen 信号
master:
1> 接收 reopen (SIGUSER1)信号, 信号处理函数 ngx_signal_handler 将 ngx_reopen=1;
2> 进入 ngx_reopen 状态, 向子进程通过 UNIX域套接字 发送 NGX_CMD_REOPEN, 如果发送失败,则通过 信号机制 发送 SIGUSER1 信号.
3> 进入 ngx_reopen_files() 重新打开 cycle->open_files 中的文件, 有: "logs/error.log", "logs/access.log".
{ // 接收信号 10 "SIGUSR1" ngx_signal_handler "reopen" ngx_signal_handler() { switch (ngx_process) { case NGX_PROCESS_MASTER: case NGX_PROCESS_SINGLE: switch (signo) { //... case ngx_signal_value(NGX_REOPEN_SIGNAL): ngx_reopen=1; break; //... } //... } } ngx_master_process_cycle() { for (;;) { //... sigsuspend(&set); ngx_time_update(); if (ngx_reopen) { ngx_reopen = 0; ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs"); ngx_reopen_files(cycle, ccf->user); ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_REOPEN_SIGNAL)); } } } }
worker:
1> 接收 master 发送来的 SIGUSER1 信号, 信号处理函数 ngx_signal_handler 中将 ngx_reopen=1; 或者 UNIX域套接字收到 NGX_CMD_REOPEN 将 ngx_reopen=1;
2> 进入 ngx_reopen 状态, 进入 ngx_reopen_files() 重新打开 cycle->open_files 中的文件, 有: "logs/error.log", "logs/access.log".
{ ngx_channel_handler() { for ( ;; ) { n=ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log); switch (ch.command) { case NGX_CMD_REOPEN: ngx_reopen=1; break; } } } ngx_signal_handler() { switch (ngx_process) { //... case NGX_PROCESS_WORKER: case NGX_PROCESS_HELPER: switch (signo) { //... case ngx_signal_value(NGX_REOPEN_SIGNAL): ngx_reopen=1; action=", shutting down"; break; //... } //... } } ngx_worker_process_cycle() { if (ngx_reopen) { ngx_reopen = 0; ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs"); ngx_reopen_files(cycle, -1); } } }
9 某worker异常退出时
master:
1> master收到内核发送来的 SIGCHLD 信号, 信号处理函数 ngx_signal_handler() 将 ngx_reap=1, 调用 ngx_process_get_status() 回收子进程,
2> ngx_process_get_status() 回收子进程, 将 exited=1, 此时 exiting=0;
3> 进入 ngx_reap 状态, ngx_reap_children(), 通知其他子进程该子进程已经退出,请关闭它的channel[0], 重新创建异常退出的worker进程. 重新创建 老的可再生的子进程的条件是:
(ngx_processes[i].exited && ngx_processes[i].respawn && !ngx_processes[i].exiting && !ngx_terminate && !ngx_quit)
这里除了 cache_loader 子进程(respawn=0)外, worker, cache_manager子进程(respawn=1)异常退出时都会被创建.
{ ngx_signal_handler() { switch (ngx_process) { case NGX_PROCESS_MASTER: case NGX_PROCESS_SINGLE: switch (signo) { case SIGCHLD: ngx_reap=1; break; } } if (signo == SIGCHLD) { ngx_process_get_status(); } } ngx_master_process_cycle() { for (;;) { if (ngx_reap) { ngx_reap=0; live=ngx_reap_children(cycle); } } } ngx_reap_children() { ch.command=NGX_CMD_CLOSE_CHANNEL; for (i=0; i < ngx_last_process; i++) { if (ngx_processes[i].exited) { if (!ngx_processes[i].detached) { ngx_close_channel(ngx_processes[i].channel, cycle->log); ngx_processes[i].channel[0]=-1; ngx_processes[i].channel[1]=-1; ch.pid=ngx_processes[i].pid; ch.slot=i; for (n=0; n < ngx_last_process; n++) { if (ngx_processes .exited || ngx_processes .pid == -1 || ngx_processes .channel[0] == -1) { continue; } ngx_write_channel(ngx_processes .channel[0], &ch, sizeof(ngx_channel_t), cycle->log); } } if (ngx_processes[i].respawn && !ngx_processes[i].exiting && !ngx_terminate && !ngx_quit) { if (ngx_spawn_process(cycle, ngx_processes[i].proc, ngx_processes[i].data, ngx_processes[i].name, i) == NGX_INVALID_PID) { ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "could not respawn %s", ngx_processes[i].name); continue; } ch.command=NGX_CMD_OPEN_CHANNEL; ch.pid=ngx_processes[ngx_process_slot].pid; ch.slot=ngx_process_slot; ch.fd=ngx_processes[ngx_process_slot].channel[0]; ngx_pass_open_channel(cycle, &ch); live=1; continue; } if (ngx_processes[i].pid == ngx_new_binary) { ccf=(ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); if (ngx_rename_file((char *) ccf->oldpid.data, (char *) ccf->pid.data) != NGX_OK) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, ngx_rename_file_n " %s back to %s failed " "after the new binary process \"%s\" exited", ccf->oldpid.data, ccf->pid.data, ngx_argv[0]); } ngx_new_binary=0; if (ngx_noaccepting) { ngx_restart=1; ngx_noaccepting=0; } } if (i == ngx_last_process - 1) { ngx_last_process--; } else { ngx_processes[i].pid=-1; } } else if (ngx_processes[i].exiting || !ngx_processes[i].detached) { live=1; } } return live; } }
worker:
1> 接收ngx_reap_children()使用 UNIX域套接字 发送来的 NGX_CMD_CLOSE_CHANNEL;执行关闭对应进程的 UNIX域套接字,清理pid及所在进程池的位置.
2> 接收ngx_reap_children()创建新进程时, 使用UNIX域套接字发送来的 NGX_CMD_OPEN_CHANNEL;执行关闭对应进程的 UNIX域套接字,清理pid及所在进程池的位置.
cache_manager:
1> 接收ngx_reap_children()使用 UNIX域套接字 发送来的 NGX_CMD_CLOSE_CHANNEL;执行关闭对应进程的 UNIX域套接字,清理pid及所在进程池的位置.
2> 接收ngx_reap_children()创建新进程时, 使用UNIX域套接字发送来的 NGX_CMD_OPEN_CHANNEL;执行关闭对应进程的 UNIX域套接字,清理pid及所在进程池的位置.
cache_loader:
如果此时 cache_loader 还存在, 它也会收到如下:
1> 接收ngx_reap_children()使用 UNIX域套接字 发送来的 NGX_CMD_CLOSE_CHANNEL;执行关闭对应进程的 UNIX域套接字,清理pid及所在进程池的位置.
2> 接收ngx_reap_children()创建新进程时, 使用UNIX域套接字发送来的 NGX_CMD_OPEN_CHANNEL;执行关闭对应进程的 UNIX域套接字,清理pid及所在进程池的位置.
----end-----
From: GS
相关文章推荐
- nginx进程模型
- Nginx学习之六-nginx核心进程模型
- Nginx源码学习(三) nginx进程模型
- Nginx学习之六-nginx核心进程模型
- Nginx源码分析 ——Nginx的进程模型
- nginx源码分析--高性能服务器开发 常见进程模型
- nginx 之 多进程模型
- 图解Nginx-Nginx进程模型1
- nginx源码分析(3)——进程模型
- Nginx学习之六-nginx核心进程模型
- 【NGINX进程模型】源码分析
- 初探Nginx架构之进程模型与事件处理机制
- nginx的进程模型
- 3-nginx进程模型
- 进程模型--nginx
- nginx进程模型
- Nginx学习之六-nginx核心进程模型
- nginx源码分析--master和worker进程模型
- memcached多线程模型 & nginx 多进程模型
- Nginx的内部(进程)模型