Nginx Insight: Event Loop

Nginx 区别于它之前的主流 Web 服务器的地方实际上是 Event Driven。

Event Loop

Event Loop 的结构实质上就是一个无限循环。

for(;;) {
  do_one_event();
}

Nginx Worker Process Event Loop

for(;;) {
  ngx_process_events_and_timers(cycle);
}

Nginx 的事件处理

proc ngx_process_events_and_timers {cycle} {
  set timeout [find_most_recent_timer]
  
  move_posted_events -from $ngx_posted_next_events -to $ngx_posted_events 

  process_events -timeout $timeout

  process_posted_events $ngx_posted_accept_events

  expire_timers

  process_posted_events $ngx_posted_events
}
  • Event 主要是三种
    1. accept
    2. Socket IO
    3. Timer
  • accept 事件交由 $ngx_posted_accept_events 队列处理
  • 模块可以通过 $ngx_posted_events 队列介入 Nginx 的事件循环

Nginx 的事件队列

Nginx 的 event 模块实现了三个事件队列:

  1. ngx_posted_events
  2. ngx_posted_next_events
  3. ngx_posted_accept_events

事件队列操作:

  • ngx_post_event(ev, &ngx_posted_events)
  • ngx_delete_posted_event(ev)

每次事件循环最后,会调用并清空 ngx_posted_events 里的所有事件。

ngx_posted_events 队列里的事件允许添加新的事件到当前队列。理论上可以在这里形成死循环。

如果希望一个事件重复触发,可以将其加入 ngx_posted_next_events 队列。事件循环会在下一个循环里把 ngx_posted_next_events 里的第一个事件移入 ngx_posted_events 队列。

用 Tcl 代码表示即 push ngx_posted_events [shift ngx_posted_next_events]

Nginx 事件循环里的 Socket

对于客户端网络连接,基本思路就是监听 IO 事件。监听方式可以是

  1. select
  2. pool
  3. epoll
  4. kqueue

对于监听链接 (Listening Socket / Aassive Socket),Nginx 也一并通过异步监听的方式处理了。这样可以异步地监听多个端口。

思路用 Tcl 的方式表示,大致如下:

foreach sock $socket_list {
   fileevent $sock readable|writable {
     ev->ready = 1;
     ev->available = -1;
     ngx_post_event(ev, &ngx_posted_events);
   }
}

foreach sock $server_socket_list {
   fileevent $sock readable|writable {
     ev->ready = 1;
     ev->available = -1;
     ngx_post_event(ev, &ngx_posted_accept_events);
   }
}

可以看到,accept 事件有单独的事件队列,并且优先级高于普通的 IO 事件。

Nginx 事件循环里的 Timer

File: event/ngx_event_timer.c

定时器添加和删除:

#define ngx_add_timer    ngx_event_add_timer
#define ngx_del_timer    ngx_event_del_timer

void ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer){
  ev->timer_set = 1;
  insert_timer_to_rbtree $ev
}

void ngx_event_del_timer(ngx_event_t *ev){
  ev->timer_set = 0;
  delete_timer_from_rbtree $ev
}

定时器触发时:

void ngx_event_expire_timers(){
  foreach ev [expired_timers_list] {
    ev->timer_set = 0;
    ev->timedout = 1;
    ev->handler(ev);
  }
}

一些和定时器有关的常数:

#define NGX_TIMER_INFINITE  (ngx_msec_t) -1
#define NGX_TIMER_LAZY_DELAY  300

Nginx Master Process Event Loop

for(;;){
  sigsuspend(&set);
}