Nginx事件处理(epoll)

事 件处理是Nginx处理请求的核心,每个子进程在ngx_worker_process_cycle()的循环里不断调用 ngx_process_events_and_timers()函数来处理各种事件。下面,分析使用epoll机制下(Linux最常用支持大并发的事 件触发机制)Nginx事件处理的过程,用源代码分析和debug信息追踪两种方法。

 
我们从ngx_worker_process_cycle()函数(即工作进程处理请求的循环)切入:
 
C代码   收藏代码
  1. static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)  
  2. {  
  3.     /*...*/  
  4.     //第一部分:初始化  
  5.     ngx_worker_process_init(cycle, 1);  
  6.     /*...*/  
  7.     for ( ;; ) {  
  8.         /*...*/  
  9.         //第二部分:处理事件  
  10.         ngx_process_events_and_timers(cycle);  
  11.         /*...*/  
  12.     }  
  13.     /*...*/  
  14. }  
 
 
C代码   收藏代码
  1. //第一部分:初始化  
  2. static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority)  
  3. {  
  4.     //配置一些环境变量  
  5.     //...  
  6.     //设置uid,groupid等  
  7.     //...  
  8.     //如果有设置CPU affinity  
  9.     //...  
  10.     //换到当前工作的目录下  
  11.     //...  
  12.     //清空所有的信号  
  13.     //...  
  14.     //清掉监听socket上以前的事件  
  15.     //...  
  16.   
  17.     //调用所有模块的init_process钩子函数  
  18.     //所有模块-->每个子进程可以调用这些模块的功能  
  19.     //init_process  
  20.     for (i = 0; ngx_modules[i]; i++) {  
  21.         if (ngx_modules[i]->init_process) {  
  22.             //如果是event module: ngx_event_process_init()被调用  
  23.             if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {  
  24.                 exit(2);  
  25.             }  
  26.         }  
  27.     }  
  28.     //将其他进程的channel[1]关闭,自己的除外  
  29.     //子进程继承了父进程的ngx_processes数组,但子进程只监听自己的channel[1]  
  30.     //...  
  31.     //将自己的channel[0]关闭  
  32.     //因为自己的channel[0]是给其他子进程,用来发送消息的sendmsg  
  33.     //...  
  34.     //调用ngx_add_channel_event()函数,给ngx_channel注册一个读事件处理函数。  
  35.     //在ngx_start_worker_processes()函数中,ngx_channel = ngx_processes[s].channel[1];  
  36.     //ngx_channel就是进程自身的channel[1],用来读取的socket  
  37.     //ngx_channel_handler处理从channel中收到的信号,当事件触发时,调用这个方法  
  38.     if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT, ngx_channel_handler) == NGX_ERROR)  
  39.     {  
  40.         exit(2);  
  41.     }  
  42. }  
 
 
每个模块(module)都有一个全局的ngx_module_t结构变量,在 worker process被创建(fork)以后,在ngx_worker_process_init()内调用到每个模块的init_process钩子。其中 ngx_event_core_module的 init_process钩子指向的是ngx_event_process_init()函数,这个函数是这样的:
 
