文章10:Nginx的超时处理

来源:互联网 发布:衡水学霸有多努力知乎 编辑:程序博客网 时间:2024/05/17 22:46
欢迎转载,请注明出处http://blog.csdn.net/yankai0219/article/details/8468456
0.序
1.综述
      1)Nginx管理时间采用的数据结构是红黑树
      2)Nginx检测那些等待处理的事件对象是否已经超时的两种方案
      3)Nginx是如何设置这两种方案的呢
 2.详细分析:
          1)初始化
          2)选择使用哪种超时判定方案:
           3)ngx_epoll_process_events函数
3.定时事件的执行。
4.总体流程图
5.程序实例
   本程序在经典的hello world模块上进行拓展,增加了简单的定时器,实现每隔1s输出hello,yankai^^^V^^^
         文件名:ngx_http_hello_module.c
          文件名:config
          nginx编译命令
          配置文件:nginx.conf内容
          运行
6.参考文章

0.序
     Nginx的超时处理贯穿整个Nginx,是十分重要的一个概念。因此必须对其有一个深刻的理解。本文参考网上诸多文章,将对超时处理给出一个详尽的处理。
         

1.综述
     1)Nginx管理时间采用的数据结构是红黑树
          nginx利用红黑树来组织那些等待处理并且需要关注其是否超时的事件对象。
          将红黑树节点作为ngx_event_t的值成员,而非指针成员,则通过红黑树找到最先到期的节点后,通过offsetof宏可以直接得到ngx_event_t本身.
          一句话概括:那些加了定时处理的事件都会加入到红黑树中进行管理。
     2)Nginx检测那些等待处理的事件对象是否已经超时的两种方案:
          (1)常规的定时检测机制:设置定时器,每过一定的时间就对红黑树管理的所有事件对象进行一次超时检测
          (2)经历距离当前最快发生超时的事件对象的时间就进行一次超时检测。比如有3个事件,事件a、b、c,超时时间5s,6s,7s,那么距离当前最快发生超时的事件对象为a,时间为5s,那么5s后就会进行一次超时检测。
      3)Nginx是如何设置这两种方案的呢?
          (1)在配置文件中,通过使用timer_resulotion xxms;设置使用常规的定时检测机制。如果没有设置timer_resulotion指令,那么Nginx使用2)(2)中方案。
          在ngx_process_events_and_timers函数中,通过下面代码可以确定使用哪种方案。如果设置了timer_resolution方案,则timer=NGX_TIMER_INFINITE(其值为-1),那么在  events = epoll_wait(ep, event_list, (int) nevents, timer);中epoll_wait就会无限时间等待,直到定时器产生SIGALRM信号,epoll_wait才中断退出。为何定时器会产生SIGALRM信号,我们接着分析。
/*ngx_process_events_and_timers函数*/  
  if (ngx_timer_resolution) {
        timer = NGX_TIMER_INFINITE;
        flags = 0;
    } else {
        timer = ngx_event_find_timer();
        flags = NGX_UPDATE_TIME;
    }
 
     4)接着分析,为何在epoll_wait无限时间等待时,定时器会产生SIGALRM信号,这儿就必须去看    ngx_event_process_init函数的实现了。  通过查看下面代码,我们发现:通过sigaction指定SIGALRM的信号处理函数为ngx_timer_signal_handler;通过系统调用setitimer设置定时器的时间。当setitimer设定的时间耗尽以后,会发送SIGALARM信号,由于指定信号处理函数,因此会调用ngx_timer_signal_handler函数。至于为何时间耗尽以后会发送SIGALARM信号,请man setitimer,这是setitimer函数的属性。
          
 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");
        }
    }

  2.详细分析:
          1)初始化:
               (1)在ngx_event_process_init函数中,初始化红黑树。其调用关系为ngx_worker_process_cycle--->ngx_worker_process_init---->ngx_event_process_init
if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
        return NGX_ERROR;
    }ngx_int_t
ngx_event_timer_init(ngx_log_t *log)
{
    ngx_rbtree_init(&ngx_event_timer_rbtree, &ngx_event_timer_sentinel,
                    ngx_rbtree_insert_timer_value);
    return NGX_OK;
}
               (2)如果使用了timer_resulotion超时判定方案的话,在ngx_event_process_init函数中会对定时器进行一些设定。通过sigaction指定SIGALRM的信号处理函数为ngx_timer_signal_handler;通过系统调用setitimer设置定时器的时间。这部分内容已经在1.综述 4)中详细介绍

          2)选择使用哪种超时判定方案:
               在ngx_process_events_and_timers函数中,选择使用哪种超时判定方案:其调用关系为ngx_worker_process_cycle--->ngx_process_events_and_timers
