Nginx学习之路(四)NginX的子进程主循环

在上一篇文章Nginx学习之路(三)NginX的子进程生产过程中说道了生产子进程过程中的proc处理过程,也就是这段代码:

 //调用传入的回调函数,子进程的正式主循环开始,回调函数的实体是ngx_worker_process_cycle  
        proc(cycle, data); 

今天就来介绍一下这个proc的具体过程:

首先,proc函数是一个随ngx_spawn_process()函数传入的回调,子进程的实体函数是ngx_worker_process_cycle(),该函数的实体过程如下:

static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
    ngx_int_t worker = (intptr_t) data;
    //在master中,ngx_process被设置为NGX_PROCESS_MASTER 
    ngx_process = NGX_PROCESS_WORKER;
    ngx_worker = worker;
    //初始化   
    ngx_worker_process_init(cycle, worker);

    ngx_setproctitle("worker process");
    //主循环开始
    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");
	//处理事件和timer,这里也是我们要着重了解的函数
        ngx_process_events_and_timers(cycle);
	//收到NGX_CMD_TERMINATE命令
        if (ngx_terminate) {
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
	    //清理后进程退出,会调用所有模块的钩子exit_process
            ngx_worker_process_exit(cycle);
        }
	//收到NGX_CMD_QUIT命令 
        if (ngx_quit) {
            ngx_quit = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
                          "gracefully shutting down");
            ngx_setproctitle("worker process is shutting down");
	    //如果进程没有"正在退出"
            if (!ngx_exiting) {
		//关闭监听socket,设置退出正在状态
                ngx_exiting = 1;
                ngx_set_shutdown_timer(cycle);
                ngx_close_listening_sockets(cycle);
                ngx_close_idle_connections(cycle);
            }
        }
	//收到NGX_CMD_REOPEN命令,重新打开log  
        if (ngx_reopen) {
            ngx_reopen = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
            ngx_reopen_files(cycle, -1);
        }
    }
}

下面就要详细的说明ngx_process_events_and_timers(cycle)这个过程,代码如下:

void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
    ngx_uint_t  flags;
    ngx_msec_t  timer, delta;

    if (ngx_timer_resolution) {
        timer = NGX_TIMER_INFINITE;
        flags = 0;

    } else {
        timer = ngx_event_find_timer();
        flags = NGX_UPDATE_TIME;

#if (NGX_WIN32)

        /* handle signals from master in case of network inactivity */

        if (timer == NGX_TIMER_INFINITE || timer > 500) {
            timer = 500;
        }

#endif
    }
    /* 
    ngx_use_accept_mutex变量代表是否使用accept互斥体 
    默认是使用,可以通过accept_mutex off;指令关闭; 
    accept mutex 的作用就是避免惊群,同时实现负载均衡 
    */  
    if (ngx_use_accept_mutex) {
	/* 
        ngx_accept_disabled变量在ngx_event_accept函数中计算。 
        如果ngx_accept_disabled大于0,就表示该进程接受的链接过多, 
        因此放弃一次争抢accept mutex的机会,同时将自己减一。 
        然后,继续处理已有连接上的事件。 
        nginx就利用这一点实现了继承关于连接的基本负载均衡。 
        */
        if (ngx_accept_disabled > 0) {
            ngx_accept_disabled--;

        } else {
	    /* 
            尝试锁accept mutex,只有成功获取锁的进程,才会将listen套接字放到epoll中。 
            因此,这就保证了只有一个进程拥有监听套接口,故所有进程阻塞在epoll_wait时, 
            才不会惊群现象。 
            */  
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                return;
            }

            if (ngx_accept_mutex_held) {
		/* 
                如果进程获得了锁,将添加一个 NGX_POST_EVENTS 标志。 
                这个标志的作用是将所有产生的事件放入一个队列中,等释放后,在慢慢来处理事件。 
                因为,处理时间可能会很耗时,如果不先施放锁再处理的话,该进程就长时间霸占了锁, 
                导致其他进程无法获取锁,这样accept的效率就低了。 
                */ 
                flags |= NGX_POST_EVENTS;

            } else {
		/* 
                没有获得所得进程,当然不需要NGX_POST_EVENTS标志。 
                但需要设置延时多长时间,再去争抢锁。 
                */  
                if (timer == NGX_TIMER_INFINITE
                    || timer > ngx_accept_mutex_delay)
                {
                    timer = ngx_accept_mutex_delay;
                }
            }
        }
    }

    delta = ngx_current_msec;
    /*接下来,epoll要开始wait事件, 
    ngx_process_events的具体实现是对应到epoll模块中的ngx_epoll_process_events函数,这个过程在在下面也要详细的说明一下 
    */  
    (void) ngx_process_events(cycle, timer, flags);
    //统计本次wait事件的耗时  
    delta = ngx_current_msec - delta;

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "timer delta: %M", delta);
    /* 
    ngx_posted_accept_events是一个事件队列,暂存epoll从监听套接口wait到的accept事件。 
    前文提到的NGX_POST_EVENTS标志被使用后,会将所有的accept事件暂存到这个队列 
    */ 
    ngx_event_process_posted(cycle, &ngx_posted_accept_events);
	//所有accept事件处理完之后,如果持有锁的话,就释放掉。
    if (ngx_accept_mutex_held) {
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }
    /* 
    delta是之前统计的耗时,存在毫秒级的耗时,就对所有时间的timer进行检查, 
    如果timeout 就从time rbtree中删除到期的timer,同时调用相应事件的handler函数处理 
    */
    if (delta) {
        ngx_event_expire_timers();
    }
    /* 
    处理普通事件(连接上获得的读写事件), 
    因为每个事件都有自己的handler方法, 
    */
    ngx_event_process_posted(cycle, &ngx_posted_events);
}