C代码   收藏代码
  1. static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)  
  2. {  
  3.     /*...*/  
  4.   
  5.     for (m = 0; ngx_modules[m]; m++) {  
  6.         //只有ngx_event_core_module和ngx_epoll_module是NGX_EVENT_MODULE类型的  
  7.         if (ngx_modules[m]->type != NGX_EVENT_MODULE) {  
  8.             continue;  
  9.         }  
  10.         if (ngx_modules[m]->ctx_index != ecf->use) {  
  11.             continue;  
  12.         }  
  13.         //获取模块上下文  
  14.         module = ngx_modules[m]->ctx;  
  15.         //初始化模块  
  16.         //ngx_epoll_module(类型ngx_module_t)是全局的结构变量,在初始化的时候由ngx_epoll_module_ctx传入参数,而init函数也在这个时候确定  
  17.         //如epoll就是ngx_epoll_init  
  18.         if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {  
  19.             exit(2);  
  20.         }  
  21.         break;  
  22.     }  
  23.   
  24.     /*...*/  
  25.   
  26.     /*=========初始化connections=========*/  
  27.   
  28.     //分配connection_n个空间给connections  
  29.     cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);  
  30.     c = cycle->connections;  
  31.     //分配connection_n个空间给read_events和write_events,初始化这些read_events和write_events  
  32.     cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);  
  33.     rev = cycle->read_events;  
  34.     for (i = 0; i < cycle->connection_n; i++) {  
  35.         rev[i].closed = 1;  
  36.         rev[i].instance = 1;  
  37.         /*不考虑线程*/  
  38.     }  
  39.     cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);  
  40.     wev = cycle->write_events;  
  41.     for (i = 0; i < cycle->connection_n; i++) {  
  42.         wev[i].closed = 1;  
  43.         /*不考虑线程*/  
  44.     }  
  45.   
  46.     //把connection和read_event,write_event联系起来,每个connection都指向一个read_event和write_event  
  47.     i = cycle->connection_n;  
  48.     next = NULL;  
  49.     do {  
  50.         //把connection链接起来  
  51.         i--;  
  52.         c[i].data = next;  
  53.         c[i].read = &cycle->read_events[i];  
  54.         c[i].write = &cycle->write_events[i];  
  55.         c[i].fd = (ngx_socket_t) -1;  
  56.         next = &c[i];  
  57.         /*不考虑线程*/  
  58.     } while (i);  
  59.   
  60.     //free_connections指向connections的头  
  61.     cycle->free_connections = next;  
  62.     //初始化free connection的数目  
  63.     cycle->free_connection_n = cycle->connection_n;  
  64.   
  65.     /* for each listening socket */  
  66.     ls = cycle->listening.elts;  
  67.     for (i = 0; i < cycle->listening.nelts; i++) {  
  68.         c = ngx_get_connection(ls[i].fd, cycle->log);//获取一个空闲的connection,并设置其file descriptor  
  69.         c->log = &ls[i].log;  
  70.         c->listening = &ls[i];//获取ngx_listening_s结构  
  71.         ls[i].connection = c; //设置ngx_listening_s.connection  
  72.         rev = c->read;  
  73.         rev->log = c->log;  
  74.         rev->accept = 1;      //接受请求  
  75.       
  76.         /*...*/  
  77.   
  78.         //设置c->read的handler为ngx_event_accept  
  79.         rev->handler = ngx_event_accept;  
  80.         if (ngx_use_accept_mutex) {  
  81.             continue;  
  82.         }  
  83.         if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {  
  84.             if (ngx_add_conn(c) == NGX_ERROR) {  
  85.                 return NGX_ERROR;  
  86.             }  
  87.         }   
  88.         else {  
  89.             if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {  
  90.                 return NGX_ERROR;  
  91.             }  
  92.         }  
  93.     }  
  94. }  
 
 
在上面的函数里,如果我们使用了epoll,那么epoll模块的ngx_epoll_init()函数就会被调用,而这个函数中最重要的就是
 
C代码   收藏代码
  1. ngx_event_actions = ngx_epoll_module_ctx.actions  
 
 
在工作进程的for循环中用来处理事件的ngx_process_events_and_timers()中,每次调用ngx_process_events()的时候,其实就是调用 ngx_epoll_module_ctx.actions里面的ngx_epoll_process_events()。
 
 
C代码   收藏代码
  1. //第二部分:处理事件  
  2. //被循环调用  
  3. //先接收连接(并不处理事件),以及处理进程间信号(如有)  
  4. //处理accept queue和event queue里面的事件  
  5. void ngx_process_events_and_timers(ngx_cycle_t *cycle)  
  6. {  
  7.           
  8.     //如果使用了accept mutex    
  9.     //nginx uses accept mutex to serialize accept() syscalls  
  10.     //多进程需要使用mutex  
  11.     if (ngx_use_accept_mutex) {  
  12.         //空闲连接数过少  
  13.         if (ngx_accept_disabled > 0) {  
  14.             ngx_accept_disabled--;  
  15.         }  
  16.         else {  
  17.             //调用ngx_trylock_accept_mutex()试着给cycle上锁,并把ngx_accept_mutex_held设为1  
  18.             //如果成功获得lock,将调用ngx_enable_accept_events()  
  19.             //ngx_enable_accept_events()中会调用ngx_add_event()/ngx_add_conn()  
  20.             //即,获得lock的worker process才会添加一个“接受请求”的事件  
  21.             if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {  
  22.                 return;  
  23.             }  
  24.             //ngx_accept_mutex_held表示当前是否已经持有锁  
  25.             //如果持有的话,就把flags添加NGX_POST_EVENTS,这样表明可以去accept请求  
  26.             //如果不持有,就去处理其他事件,在ngx_epoll_process_events里会调用  
  27.             //rev/wev->handler()  
  28.             if (ngx_accept_mutex_held) {  
  29.                 flags |= NGX_POST_EVENTS;  
  30.             }  
  31.             else {  
  32.                 if (timer == NGX_TIMER_INFINITE ||   
  33.                     timer > ngx_accept_mutex_delay) {  
  34.                     //ngx_accept_mutex_delay 当获得锁失败后,再次去请求锁的间隔时间  
  35.                     timer = ngx_accept_mutex_delay;  
  36.                 }  
  37.             }  
  38.         }  
  39.     }  
  40.   
  41.         //#define ngx_process_events   ngx_event_actions.process_events  
  42.     //In epoll, ngx_event_actions = ngx_epoll_module_ctx.actions;  
  43.     //全局变量ngx_epoll_module_ctx(类型ngx_event_module_t),内有actions(类型ngx_event_actions_t),定义process_events钩子  
  44.     //钩子调用epoll的ngx_epoll_process_events()  
  45.     (void) ngx_process_events(cycle, timer, flags);  
  46.   
  47.     delta = ngx_current_msec - delta;//计算process所用时间  
  48.   
  49.     //ngx_posted_accept_events队列不为空-->有accept事件发生,就去处理  
  50.     //accept事件,其实最后就是调用accept函数接收新的连接  
  51.     //rev->handler = ngx_event_accept在ngx_event_process_init里面设置  
  52.     if (ngx_posted_accept_events) {  
  53.         ngx_event_process_posted(cycle, &ngx_posted_accept_events);  
  54.     }  
  55.   
  56.     //已经处理完接收新连接的事件了,如果前面获取到了accept锁,那就解锁  
  57.     if (ngx_accept_mutex_held) {  
  58.         ngx_shmtx_unlock(&ngx_accept_mutex);  
  59.     }  
  60.   
  61.     if (delta) {  
  62.         ngx_event_expire_timers();  
  63.     }  
  64.   
  65.     //除了accept事件之外的其他事件放在这个队列中,如果队列不为空,就去处理相关的事件  
  66.     if (ngx_posted_events) {  
  67.         //一般不用线程来处理  
  68.         if (ngx_threaded) {  
  69.             ngx_wakeup_worker_thread(cycle);  
  70.         } else {  
  71.             ngx_event_process_posted(cycle, &ngx_posted_events);  
  72.         }  
  73.     }  
  74. }  
 
 
说到这里不得不提一下一个很重要的结构ngx_module_t,这里面定义了一些非常重要的函数钩子:
 
C代码   收藏代码
  1. struct ngx_module_s {  
  2.     //ctx_index是分类的模块计数器,nginx的模块可以分为四种:core、event、http和  
  3.     //mail,每一种的模块又会各自计数一下,这个ctx_index就是每个模块在其所属类组的计数值  
  4.   
  5.     ngx_uint_t            ctx_index;      
  6.   
  7.     //index是一个模块计数器,按照每个模块在ngx_modules[]数组中的声明顺序  
  8.     //(见objs/ngx_modules.c),从0开始依次给每个模块进行编号  
  9.     ngx_uint_t            index;   
  10.   
  11.     /*...*/  
  12.     ngx_uint_t            version;  
  13.   
  14.     //ctx是模块的上下文,不同种类的模块有不同的上下文,四类模块就有四种模块上下文,实现为四个不同的结构体,所以ctx是void *  
  15.     void                 *ctx;   
  16.   
  17.     //commands 是模块的指令集,nginx的每个模块都可以实现一些自定义的指令,这些指令  
  18.     //写在配置文件的适当配置项中,每一个指令在源码中对应着一个 ngx_command_t结构的  
  19.     //变量,nginx会从配置文件中把模块的指令读取出来放到模块的commands指令数组中,  
  20.     //这些指令一般是把配置项的 参数值赋给一些程序中的变量或者是在不同的变量之间合并或  
  21.     //转换数据(例如include指令),指令可以带参数也可以不带参数,你可以把这些指令想象  
  22.     //为 unix的命令行或者是一种模板语言的指令。  
  23.     ngx_command_t        *commands;  
  24.   
  25.     //type就是模块的种类,前面已经说过,nginx模块分为core、event、http和mail四类,type用宏定义标识四个分类。   
  26.     ngx_uint_t            type;   
  27.   
  28.     //init_master、 init_module、init_process、init_thread、exit_thread、  
  29.     //exit_process、 exit_master是函数指针,指向模块实现的自定义回调函数,  
  30.     //这些回调函数分别在初始化master、初始化模块、初始化工作进程、初始化线程、  
  31.     //退出线程、退出工作进程和退出master的时候被调用,如果模块需要在这些时机做处理,  
  32.     //就可以实现对应的函数,并把它赋值给对应的函数指针来注册一个回调 函数接口  
  33.     ngx_int_t           (*init_master)(ngx_log_t *log);  
  34.     ngx_int_t           (*init_module)(ngx_cycle_t *cycle);  
  35.     ngx_int_t           (*init_process)(ngx_cycle_t *cycle);  
  36.     ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);  
  37.     void                (*exit_thread)(ngx_cycle_t *cycle);  
  38.     void                (*exit_process)(ngx_cycle_t *cycle);  
  39.     void                (*exit_master)(ngx_cycle_t *cycle);  
  40.   
  41.     /*...*/  
  42.   
  43. };  
 
 