if (ngx_timer_resolution) {
        timer = NGX_TIMER_INFINITE;
        flags = 0;

    } else {
        timer = ngx_event_find_timer();
        flags = NGX_UPDATE_TIME;
    }
          
          3)紧接着就是进入ngx_process_events函数进行处理即ngx_epoll_process_events函数
                    <1>当采用不同超时判定方案时,epoll_wait的反应是不同的。分别进行说明
                     (1)采用1)常规定时方案timer_resolution:
                             a) 当采用timer_resolution时,在ngx_process_events_and_timers中, timer = NGX_TIMER_INFINITE(其值为-1); flags = 0;对于epoll_wait而言,就是无限等待。
                             b)当setitimer设定的时间耗尽时,会产生SIGALARM信号,此时会调用信号处理函数ngx_timer_signal_handler将ngx_event_timer_alarm赋值为1.
                              c)产生SIGALARM信号,将epoll_wait中断,此时epoll_wait报错退出,ngx_errno中保存出错信息,如果ngx_errno为EINTR,那么epoll_wait就是因为中断退出。接着判断中断是否为SIGALARM信号,因为只有中断是SIGALARM信号时,才会调用ngx_timer_signal_handler将ngx_event_timer_alarm赋值为1.然后退出返回NGX_OK。
                      (2)采用经历距离当前最快发生超时的事件对象的时间就进行一次超时检测方案.
                              a)当采用该方案时,在ngx_process_events_and_timers中  timer = ngx_event_find_timer();  flags = NGX_UPDATE_TIME;epoll_wait等待时间为红黑树中最小的等待时间。
                              b)当timer耗尽后,由于超时epoll_wait会退出。此时events=0;当timer!=-1时,退出返回NGX_OK。
                     <2>不管是采用哪种超时判定方案,都会进行判断是否更新时间缓存。当为方案2)或者因为SIGALARM中断时,都会更新时间缓存。
                    
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
        ngx_time_update();
    }

                      <3>只要在未超时时,发生事件,那么events大于零,nginx就会去处理事件。  
 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 (err) {/*由于中断等错误*/
        if (err == NGX_EINTR) {

            if (ngx_event_timer_alarm) {
                ngx_event_timer_alarm = 0;
                return NGX_OK;
            }
        } else {..........}
        return NGX_ERROR;
    }

    if (events == 0) {/*由于超时*/
        if (timer != NGX_TIMER_INFINITE) {
            return NGX_OK;
        }
        return NGX_ERROR;
    }

