在上一篇文章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上的所有请求失败,不过不会影响到所有请求,所以降低了风险。