C代码   收藏代码
  1. //epoll处理事件的函数  
  2. static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)  
  3. {  
  4.   
  5.     //得到发生的事件表event_list  
  6.     events = epoll_wait(ep, event_list, (int) nevents, timer);  
  7.   
  8.     if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {  
  9.         ngx_time_update();  
  10.     }  
  11.   
  12.     //上锁ngx_posted_events_mutex  
  13.     //ngx_posted_events_mutex只有在NGX_THREAD宏定义有效时才有效  
  14.     ngx_mutex_lock(ngx_posted_events_mutex);  
  15.   
  16.     for (i = 0; i < events; i++) {  
  17.         //获取事件的connection  
  18.         c = event_list[i].data.ptr;  
  19.         instance = (uintptr_t) c & 1;  
  20.         c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);  
  21.         rev = c->read;  
  22.   
  23.         revents = event_list[i].events;  
  24.   
  25.         if ((revents & (EPOLLERR|EPOLLHUP)) &&   
  26.            (revents & (EPOLLIN|EPOLLOUT)) == 0) {  
  27.         /* 
  28.         * if the error events were returned without EPOLLIN or EPOLLOUT, 
  29.         * then add these flags to handle the events at least in one active handler 
  30.         */  
  31.             revents |= EPOLLIN|EPOLLOUT;  
  32.         }  
  33.   
  34.         //default rev->active is 1  
  35.         if ((revents & EPOLLIN) && rev->active) {  
  36.             if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {  
  37.                 rev->posted_ready = 1;  
  38.             }  
  39.         }   
  40.         else {  
  41.             rev->ready = 1;  
  42.         }  
  43.         if (flags & NGX_POST_EVENTS) {  
  44.         //如果是新的连接,accept就会被设为1;accept()之后还没有断开(timeout),accept就是0  
  45.         //这个步骤不处理连接,只是把连接放在queue(ngx_posted_accept_events或者ngx_posted_events)里面  
  46.             queue = (ngx_event_t **) (rev->accept ? &ngx_posted_accept_events : &ngx_posted_events);  
  47.             ngx_locked_post_event(rev, queue);  
  48.         }   
  49.         else {  
  50.             rev->handler(rev);  
  51.         }  
  52.   
  53.         wev = c->write;  
  54.         if ((revents & EPOLLOUT) && wev->active) {  
  55.             if (flags & NGX_POST_THREAD_EVENTS) {  
  56.             wev->posted_ready = 1;  
  57.         }  
  58.         else {  
  59.             wev->ready = 1;  
  60.         }  
  61.         if (flags & NGX_POST_EVENTS) {  
  62.             ngx_locked_post_event(wev, &ngx_posted_events);  
  63.         }  
  64.         else {  
  65.             wev->handler(wev);  
  66.         }  
  67.     }  
  68.     //解锁  
  69.     ngx_mutex_unlock(ngx_posted_events_mutex);  
  70.     return NGX_OK;  
  71. }  
 
 
//Debug追踪
下面,以单进程和多进程处理一个http请求为例,分析 一下事件处理的流程。我用nginx里面已有的ngx_log_debugX()来插入事件处理的主要函数 ngx_epoll_process_events()和ngx_event_process_posted()。在编译的时候,需要加 上"--with-debug"参数。并指定nginx.conf里面的"error_log   logs/debug.log  debug_core | debug_event | debug_http;"。重新启动nginx。
 
