Nginx 事件基本處理流程分析

Nginx 事件基本處理流程分析

https://www.cnblogs.com/NerdWill/p/4989859.html

說明:本文章重點關注事件處理模型。有興趣的同學可以去http://tengine.taobao.org/book/查找更多資料。Tengine應該是淘寶基於Nginx自己做的修改。這個地址的文檔還在不斷的完善更新中,內容算是比較全面的。

 程序流程圖:

說明:

 

一、進程生成順序

1.main(src/core/nginx.c)函數啓動ngx_master_process_cycle,啓動主服務進程。

2.ngx_master_process_cycle(src/os/unix/ngx_process_cycle.c)裏面調用ngx_start_worker_processes.

3.ngx_start_worker_processes(src/os/unix/ngx_process_cycle.c)裏面循環執行生成特定數量的進程池。

4.ngx_spawn_process(src/os/unix/ngx_process.c)根據指定的respawn選項fork子進程,相關子進程信息保存在全部ngx_processes數組(ngx_process_t    ngx_processes[NGX_MAX_PROCESSES])中,子進程的運行過程通過參數proc指定,這裏是ngx_worker_process_cycle(src/os/unix/ngx_process_cycle.c)。在fork之前先通過socketpair生成master process和worker process間進行通信的channel[2]。

master_process進程作爲Nginx的服務主進程,管理其他子進程的生存週期,包括cache_manager_processes子進程,全部worker_processes,信號處理,timer等。

 

二、timer定時器超時處理機制

上圖中的左側紅色1,2,3步構成了timer和select/poll/epoll_wait等等待函數配合使用的基本流程,libevent裏面的timer處理機制也是一樣的,即:

1.從timer樹(一般使用紅黑樹)中取出最小的timer;

2.傳入epoll_wait等I/O複用函數;

3.處理堆中的timer超時事件。

4.處理正常的連接事件。

流程圖:

補充:

由於需要對大量timer進行實時增刪和檢索,所以需要效率比較高的結構,紅黑樹是理想選擇。

概念上說管理timer的樹應該使用最小堆,每次只需要從樹根取出最小的timer。但是堆得問題是插入和刪除時都可能需要從根到葉節點的log(n)次交換,代價較大。

而紅黑樹的好處是插入和刪除時最多需要不超過3次旋轉操作,雖然總的複雜度都是O(logN),但基本都是顏色變換和key比較等簡單操作,不涉及節點交換(值交換)和旋轉(指針交換)。所以,從統計性能(可理解爲cpu實際執行的指令數)來說,紅黑樹優於堆和AVL樹。

 

三、worker process I/O處理流程

每個worker process的運行過程ngx_worker_process_cycle如下:

複製代碼

 1 static void
 2 ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
 3 {
 4     ngx_int_t worker = (intptr_t) data;
 5 
 6     ngx_process = NGX_PROCESS_WORKER;
 7     ngx_worker = worker;
 8 
 9     ngx_worker_process_init(cycle, worker);
10 
11     ngx_setproctitle("worker process");
12 
13     for ( ;; ) {
14 
15         if (ngx_exiting) {
16             ngx_event_cancel_timers();
17 
18             if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel)
19             {
20                 ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
21 
22                 ngx_worker_process_exit(cycle);
23             }
24         }
25 
26         ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
27 
28         ngx_process_events_and_timers(cycle);
29 
30         if (ngx_terminate) {
31             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
32 
33             ngx_worker_process_exit(cycle);
34         }
35 
36         if (ngx_quit) {
37             ngx_quit = 0;
38             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
39                           "gracefully shutting down");
40             ngx_setproctitle("worker process is shutting down");
41 
42             if (!ngx_exiting) {
43                 ngx_exiting = 1;
44                 ngx_close_listening_sockets(cycle);
45                 ngx_close_idle_connections(cycle);
46             }
47         }
48 
49         if (ngx_reopen) {
50             ngx_reopen = 0;
51             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
52             ngx_reopen_files(cycle, -1);
53         }
54     }
55 }

複製代碼

可以看出,裏面除了exiting,terminate,quit和reopen等控制操作外,只有ngx_process_events_and_timers(src/event/ngx_event.c)。再看ngx_process_events_and_timers函數:

