nginx超时事件的处理

来源:互联网 发布:网络机顶盒安装视频 编辑:程序博客网 时间:2024/05/17 22:54

几乎每个比较大型的系统都会有自己的一套超时事件的处理方式,nginx也不例外。nginx出于高性能的考虑,使用红黑树来管理超时的事件。这里红黑树的原理在网络中有很多文档去介绍,这里就不讲述红黑树的原理了。这篇博客值讲述nginx是怎么把事件与红黑树联系到一起的。大家都知道nginx处理事件有一个结构体:

typedef struct ngx_event_s  ngx_event_tstruct ngx_event_s {......ngx_rbtree_node_t   timer;......}
nginx就是通过在ngx_event_s中添加一个timer成员,与红黑树联系到一起的。讲述事件超时的处理就不得不提到两个全局变量:

ngx_thread_volatile ngx_rbtree_t  ngx_event_timer_rbtree;        //整棵红黑树结构static ngx_rbtree_node_t          ngx_event_timer_sentinel;      //属于红黑树的一个节点,在红黑树的操作中被当作哨兵借点使用
事件超时的初始化是通过  ngx_event_timer_init进行的

ngx_int_tngx_event_timer_init(ngx_log_t *log){    ngx_rbtree_init(&ngx_event_timer_rbtree, &ngx_event_timer_sentinel,                    ngx_rbtree_insert_timer_value);#if (NGX_THREADS)    if (ngx_event_timer_mutex) {        ngx_event_timer_mutex->log = log;        return NGX_OK;    }    ngx_event_timer_mutex = ngx_mutex_init(log, 0);    if (ngx_event_timer_mutex == NULL) {        return NGX_ERROR;    }#endif    return NGX_OK;}
这块代码也比较简单,主要是调用nginx红黑树的接口来进行初始化。而#if和#endif中的那块是关于多线程的代码,由于nginx到现在还没有用到多线程,这块代码可以直接无视。ngx_event_timer_init是通过ngx_event_process_init调用的。这就是说每个子进程都会有自己的事件超时管理。

事件超时初始化已经完成了,那怎么才能把事件插入到红黑树中进行管理呢?这就需要介绍一个函数ngx_event_add_timer:

static ngx_inline voidngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer){    ngx_msec_t      key;    ngx_msec_int_t  diff;    key = ngx_current_msec + timer;.....    ev->timer.key = key;    ngx_mutex_lock(ngx_event_timer_mutex);    ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);    ngx_mutex_unlock(ngx_event_timer_mutex);    ev->timer_set = 1;}
这个函数的代码也是比较简单的,也无需多说了。到现在,nginx事件超时如何初始化,如何把事件插入到红黑树中进行管理都讲到了。但是可能到现在还是有点迷糊就是nginx是怎么串联到整个系统中的呢?下面来讲述。

nginx工作进程都是通过ngx_process_events_and_timers函数来进行处理事件的。ngx_process_events_and_timers会选择io复用的方式,以epoll为例。nginx会阻塞在epoll_wait这个调用中。如果有事件发生就会返回并进行处理;如果没有事件返回,那epoll就会在指定的时间内返回。现在的问题是这个时间是多少呢?请看ngx_process_events_and_timers:

voidngx_process_events_and_timers(ngx_cycle_t *cycle){...... if (ngx_timer_resolution) {        timer = NGX_TIMER_INFINITE;        flags = 0;    } else {        timer = ngx_event_find_timer();        flags = NGX_UPDATE_TIME;#if (NGX_THREADS)        if (timer == NGX_TIMER_INFINITE || timer > 500) {            timer = 500;        }#endif    }......    delta = ngx_current_msec;    (void) ngx_process_events(cycle, timer, flags);    delta = ngx_current_msec - delta;......  if (delta) {        ngx_event_expire_timers();    }......}
epoll_wait等待的时间会根据全局变量ngx_timer_resolution来设定,如果ngx_timer_resolution有值就会把epoll_wait的时间设为-1,并且flags设为0.至于flags有什么作用等下再说。如果ngx_timer_resolution没有值,就会从红黑树从搜索即将发生超时事件的超时事件为timer的值,同样也会设置flags的值。继续向下看,delta会计算出epoll_wait处理的时间。如果delta为0就不会去处理nginx中红黑树的超时事件。下面来看下epoll_wait的那快代码:

static ngx_int_tngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags){......   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();    }......}
从那句if语句可以看到如果flags设置了更新时间或者ngx_event_timer_alarm不为0就会更新ngx_current_sec的值,这样在ngx_process_events_and_timers的delta就不可能为0,这样就会去处理红黑树中的超时事件。

但是如果既没有事件发生而且timer为-1,那nginx岂不是要永远阻塞在epoll_wait上,这样超时的事件永远得不到处理。真的是这样吗?当然不是,这就是为什么上面判断if的时候还有一个ngx_event_timer_alarm的原因。请看ngx_event_process_init:

static ngx_int_tngx_event_process_init(ngx_cycle_t *cycle){......if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {        struct sigaction  sa;        struct itimerval  itv;        ngx_memzero(&sa, sizeof(struct sigaction));        sa.sa_handler = ngx_timer_signal_handler;        sigemptyset(&sa.sa_mask);        if (sigaction(SIGALRM, &sa, NULL) == -1) {            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,                          "sigaction(SIGALRM) failed");            return NGX_ERROR;        }        itv.it_interval.tv_sec = ngx_timer_resolution / 1000;        itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;        itv.it_value.tv_sec = ngx_timer_resolution / 1000;        itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;        if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,                          "setitimer() failed");        }    }......}
这里设置一个定时器,而超时事件就是ngx_timer_resolution毫秒,这样就算事件发生,epoll_wait的时间为-1.还是会被这个信号事件打断。我们来看下ngx_timer_signal_handler:

static voidngx_timer_signal_handler(int signo){    ngx_event_timer_alarm = 1;#if 1    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, "timer signal");#endif}
刚好就是我们在epoll_wait需要的结果。这样就解决了nginx那个永远阻塞在epoll_wait上的问题。

在分析源码的时候,我们看到ngx_timer_resolution好几次了,那这个变量的值从哪来的呢?这个是通过timer_resolution这个命令配置在配置文件中的。

自此nginx超时事件的处理已经讲完。





原创粉丝点击