单进程(work_processes 1):
1. 在初始化即ngx_worker_process_init()中调用两次ngx_epoll_add_event()。第一次是在 ngx_event_process_init()里面,即给每个监听的端口(在我的例子里只监听80端口)添加一个NGX_READ_EVENT事件; 第二次是ngx_add_channel_event(),即给进程间通行的socketpair添加 NGX_READ_EVENT事件。
2. 不断调用ngx_epoll_process_events()函数,探测监听的事件是否发生。如果此时有一个http请求进来,就会触发epoll的事件。由于之前每个监听的端口已经设置handler是ngx_event_accept(),这样,就会在 ngx_epoll_process_events()里面调用rev->handler(rev),即调用 ngx_event_accept()。 在这个函数里,accept()被调用,即接收请求并为其分配一个新的连接,初始化这个新连接,并调用listening socket的handler,即ls->handler(c)。因为ls->handler在http_block()(读取配置之后)里 面已经设置了(ls->handler = ngx_http_init_connection;),那么就会调用ngx_http_init_connection()。而在这个函数里,又会添加 一个读事件,并设置其处理钩子是ngx_http_init_request()。
3. epoll触发新的事件调用 ngx_http_init_request(),并继续http请求处理的每一个环节。(如process request line,process headers,各个phase等)
4. 最后client关闭了连接(我用的是Linux下的curl)。调用了ngx_http_finalize_request() => ngx_http_finalize_connection() => ngx_http_set_keepalive()。 ngx_http_set_keepalive() 函数设置事件的处理函数是ngx_http_keepalive_handler(),并调用ngx_post_event()把它添加到 ngx_posted_events队列里。然后ngx_event_process_posted()函数就会一一处理并删除队列里所有的事件。在 ngx_http_keepalive_handler() 函数里,调用ngx_http_close_connection() => ngx_close_connection() => ngx_del_conn(c,NGX_CLOSE_EVENT)。ngx_del_conn()即 ngx_epoll_del_connection(),即把这个处理请求的connection从epoll监听的事件列表中删除。
 
 
多进程(我设置了work_processes 2):和单进程不同,单进程设置epoll timer为-1,即没有事件就一直阻塞在那里,直到监听的端口收到请求。而多进程则不同,每个进程会设置一个epoll_wait()的 timeout,去轮番尝试获取在监听端口接受请求的权利,如果没有事件就去处理其它的事件,如果获得了就阻塞( *直到有任意事件发生)
1. 在ngx_event_process_init()里面,只会调用 ngx_add_channel_event() 给进程间通信的socketpair添加事件,而不给http监听的端口添加事件(为了保证不会有多个工作进程来同时接受请求)。而每个进程被 fork()之后,父进程(master process)都会调用ngx_pass_open_channel() => ngx_write_channel()  => sendmsg()来通知所有已经存在的进程(这会触发接收方的事件,调用ngx_channel_handler()函数)
2. 在ngx_process_events_and_timers()里,用一个锁来同步所有的进程 ngx_trylock_accept_mutex(),并只有一个进程能够得到ngx_accept_mutex这个锁。得到这个锁的进程会调用 ngx_enable_accept_events()添加一个监听端口的事件。
3. 在ngx_epoll_process_events()里,调用了ngx_locked_post_event()添加了一个读事件到accept queue(即ngx_posted_accept_events),然后在ngx_event_process_posted()里面处理,即调用 ngx_event_accept(),并添加一个读事件(后面和单进程是一样的)。在处理完ngx_posted_accept_events队列里面的所有accept事件之后,ngx_accept_mutex这个锁也会被释放,即把接受请求的权利让给其它的进程。
 
*在多进程的模式下,每当有新的子进程启动的时候,父进程(master process)都会向其余所有进程的socketpair channel广播新的子进程的channel。这样,就会导致之前获取监听端口权限(即ngx_accept_mutex)的进程触发epoll事件, 从而释放ngx_accept_mutex,虽然这个是发生在初始化阶段(之后子进程间一般不通信),一般不会产生两个或多个进程同时在epoll添加监 听端口事件的情况。但是在理论上,这样的设计可能会导致系统的bug(比如有人通过给子进程发送信号的办法来实现一些特殊的功能时,就有可能让其中一个进 程放弃ngx_accept_mutex,而另外某一个进程在之后先于它再次获取到ngx_accept_mutex)。

猜你喜欢

转载自zzc1684.iteye.com/blog/2164173
今日推荐