複製代碼

 1 void
 2 ngx_process_events_and_timers(ngx_cycle_t *cycle)
 3 {
 4     ngx_uint_t  flags;
 5     ngx_msec_t  timer, delta;
 6 
 7     if (ngx_timer_resolution) {
 8         timer = NGX_TIMER_INFINITE;
 9         flags = 0;
10 
11     } else {
12         timer = ngx_event_find_timer(); 
13         flags = NGX_UPDATE_TIME;
14 
15 #if (NGX_WIN32)
16 
17         /* handle signals from master in case of network inactivity */
18 
19         if (timer == NGX_TIMER_INFINITE || timer > 500) {
20             timer = 500;
21         }
22 
23 #endif
24     }
25 
26     if (ngx_use_accept_mutex) {
27         if (ngx_accept_disabled > 0) {
28             ngx_accept_disabled--;
29 
30         } else {
31             if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
32                 return;
33             }
34 
35             if (ngx_accept_mutex_held) {
36                 flags |= NGX_POST_EVENTS;
37 
38             } else {
39                 if (timer == NGX_TIMER_INFINITE
40                     || timer > ngx_accept_mutex_delay)
41                 {
42                     timer = ngx_accept_mutex_delay;
43                 }
44             }
45         }
46     }
47 
48     delta = ngx_current_msec;
49 
50     (void) ngx_process_events(cycle, timer, flags);
51 
52     delta = ngx_current_msec - delta;
53 
54     ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
55                    "timer delta: %M", delta);
56 
57     ngx_event_process_posted(cycle, &ngx_posted_accept_events);
58 
59     if (ngx_accept_mutex_held) {
60         ngx_shmtx_unlock(&ngx_accept_mutex);
61     }
62 
63     if (delta) {
64         ngx_event_expire_timers();
65     }
66 
67     ngx_event_process_posted(cycle, &ngx_posted_events);
68 }

複製代碼

1.函數開頭7-24行計算定時器timer,用於後面的ngx_process_events(50行)函數,最終用於各種I/O複用函數的timeout參數,如epoll_wait的timeout參數。ngx_process_events是一個宏,#define ngx_process_events   ngx_event_actions.process_events(src/event/ngx_event.h)。全局變量ngx_event_actions在各個event module的init方法裏面設置,如ngx_epoll_init(src/event/modules/ngx_epoll_module.c)中ngx_event_actions = ngx_epoll_module_ctx.actions;。而各個module的init方法的調用需要通過Nginx指定使用哪種類型的module來設置。相關初始設置在ngx_event_core_init_conf(src/event/ngx_event.c)裏面指定。

2.63-65行處理timer超時事件。

以epoll module爲例,ngx_process_events最終指向ngx_epoll_process_events(src/event/modules/ngx_epoll_module.c)。

複製代碼

 1 static ngx_int_t
 2 ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
 3 {
 4    
 5     ......
 6     //將最近的timer作爲epoll_wait的超時
 7     events = epoll_wait(ep, event_list, (int) nevents, timer);
 8 
 9     err = (events == -1) ? ngx_errno : 0;
10 
11     ......
12     //處理全部事件
13     for (i = 0; i < events; i++) {
14         
15        ......
16 
17         if ((revents & EPOLLIN) && rev->active) {
18 
19             rev->ready = 1;
20 
21             if (flags & NGX_POST_EVENTS) {
22                 queue = rev->accept ? &ngx_posted_accept_events
23                                     : &ngx_posted_events;
24 
25                 //將事件放入隊列,稍後處理
26                 ngx_post_event(rev, queue);
27 
28             } else {
29                 rev->handler(rev);
30             }
31         }
32 
33         wev = c->write;
34 
35         if ((revents & EPOLLOUT) && wev->active) {
36 
37            ......
38 
39             wev->ready = 1;
40 
41             if (flags & NGX_POST_EVENTS) {
42                 //將事件放入隊列,稍後處理
43                 ngx_post_event(wev, &ngx_posted_events);
44 
45             } else {
46                 wev->handler(wev);
47             }
48         }
49     }
50 
51     return NGX_OK;
52 }

複製代碼

 

可以看出,前面計算得到的timer傳進了epoll_wait裏面。其實這個timer的值是通過函數 ngx_event_find_timer從全部的timer組成的最小堆(Nginx裏面使用RB tree)裏面取出來的最小timer值。 epoll_wait之後就是常見的事件處理了。將事件放入隊列主要是爲快速釋放accept鎖,給其他worker進程機會去處理accept事件。

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