然后就要介绍(void) ngx_process_events(cycle, timer, flags)这个过程了,有的人喜欢称这个为钩子函数,在我的理解他就是一个回调函数,这个函数的定义如下:

#define ngx_process_events   ngx_event_actions.process_events

注意这个ngx_event_actions,这个就是实现io复用的主要的结构体,它是一个全局变量,定义如下

ngx_event_actions_t   ngx_event_actions;
typedef struct {
    ngx_int_t  (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    ngx_int_t  (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

    ngx_int_t  (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
    ngx_int_t  (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);

    ngx_int_t  (*add_conn)(ngx_connection_t *c);
    ngx_int_t  (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);

    ngx_int_t  (*notify)(ngx_event_handler_pt handler);

    ngx_int_t  (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
                                 ngx_uint_t flags);

    ngx_int_t  (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
    void       (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;

然后在ngx_epoll_module.c和ngx_poll_module.c以及ngx_select_module.c中实现了关于epoll,poll,select这几种io复用方式的具体方法,下面以epoll为例,在ngx_epoll_module.c中:

ngx_event_actions = ngx_epoll_module_ctx.actions;
static ngx_event_module_t  ngx_epoll_module_ctx = {
    &epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */

    {
        ngx_epoll_add_event,             /* add an event */
        ngx_epoll_del_event,             /* delete an event */
        ngx_epoll_add_event,             /* enable an event */
        ngx_epoll_del_event,             /* disable an event */
        ngx_epoll_add_connection,        /* add an connection */
        ngx_epoll_del_connection,        /* delete an connection */
#if (NGX_HAVE_EVENTFD)
        ngx_epoll_notify,                /* trigger a notify */
#else
        NULL,                            /* trigger a notify */
#endif
        ngx_epoll_process_events,        /* process the events */
        ngx_epoll_init,                  /* init the events */
        ngx_epoll_done,                  /* done the events */
    }
};

这几种add,del等事件就不具体赘述了,详细说明下ngx_epoll_process_events的过程:

static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
    int                events;
    uint32_t           revents;
    ngx_int_t          instance, i;
    ngx_uint_t         level;
    ngx_err_t          err;
    ngx_event_t       *rev, *wev;
    ngx_queue_t       *queue;
    ngx_connection_t  *c;

    /* NGX_TIMER_INFINITE == INFTIM */

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "epoll timer: %M", timer);
    //调用epoll_wait获取事件  
    events = epoll_wait(ep, event_list, (int) nevents, timer);

    err = (events == -1) ? ngx_errno : 0;
    //更新时间 
    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
        ngx_time_update();
    }
    //epoll_wait出错处理 
    if (err) {
        if (err == NGX_EINTR) {

            if (ngx_event_timer_alarm) {
                ngx_event_timer_alarm = 0;
                return NGX_OK;
            }

            level = NGX_LOG_INFO;

        } else {
            level = NGX_LOG_ALERT;
        }

        ngx_log_error(level, cycle->log, err, "epoll_wait() failed");
        return NGX_ERROR;
    }
    //本次调用没有事件发生  
    if (events == 0) {
        if (timer != NGX_TIMER_INFINITE) {
            return NGX_OK;
        }

        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                      "epoll_wait() returned no events without timeout");
        return NGX_ERROR;
    }
    //遍历本次epoll_wait返回的所有事件  
    for (i = 0; i < events; i++) {
	//获取连接ngx_connection_t的地址
        c = event_list[i].data.ptr;
	//连接的地址最后一位具有特殊意义:用于存储instance变量,将其取出来
        instance = (uintptr_t) c & 1;
	//无论是32位还是64位机器,其地址最后一位一定是0,获取真正地址
        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
	//取出读事件
        rev = c->read;
	//判断读事件是否为过期事件 
        if (c->fd == -1 || rev->instance != instance) {

            /*
             * the stale event from a file descriptor
             * that was just closed in this iteration
             */

            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "epoll: stale event %p", c);
            continue;
        }
	//取出事件类型 
        revents = event_list[i].events;

        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                       "epoll: fd:%d ev:%04XD d:%p",
                       c->fd, revents, event_list[i].data.ptr);

        if (revents & (EPOLLERR|EPOLLHUP)) {
            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "epoll_wait() error on fd:%d ev:%04XD",
                           c->fd, revents);

            /*
             * if the error events were returned, add EPOLLIN and EPOLLOUT
             * to handle the events at least in one active handler
             */

            revents |= EPOLLIN|EPOLLOUT;
        }

#if 0
        if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                          "strange epoll_wait() events fd:%d ev:%04XD",
                          c->fd, revents);
        }
