nginx時間更新機制(ngx_timer_resolution)

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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章