寫NGINX系列的隨筆,一來總結學到的東西,二來記錄下疑惑的地方,在接下來的學習過程中去解決疑惑。
也希望同樣對NGINX感興趣的朋友能夠解答我的疑惑,或者共同探討研究。
整個NGINX系列的文章中,我會將我的疑惑用紅色標出,希望能遇到前輩在評論中給我解答迷津。
在介紹定時器之前,先簡要說下nginx處理事件的流程和方式。
Worker進程的主要流程:
1 static void 2 ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data){ 3 for(;;) { 4 if(ngx_exiting) {} 5 ngx_process_events_and_timers(cycle); 6 if (ngx_terminate) {} 7 if (ngx_quit) {} 8 if (ngx_reopen) {} 9 } 10 11 void 12 ngx_process_events_and_timers(ngx_cycle_t *cycle) { 13 (void) ngx_process_events(cycle, timer, flags); 14 }
ngx_process_events調用epoll(linux下)實現事件的處理。
ngx_process_events處理事件有兩種方式:一是直接調用處理函數處理,二是將事件放到post隊列中,函數返回後再處理隊列中的事件。
在使用了 NGX_POST_EVENTS標記時,ngx_process_events不直接處理事件,將事件放到Post隊列中,待函數返回,再在隊列中取出事件處理。
因為調用ngx_process_events會加鎖(為什麼加鎖?),函數返回後,將鎖釋放再處理事件,可以減少鎖的占用時間。
以上是nginx處理事件的大體方式,下面介紹nginx中定時器的實現。
Nginx定時器使用紅黑樹組織(為什麼使用紅黑樹,紅黑樹效率高到哪?可以研究下)存儲,這個不多說。
Nginx定時器的觸發有兩種方式,第一種是設置時間信號。
ngx_event_process_init函數中 ,設置了時間信號,每隔固定時間觸發,時間信號的處理函數,只是設置ngx_event_timer_alarm = 1,但他會中斷ngx_process_events中epoll_wait的處理,epoll_wait返回後,調用ngx_time_update更新時間,接著返回到函數ngx_process_events_and_timers中處理,ngx_process_events_and_timers中,會調用ngx_event_expire_timers,查詢超時的事件並處理。
但這種方式有個問題,如果事件信號是在處理IO事件時(epoll_wait調用之後)發生的,那麼定時器的查詢遍歷,只能到下一次epoll_wait調用時才會處理,如果這時有IO事件發生,那麼epoll_wait可以立即返回,然後因為上次信號發生已經置ngx_event_timer_alarm = 1,可以立即更新時間,ngx_process_events返回後可以處理定時器事件。但如果沒有IO事件發生,epoll_wait會阻塞到下次時間信號到來,然後處理定時器事件,這樣豈不大大降低了定時器的精確度。這塊nginx怎麼處理的?
另外每隔固定時間(具體設置的時間信號的時間)才更新時間值,甚至可能是兩倍時間信號的時間才更新時間值,那麼代碼中在插入的定時器,實際觸發時間和理論時間就會有這麼大的誤差。是不是這樣呢?
Nginx定時器的第二種觸發方式是利用epoll_wait的超時。
每次在調用epoll_wait之前,nginx都會取得下一個最小(最早要觸發)的定時器的時間值,然後拿這個值作為epoll_wait的超時時間。這樣epoll_wait在返回後就可以處理超時事件了。既可以在頻繁IO的情況下處理超時,又可以在IO少量的情況下處理超時。
這種方式epoll_wait返回後,都會先更新時間,這樣epoll_wait返回後,在IO事件的處理代碼中加入定時器,誤差不會太大,因為時間剛剛被更新。
但這個方法的問題是,IO頻繁的情況下,也會頻繁更新時間,是否會影響性能?
這兩種方式各自的優缺點是哪些呢?