#endif
	//是读事件且该事件是活跃的  
        if ((revents & EPOLLIN) && rev->active) {

#if (NGX_HAVE_EPOLLRDHUP)
            if (revents & EPOLLRDHUP) {
                rev->pending_eof = 1;
            }

            rev->available = 1;
#endif

            rev->ready = 1;
	    //事件需要延后处理
            if (flags & NGX_POST_EVENTS) {
		/*如果要在post队列中延后处理该事件,首先要判断它是新连接时间还是普通事件 
                以确定是把它加入到ngx_posted_accept_events队列或者ngx_posted_events队列中。*/
                queue = rev->accept ? &ngx_posted_accept_events
                                    : &ngx_posted_events;
		//将该事件添加到相应的延后队列中
                ngx_post_event(rev, queue);

            } else {
		//立即调用事件回调方法来处理这个事件
                rev->handler(rev);
            }
        }

        wev = c->write;

        if ((revents & EPOLLOUT) && wev->active) {

            if (c->fd == -1 || wev->instance != instance) {

                /*
                 * the stale event from a file descriptor
                 * that was just closed in this iteration
                 */

                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                               "epoll: stale event %p", c);
                continue;
            }

            wev->ready = 1;
#if (NGX_THREADS)
            wev->complete = 1;
#endif

            if (flags & NGX_POST_EVENTS) {
                ngx_post_event(wev, &ngx_posted_events);

            } else {
                wev->handler(wev);
            }
        }
    }

    return NGX_OK;
}

总结一下,worker进程的主要任务就是通过io复用的方式,处理基本的网络事件,多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。nginx的这种多进程处理模式好处肯定会很多了,首先,对于每个worker进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销,同时在编程以及问题查找时,也会方便很多。其次,采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快启动新的worker进程。当然,worker进程的异常退出,肯定是程序有bug了,异常退出,会导致当前worker上的所有请求失败,不过不会影响到所有请求,所以降低了风险。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章