這兩天着重看nginx事件模塊的處理,這一部分跟網絡框架比較大。跟現在工作內容相關性也比較大,總結一下它在這方面的處理方式,下次寫代碼的時候可以用一下。得到了2點啓發:
1. 防止epoll驚羣,可以採用鎖的方式處理,但是不能等epoll響應的事件處理完再釋放鎖,這樣鎖的佔用時間會比較長,從而使得其他的進程無法epoll wait,拖慢效率。可以先把事件加入連接池,釋放鎖,然後再處理。
2. 負載均衡的粗略實現。下面有描述。
同時也有些許疑問。
1. 這些listen的處理可以理解,但當普通連接,他轉發給server之後,如何做處理還不清楚,這點需要更層次的閱讀代碼來理解。
2. ngx_process_events_and_timers 代碼當中參雜了一些定時器的實現,這個定時器與cache manage process有關,需要理清楚一些。
首先nginx整個流程處理依賴的是一個cycle的數據結構,這個數據結構當中保存了一個ngx_listening_t鏈表,紀錄的是nginx監聽的信息。在init_cycle的時候會根據配置文件來初始化這個鏈表,並且調用ngx_open_listening_sockets函數來bind和listen。
注意這個init_cycle是在主進程內進行初始化的,所以在spawn worker process的時候會複製一份給worker process。
現在來看
ngx_event.c :ngx_event_process_init
的部分代碼,這部分代碼包含了許多東西,下面來看下這代碼,他預先分配了連接池和讀事件,寫事件。
cycle->connections =ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
//預先分配connection_n個連接
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,cycle->log);
//預先分配connection_n個讀事件和寫事件
i = cycle->connection_n;
next = NULL;
do {
i--;
c[i].data = next;
c[i].read = &cycle->read_events[i];
c[i].write = &cycle->write_events[i];
c[i].fd = (ngx_socket_t) -1;
next = &c[i];
} while (i);
//這個循環將預先分配的讀事件和寫事件分配到連接。
cycle->free_connections = next;
cycle->free_connection_n = cycle->connection_n;
//構造空閒連接池。
- 繼續往下閱讀
ngx_event.c :ngx_event_process_init
的部分代碼。下面這部分代碼就跟監聽事件註冊到epoll 有關了。值得注意的一點,這邊會有TCP和UDP兩種協議,以下討論的是TCP協議,UDP不予考慮。
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
c = ngx_get_connection(ls[i].fd, cycle->log);
// 根據監聽端口的fd,從以上的空閒connection池獲取連接
if (c == NULL) {
return NGX_ERROR;
}
c->type = ls[i].type;
c->log = &ls[i].log;
c->listening = &ls[i];
ls[i].connection = c;
rev = c->read;
rev->log = c->log;
rev->accept = 1;
rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept
: ngx_event_recvmsg;
// 根據type 是TCP還是UDP來分配回調函數是accept還是recvmsg。
}
- 上面講的是epoll add的部分,所以現在自然而然要討論epoll wait的部分了。但是呢這邊先討論一個”驚羣” 現象的處理了。其實思想比較簡單,就是加鎖,但是呢,因爲加鎖的會堵塞,影響性能, 所以先判斷然後鎖。
代碼位置爲ngx_event:ngx_process_events_and_timers
這邊擼代碼會比較多, 所以只列一些關鍵函數, 並說明其做的事情。
ngx_trylock_accept_mutex(); //獲取鎖,獲取不到就直接返回。
ngx_process_events();// 這是一個宏,根據配置文件,定義處理的方法,如果是epoll module 則做3件事情,
// epoll wait()
// 根據epoll的返回,添加事件到隊列
// 根據read事件的標誌位accept來區分什麼accept事件還是recv事件,從而添加到不同的隊列
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
//處理accept事件隊列的數組,值得注意,這時候不unlock鎖,至於爲什麼?現在不明
ngx_event_process_posted(cycle, &ngx_posted_events);
- nginx如何做負載均衡的呢?
用下面的ngx_accept_disabled 變量來控制負載的均衡,ngx_accept_disabled 表示空閒連接數的7/8,
如果小於0.則直接處理事件池的事件了。
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
}