当前位置: 首页 > 工具软件 > ngx-mask > 使用案例 >

nginx时间更新机制(ngx_timer_resolution)

赵光赫
2023-12-01

nginx worker进程就是在处理网络事件、定时器事件和信号,核心是处理网络事件和定时器事件。下面看一下 worker进程是如何精确处理这些核心事件的。

 

1、worker进程启动

在 worker进程处理函数中,首先进行 worker进程运行相关的配置初始化设置操作,然后进入无限循环,处理 worker进程关注的信号和定时器事件以及核心的网络事件。下面是相关代码:

static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) {
    // ... 初始化相关操作

    for ( ;; ) {
        // ... 信号处理
        
        // worker进程核心处理逻辑 处理事件和定时器
        ngx_process_events_and_timers(cycle);
    }
}

 

2、worker进程核心处理函数

在 ngx_process_events_and_timers核心处理函数中,如果配置了 ngx_timer_resolution时间,则传给 epoll_wait的超时时间为 -1,表示永久阻塞等待,如果没有网络事件发生在 ngx_timer_resolution事件后定时器超时就会触发 ALARM信号,强制 epoll_wait返回。

如果没有配置 ngx_timer_resolution时间,nginx会调用 ngx_event_find_timer函数找到最近一个定时器超时的时间,把此时间设置为 epoll_wait的超时时间,如果没有网络事件的发生,在最近的定时器超时之前 epoll_wait函数就会返回,处理对应的超时事件。

 

这里看代码时一直有一个疑问,就是在调用 ngx_event_find_timer函数的时候获取了最近定时器的超时时间,如果在 epoll_wait等待期间,又添加了新的定时器比之前获取超时时间还少怎么办?如何触发刚刚添加的定时器。其实这里最开始就思考错误了,因为 nginx每个进程都是单线程的,当前进程在 epoll_wait阻塞的时候,进程是不会有地方可以添加新的定时器的。所以 ngx_event_find_timer函数返回的就是所有定时器中的最小的超时时间。

void ngx_process_events_and_timers(ngx_cycle_t *cycle) {
    // ...
    if (ngx_timer_resolution) {
        /*
            开启了时间精度  给epoll_wait的超时参数为-1  阻塞等待
            epoll_wait返回有两种情况:
            1、有网络事件发生
            2、有信号产生 提前加好的ALARM定时器在ngx_timer_resolution时间耗尽后 触发ALARM信号
        */
        timer = NGX_TIMER_INFINITE;
        flags = 0;

    } else {
        /*
            未开启高精度时钟
            在定时器中找到最近一个超时定时器的时间 设置为epoll_wait的超时时间
            防止长时间没有网络事件发生  epoll_wait等待时间过久  导致定时器事件延迟触发

            注意:
            这里之前一直有一个疑问  就是ngx_event_find_timer返回当前所有定时器中最近一个超时时间
            如果epoll_wait在等待超时时间内  有其他的定时器事件加入并且超时在之前最近的事件超时之前  如何触发新加的最近的超时事件定时器
            其实不会出现上面那种情况  因为nginx是单进程模型  进程阻塞在epoll_wait  不可能有地方添加定时器
        */
        timer = ngx_event_find_timer();
        flags = NGX_UPDATE_TIME;
    }

    delta = ngx_current_msec;
    // 根据不同系统调用不同的网络监听接口
    (void) ngx_process_events(cycle, timer, flags);
    delta = ngx_current_msec - delta;

    /*
        ngx_process_events执行时消耗的时间delta大于0
        这可能有新的定时器事件被触发 调用ngx_event_expire_timers方法处理所有满足条件的定时器事件
    */
    if (delta) {
        ngx_event_expire_timers();
    }

    // ...
}

 

3、网络事件处理函数ngx_process_events

调用 epoll_wait在参数 timer时间内等待网络事件或者信号事件的发生。

这里 epoll_wait返回后,需要判断是否需要更新 nginx的全局系统时间,如果 flags为 NGX_UPDATE_TIME,即没有 ngx_timer_resolution配置时,这里可能是最近的定时器超时了,需要更新全局系统时间,用于后续时间等的判断;或者 ngx_event_timer_alarm标志位为1,即设置了 ngx_timer_resolution配置,如果标志为1,说明是因为系统定时器超时触发的回调函数(ngx_event_process_init函数中设置)设置的此标志位,所以是 epoll_wait超时了,需要更新全局系统时间。

static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) {
	    //调用epoll_wait获取事件 timer参数是在process_events调用时传入的
    events = epoll_wait(ep, event_list, (int) nevents, timer);
    err = (events == -1) ? ngx_errno : 0;

    /*
        Nginx对时间的缓存和管理 flags标志位指示要更新时间
        或者ngx_event_timer_alarm标志位1  即之前ngx_event_core_module模块设置的系统定时器触发 设置了标志位
    */
    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;
            }
            level = NGX_LOG_INFO;

        } else {
            level = NGX_LOG_ALERT;
        }

        return NGX_ERROR;
    }
    
 	//遍历本次epoll_wait返回的所有事件
    for (i = 0; i < events; i++) {
        // ...    
    }
    // ...
}

 

4、核心事件模块初始化init_process

在核心事件模块 ngx_event_core_module的初始化函数 ngx_event_process_init中,会对定时器进行初始化。

如果配置中设置了 ngx_timer_resolution参数,则设置指定时间的定时器,保证最长 ngx_timer_resolution就会触发信号,强制让 epoll_wait函数返回,即使没有网络事件的发生。

static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle) {
    // ...
    	//初始化红黑树实现的定时器
    if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
        return NGX_ERROR;
    }
    
     /*
        配置文件中设置了控制时间精度  根据配置时间设置指定时间的定时器
        这里是配合epoll_wait使用  开启了指定时间精度  epoll_wait会阻塞等待  直到有网络事件发生或者到了定时器指定的时间 发生信号导致epoll_wait返回
    */
    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) {
            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_signal_handler,非常简单就是设置 ngx_event_timer_alarm标志位1。这个标识会在其他地方在更新时间的时候使用到。

static void ngx_timer_signal_handler(int signo) {
    ngx_event_timer_alarm = 1;
}

 

 类似资料: