Nginx中Master是怎么知道nginx worker进程程的状态的

NGINX(5)
& & & && 结合一书和学习服务器的高并发处理。
& 最近编写了一个简单的httpd web服务器,虽说比较简单,但是可以实现基本的web服务器的功能,而且还是有数据库的增删查改,由兴趣的同学可以到我的github上边参与进来,添加一些你的见解,我的github:
&&&&&& 好的,言归正传,今天开始对nginx进行一些探索,希望在这里可以记录我学习的步伐,同时希望可以对别人有帮助。。。。。。。。。。
众所周知,nginx性能高,而nginx的高性能与其架构是分不开的。
那么nginx究竟是怎么样的呢?这一节我们先来初识一下nginx框架吧。
&&&&&&& 一.& nginx在启动后,在unix系统中会以daemon的方式在后台运行,后台进程包含一个master进程和多个worker进程。我们也可以手动地关掉后台模式,让nginx在前台运行,并且通过配置让nginx取消master进程,从而可以使nginx以单进程方式运行。很显然,生产环境下我们肯定不会这么做,所以关闭后台模式,一般是用来调试用的,在后面的章节里面,我们会详细地讲解如何调试nginx。所以,我们可以看到,nginx是以多进程的方式来工作的,当然nginx也是支持多线程的方式的,只是我们主流的方式还是多进程的方式,也是nginx的默认方式。nginx采用多进程的方式有诸多好处,所以我就主要讲解nginx的多进程模式吧。
刚才讲到,nginx在启动后,会有一个master进程和多个worker进程。master进程主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。而基本的网络事件,则是放在worker进程中来处理了。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,这里面的原因与nginx的进程模型以及事件处理模型是分不开的。nginx的进程模型,可以由下图来表示:
&二. 在nginx启动后,如果我们要操作nginx,要怎么做呢?
从上文中我们可以看到,master来管理worker进程,所以我们只需要与master进程通信就行了。master进程会接收来自外界发来的信号,再根据信号做不同的事情。所以我们要控制nginx,只需要通过kill向master进程发送信号就行了。比如kill -HUP pid,则是告诉nginx,从容地重启nginx,我们一般用这个信号来重启nginx,或重新加载配置,因为是从容地重启,因此服务是不中断的。master进程在接收到HUP信号后是怎么做的呢?首先master进程在接到信号后,会先重新加载配置文件,然后再启动新的worker进程,并向所有老的worker进程发送信号,告诉他们可以光荣退休了。新的worker在启动后,就开始接收新的请求,而老的worker在收到来自master的信号后,就不再接收新的请求,并且在当前进程中的所有未处理完的请求处理完成后,再退出。
上边 红色标记的部分就是Nginx支持热部署的原理实现:
我再总结一下热部署的实现:
说白了,就是因为 master进程的关系。当通知ngnix重读配置文件的时候,master进程会进行语法错误的判断。如果存在语法错误的话,返回错误,不进行装载。   
&&&&&&&&如果配置文件没有语法错误,那么ngnix也不会将新的配置调整到所有worker中。而是,先不改变已经建立连接的worker,等待worker将所有请求结束之后,将原先在旧的配置下启动的worker杀死,然后使用新的配置创建新的worker。   
当然,直接给master进程发送信号,这是比较老的操作方式,nginx在0.8版本之后,引入了一系列命令行参数,来方便我们管理。
比如,./nginx
-s reload,就是来重启nginx,./nginx -s stop,就是来停止nginx的运行。
如何做到的呢?我们还是拿reload来说,我们看到,执行命令时,我们是启动一个新的nginx进程,而新的nginx进程在解析到reload参数后,就知道我们的目的是控制nginx来重新加载配置文件了,它会向master进程发送信号,然后接下来的动作,就和我们直接向master进程发送信号一样了。
三. 现在,我们知道了当我们在操作nginx的时候,nginx内部做了些什么事情,那么,worker进程又是如何处理请求的呢?
我们前面有提到,worker进程之间是平等的,每个进程,处理请求的机会也是一样的。当我们提供80端口的http服务时,一个连接请求过来,每个进程都有可能处理这个连接,怎么做到的呢?
首先,每个worker进程都是从master进程fork过来,在master进程里面,先建立好需要listen的socket(listenfd)之后,然后再fork出多个worker进程。所有worker进程的listenfd会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有worker进程在注册listenfd读事件前抢accept_mutex,只有抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该连接。当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。我们可以看到,一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。
如下图所示:
那么,nginx采用这种进程模型有什么好处呢?当然,好处肯定会很多了。首先,对于每个worker进程来说,独立的进程,不需要加锁,(& 相对于多线程来说)所以省掉了锁带来的开销,同时在编程以及问题查找时,也会方便很多。其次,采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快启动新的worker进程。当然,worker进程的异常退出,肯定是程序有bug了,异常退出,会导致当前worker上的所有请求失败,不过不会影响到所有请求,所以降低了风险。当然,好处还有很多,大家可以慢慢体会。
上面讲了很多关于nginx的进程模型,接下来,我们来看看nginx是如何处理事件的。
有人可能要问了,nginx采用多worker的方式来处理请求,每个worker里面只有一个主线程,那能够处理的并发数很有限啊,多少个worker就能处理多少个并发,何来高并发呢?非也,这就是nginx的高明之处,nginx采用了异步非阻塞的方式来处理请求,也就是说,nginx是可以同时处理成千上万个请求的。
想想apache的常用工作方式(apache也有异步非阻塞版本,但因其与自带某些模块冲突,所以不常用),每个请求会独占一个工作线程,当并发数上到几千时,就同时有几千的线程在处理请求了。这对操作系统来说,是个不小的挑战,线程带来的内存占用非常大,线程的上下文切换带来的cpu开销很大,自然性能就上不去了,而这些开销完全是没有意义的。
为什么nginx可以采用异步非阻塞的方式来处理呢,或者异步非阻塞到底是怎么回事呢?
我们先回到原点,看看一个请求的完整过程。首先,请求过来,要建立连接,然后再接收数据,接收数据后,再发送数据。具体到系统底层,就是读写事件,而当读写事件没有准备好时,必然不可操作,如果不用非阻塞的方式来调用,那就得阻塞调用了,事件没有准备好,那就只能等了,等事件准备好了,你再继续吧。阻塞调用会进入内核等待,cpu就会让出去给别人用了,对单线程的worker来说,显然不合适,当网络事件越多时,大家都在等待呢,cpu空闲下来没人用,cpu利用率自然上不去了,更别谈高并发了。
好吧,你说加进程数,这跟apache的线程模型有什么区别,注意,别增加无谓的上下文切换。
所以,在nginx里面,最忌讳阻塞的系统调用了。不要阻塞,那就非阻塞喽。
非阻塞就是,事件没有准备好,马上返回EAGAIN,告诉你,事件还没准备好呢,你慌什么,过会再来吧。好吧,你过一会,再来检查一下事件,直到事件准备好了为止,在这期间,你就可以先去做其它事情,然后再来看看事件好了没。虽然不阻塞了,但你得不时地过来检查一下事件的状态,你可以做更多的事情了,但带来的开销也是不小的。
所以,才会有了异步非阻塞的事件处理机制,具体到系统调用就是像select/poll/epoll/kqueue这样的系统调用。它们提供了一种机制,让你可以同时监控多个事件,调用他们是阻塞的,但可以设置超时时间,在超时时间之内,如果有事件准备好了,就返回。
这种机制正好解决了我们上面的两个问题,拿epoll为例(在后面的例子中,我们多以epoll为例子,以代表这一类函数),当事件没准备好时,放到epoll里面,事件准备好了,我们就去读写,当读写返回EAGAIN时,我们将它再次加入到epoll里面。这样,只要有事件准备好了,我们就去处理它,只有当所有事件都没准备好时,才在epoll里面等着。
这样,我们就可以并发处理大量的并发了,当然,这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断地切换而已,切换也是因为异步事件未准备好,而主动让出的。这里的切换是没有任何代价,你可以理解为循环处理多个准备好的事件,事实上就是这样的。
与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换)。更多的并发数,只是会占用更多的内存而已。 我之前有对连接数进行过测试,在24G内存的机器上,处理的并发请求数达到过200万。现在的网络服务器基本都采用这种方式,这也是nginx性能高效的主要原因。
我们之前说过,推荐设置worker的个数为cpu的核数,在这里就很容易理解了,更多的worker数,只会导致进程来竞争cpu资源了,从而带来不必要的上下文切换。而且,nginx为了更好的利用多核特性,提供了cpu亲缘性的绑定选项,我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来cache的失效。像这种小的优化在nginx中非常常见,同时也说明了nginx作者的苦心孤诣。比如,nginx在做4个字节的字符串比较时,会将4个字符转换成一个int型,再作比较,以减少cpu的指令数等等。
现在,知道了nginx为什么会选择这样的进程模型与事件模型了。对于一个基本的web服务器来说,事件通常有三种类型,网络事件、信号、定时器。从上面的讲解中知道,网络事件通过异步非阻塞可以很好的解决掉。如何处理信号与定时器?
首先,信号的处理。对nginx来说,有一些特定的信号,代表着特定的意义。信号会中断掉程序当前的运行,在改变状态后,继续执行。如果是系统调用,则可能会导致系统调用的失败,需要重入。关于信号的处理,大家可以学习一些专业书籍,这里不多说。对于nginx来说,如果nginx正在等待事件(epoll_wait时),如果程序收到信号,在信号处理函数处理完后,epoll_wait会返回错误,然后程序可再次进入epoll_wait调用。
另外,再来看看定时器。由于epoll_wait等函数在调用的时候是可以设置一个超时时间的,所以nginx借助这个超时时间来实现定时器。nginx里面的定时器事件是放在一颗维护定时器的红黑树里面,每次在进入epoll_wait前,先从该红黑树里面拿到所有定时器事件的最小时间(也可以理解为最小堆),在计算出epoll_wait的超时时间后进入epoll_wait。所以,当没有事件产生,也没有中断信号时,epoll_wait会超时,也就是说,定时器事件到了。这时,nginx会检查所有的超时事件,将他们的状态设置为超时,然后再去处理网络事件。由此可以看出,当我们写nginx代码时,在处理网络事件的回调函数时,通常做的第一个事情就是判断超时,然后再去处理网络事件。
我们可以用一段伪代码来总结一下nginx的事件处理模型:
while (true) {
for t in run_tasks:
t.handler();
update_time(&now);
timeout = ETERNITY;
for t in wait_tasks: /* sorted already */
if (t.time &= now) {
t.timeout_handler();//超时了
timeout = t.time -
nevents = poll_function(events, timeout);
for i in nevents:
if (events[i].type == READ) {
t.handler = read_
} else { /* events[i].type == WRITE */
t.handler = write_
run_tasks_add(t);
好,本节我们讲了进程模型,事件模型,包括网络事件,信号,定时器事件。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:88915次
积分:2584
积分:2584
排名:第12673名
原创:163篇
转载:48篇
评论:12条
(1)(1)(1)(2)(1)(7)(26)(5)(4)(4)(2)(3)(1)(10)(20)(15)(6)(29)(51)(15)(8)nginx(12)
nginx分为single和 master两种进程模型。master模型为一个master模型和n个worker进程的工作方式 。本文分析nginx的 master进程做了哪些事情,它是如何管理好各个worker进程的。
在main函数中完成了nginx启动初始化过程,启动初始化过程中的一个重要环节就是解析配置文件,回调各个配置指令的回调函数,因此完成了各个模块的配置相互关联。在完成初始化后,就调用ngx_master_process_cycle,这个函数具体做了什么事情。
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigaddset(&set, SIGALRM);
sigaddset(&set, SIGIO);
sigaddset(&set, SIGINT);
sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle-&log, ngx_errno,
&sigprocmask() failed&);
}上面屏蔽一系列的信号,以防创建worker进程时,被打扰。
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle-&conf_ctx, ngx_core_module);
ngx_start_worker_processes(cycle, ccf-&worker_processes,
NGX_PROCESS_RESPAWN);
ngx_start_cache_manager_processes(cycle, 0);这里开始创建worker子进程,master进程就是通过依次调用这两个函数来创建子进程。第一个调用的函数创建的子进程称为worker进程,第二个调用的函数创建的就是有关cache的子进程。接收请求,完成响应的就是worker进程。
for (i = 0; i & i++) {
ngx_spawn_process(cycle, ngx_worker_process_cycle,
(void *) (intptr_t) i, &worker process&, type);
ch.pid = ngx_processes[ngx_process_slot].
ch.slot = ngx_process_
ch.fd = ngx_processes[ngx_process_slot].channel[0];
ngx_pass_open_channel(cycle, &ch);
}此处就是循环创建n个worker进程,fork新进程的具体工作在ngx_spawn_process函数中完成。这里涉及到一个全局数组ngx_processes(src/os/unix/ngx_process.c),数组的长度为NGX_MAX_PROCESSES(默认为 1024),存储的元素类型是ngx_process_t(src/os/uninx/ngx_process.h文件)。全局数组ngx_processes就是用来存储每个子进程的相关信息,如pid,channel,进程做具体事情的接口指针等,这
些信息就是用结构体ngx_process_t来描述的。在ngx_spawn_process创建好一个worker进程返回 后,master进程就将worker进程的pid,worker进程在ngx_processes数组中的位置及channel[0]传递给前面创建好的worker进程,然后循环创建下一个worker进程。channel是用socketpair创建的,用于进程间通信。master和worker进程以及worker进程之间都可以通过这样的一个通道进行通信。
ngx_start_cache_manager_processes函数和start_worker的工作相关无几。接着master进程就陷入死循环中守护着worker进程。在master_cycle中调用了sigsuspend,因而master进程挂起,等待信号的产生(收到信号,调用信号处理函数,设置对应的全局变量,sigsuspend函数返回,判断全局变量并采取相应的动作)
if (ngx_quit) {
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
ls = cycle-&listening.
for (n = 0; n & cycle-&listening. n++) {
if (ngx_close_socket(ls[n].fd) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle-&log, ngx_socket_errno,
ngx_close_socket_n & %V failed&,
&ls[n].addr_text);
cycle-&listening.nelts = 0;
}对SIGQUIT信号进行的处理动作。调用ngx_signal_worker_processes函数向每个worker进程递送SIGQUIT信号,通知worker进程退出工作,然后关闭所有的监听套接字。用continue是为了子进程发送SIGCHLD信号给master进程,让master进程为其善后。&
if (ngx_reap) {
ngx_reap = 0;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle-&log, 0, &reap children&);
live = ngx_reap_children(cycle);
}此时,ngx_reap为1,master进程调用ngx_reap_children处理所有的worker进程。
master进程就这些。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:566912次
积分:17201
积分:17201
排名:第491名
原创:1222篇
转载:225篇
评论:25条
文章:12篇
阅读:7960
文章:47篇
阅读:29299
(4)(11)(11)(10)(6)(23)(19)(22)(11)(25)(22)(42)(9)(30)(15)(25)(33)(32)(28)(12)(16)(8)(45)(50)(87)(39)(24)(14)(32)(54)(55)(43)(51)(43)(52)(13)(14)(76)(70)(54)(56)(38)(1)(2)(11)(5)(18)(7)(7)(16)(2)(2)(1)(3)(3)(2)(25)(7)(2)(1)(6)(8)1189人阅读
nginx学习(8)
nginx一般情况下都是配置成一个多进程的程序,由一个master进程和多个worker进程组成,master进程通过信号来管理worker进程的运行状态。下面从代码的角度来分析master和worker进程的启动情形。
1.在main函数最后,会根据配置方式,决定nginx的启动形式(单进程或者多进程)
if (ngx_process == NGX_PROCESS_SINGLE) {
ngx_single_process_cycle(cycle);
ngx_master_process_cycle(cycle);
2.在ngx_master_process_cycle中首先是完成信号配置相关程序
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigaddset(&set, SIGALRM);
sigaddset(&set, SIGIO);
sigaddset(&set, SIGINT);
sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle-&log, ngx_errno,
&sigprocmask() failed&);
sigemptyset(&set);
这段代码是阻塞了一些信号,通过这种方式可以使后面生成worker子进程等程序不受信号中断干扰影响
3.ngx_master_process_cycle中利用fork生成woker子进程
ngx_start_worker_processes(cycle, ccf-&worker_processes,
NGX_PROCESS_RESPAWN);
ngx_spawn_process(cycle, ngx_worker_process_cycle,
(void *) (intptr_t) i, &worker process&, type);
--------》
pid = fork();
switch (pid) {
ngx_log_error(NGX_LOG_ALERT, cycle-&log, ngx_errno,
&fork() failed while spawning \&%s\&&, name);
ngx_close_channel(ngx_processes[s].channel, cycle-&log);
return NGX_INVALID_PID;
ngx_pid = ngx_getpid();
proc(cycle, data);
}fork()出的子进程会调用proc(cycle,data),然后在对应的ngx_worker_process_cycle中进入死循环。而主进程则会从switch中跳出,再完成后续的一些操作。
4.主进程进入死循环,以应对各种可能出现的信号
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);
............当然主进程并不是不停地在循环执行上述程序,而是会通过sigsuspend调用使master进程休眠,然后等待master进程收到信号再激活。这里处理信号的方式值得借鉴,利用sigsuspend方式可以提高CPU的工作效率。nginx sigsuspend参见nginx sigsuspend分析,参考//learning-of-sigsuspend.html
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:74510次
积分:2254
积分:2254
排名:第15134名
原创:143篇
转载:68篇
评论:32条
(1)(3)(1)(3)(1)(7)(14)(26)(21)(27)(9)(14)(8)(18)(6)(18)(29)(5)(1)(4)}

我要回帖

更多关于 master worker模式 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信