/*events > 0*/

    for (i = 0; i < events; i++) {

................
             
3.定时事件的执行。

 (void) ngx_process_events(cycle, timer, flags);

    delta = ngx_current_msec - delta;
.................
 if (delta) {/* delta其实是用来反应epoll_wait阻塞了多长时间,所以delta等于0时表示本次epoll_wait几乎没有阻塞,所以上一次的事件循环和本次事件循环是在几乎0延迟的时间内完成的,当前时间没有发生改变,故不需要去检查定时事件。*/
        ngx_event_expire_timers();
 }
     ngx_event_expire_timers函数就是遍历一下定时器红黑树,找出超时的定时事件并执行事件的回调函数。 
      由于nginx利用红黑树来组织管理超时事件,因此检测是否有事件超时并不需要遍历所有的事件对象,而直接找到最近的即将超时的事件对象,判断其是否超时,如果超时则进行相应的超时处理,处理完了再判断第二近的即将超时的事件对象,如此反复,直到遇到某个事件还未超时或所有事件都超时并已处理就结束检测。这就是超时检测与处理函数ngx_event_expire_timers的大致逻辑,当然还有一些其它额外的动作,比如将超时了的事件对象的相关字段置位、超时处理、事件对象移出事件计时红黑树等等.
     ngx_event_expire_timer函数的部分代码:
void
ngx_event_expire_timers(void)
{
    ngx_event_t        *ev;
    ngx_rbtree_node_t  *node, *root, *sentinel;
    sentinel = ngx_event_timer_rbtree.sentinel;
    for ( ;; ) {
        root = ngx_event_timer_rbtree.root;
       node = ngx_rbtree_min(root, sentinel);
        if ((ngx_msec_int_t) (node->key - ngx_current_msec) <= 0) {
            ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
            ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
            ngx_mutex_unlock(ngx_event_timer_mutex);
            ev->timer_set = 0;
            ev->timedout = 1;
            ev->handler(ev);
            continue;
        }
        break;
    }
}

     4.总体流程图
          流程图我采用nginx启动流程总分析中的图
     




5.程序实例
   本程序在经典的hello world模块上进行拓展,增加了简单的定时器,实现每隔1s输出hello,yankai^^^V^^^

/*
说明:ngx_http_hello_module.c and config 这两个文件放在目录ngx_http_hello_module下
 本程序在经典的hello world模块上进行拓展,增加了简单的定时器,实现每隔1s输出hello,yankai^^^V^^^
   实现方法:
     在模块的init_process处ngx_add_timer;
   参考博文:
 Nginx定时器的实现及定时事件的使用
*/
/*
文件名:ngx_http_hello_module.c
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <nginx.h>
#include <time.h>
#define YK_INIT_PROCESS 1
char               * ngx_hello_hello(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r);
#if YK_INIT_PROCESS
static ngx_int_t ngx_http_hello_process_init( ngx_cycle_t *cycle);
void ngx_http_hello_print( ngx_event_t *ev);
#endif

static ngx_command_t ngx_http_hello_commands[]={
     {
          ngx_string( "hello"),
          NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
          ngx_hello_hello,
          0,
          0,
          NULL
     },
     ngx_null_command
};
static ngx_http_module_t ngx_http_hello_module_ctx={
     NULL,/*preconfiguration*/
     NULL,/*postconfiguration*/
     NULL,/*create_main_conf*/
     NULL,/*init_main_conf*/
     NULL,/*create_srv_conf*/
     NULL,/*merge_srv_conf*/
     NULL,/*create_loc_conf*/
     NULL/*merge_loc_conf */
};
ngx_module_t ngx_http_hello_module={
     NGX_MODULE_V1,
     &ngx_http_hello_module_ctx,
     ngx_http_hello_commands,
     NGX_HTTP_MODULE,
     NULL,/* init master */
     NULL,/* init module */
#if YK_INIT_PROCESS
    ngx_http_hello_process_init, /* init process */
#else
   NULL,
#endif
     NULL,/* init thread */
     NULL,/* exit thread */
     NULL,/* exit process */
     NULL,/* exit master */
     NGX_MODULE_V1_PADDING
};
char   * ngx_hello_hello( ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
                 printf("begin into ngx_hello_hello\n" );
     ngx_http_core_loc_conf_t *clcf;
     clcf = ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module);
     clcf-> handler = ngx_http_hello_handler;
     return NGX_CONF_OK;
}
ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r)
{
                 printf("begin into ngx_http_hello_handler\n" );
     ngx_buf_t * b;
     ngx_chain_t out;
     ngx_str_t output_data = ngx_string( "hello,yankai^V^");
     ngx_str_t my_content_type = ngx_string( "text/plain");
     r-> headers_out.content_type .len = my_content_type. len;
     r-> headers_out.content_type .data = my_content_type. data;

        b = ngx_palloc(r-> pool,sizeof (ngx_buf_t));
     out.buf = b;
     out.next = NULL;

     b-> pos = output_data.data ;
     b-> last = output_data.data + output_data.len;

     b-> memory = 1;
     b-> last_buf = 1;

     r-> headers_out.status = NGX_HTTP_OK;
     r-> headers_out.content_length_n = output_data.len;
     ngx_http_send_header(r);
     return ngx_http_output_filter( r,&out);
}
#if YK_INIT_PROCESS
static ngx_event_t yk_ev;
static ngx_connection_t dummy;
static ngx_int_t ngx_http_hello_process_init( ngx_cycle_t *cycle)
{
                 printf("begin into ngx_http_hello_process_init\n" );

                ngx_memzero(&yk_ev, sizeof(ngx_event_t ));
    yk_ev.handler = ngx_http_hello_print;
    yk_ev.log = cycle-> log ;
    yk_ev.data = &dummy;
    dummy.fd = ( ngx_socket_t) -1;
    ngx_add_timer(&yk_ev,1000);
    return NGX_OK;
}
void ngx_http_hello_print( ngx_event_t *ev)
{

    printf( "hello, yankai^^^V^^^");
    ngx_add_timer(&yk_ev,1000);
}
#endif
  
/*
文件名:config
*/
ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c"
CORE_LIBS="$CORE_LIBS - lpcre"
/*
nginx编译命令
我把上述两个文件放在了根目录下add_module_src/ngx_http_hello_module目录下
*/
./configure  --prefix=/usr/local/nginx --with-debug --add-module=add_module_src/ngx_http_hello_module/
/*
配置文件:nginx.conf内容
*/
worker_processes  1;
daemon off;



events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/ octet-stream;


    sendfile        on;

    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

      

        location / { 
           
            root   html;
            index  index.html index.htm;
        }
        location /hello_world {
            hello;    
        }
    }#server
}#http
/*
运行
*/
启动nginx后,就会每隔1s打印








6、参考文章 
nginx的超时处理
http://blog.sina.com.cn/s/blog_7303a1dc0100xhi5.html 
高性能服务器编程中的定时器
Nginx定时器的实现及定时事件的使用
nginx的时间管理
利用timer_resolution设置减少gettimeofday调用的次数

版权声明:本文为博主原创文章,未经博主允许不得转载。

0 0
原创粉丝点击