文章目錄
- Nginx架構淺析
- 1.Nginx基礎架構
- 2.Master行程
- 2.1核心邏輯
- 2.2熱更
- 2.2.1熱多載-配置熱更
- 2.2.2熱升級-程式熱更
- 3.Worker行程
- 3.1核心邏輯
- 3.2事件驅動-epoll
- 3.3驚群
- 3.4負載均衡
- 4.思考
- 4.1為什么不采用多執行緒模型管理連接?
- 4.2為什么不采用多執行緒處理邏輯業務?
- PPT下載
Nginx架構淺析
最近專案中使用了基于Nginx的OpenResty的框架,于是有了想學習了解Nginx相關內容的想法,現將一些理解撰寫成文,和大家討論一下,
1.Nginx基礎架構
nginx啟動后以daemon形式在后臺運行,后臺行程包含一個master行程和多個worker行程,如下所示:
nginx是由一個master管理行程,多個worker行程處理作業的多行程模型,基礎架構設計,如下所示:

master負責管理worker行程,worker行程負責處理網路事件,整個框架被設計為一種依賴事件驅動、異步、非阻塞的模式,
設計優點:
- 1.可以充分利用多核機器,增強并發處理能力,
- 2.多worker間可以實作負載均衡,
- 3.Master監控并統一管理worker行為,在worker例外后,可以主動拉起worker行程,從而提升了系統的可靠性,并且由Master行程控制服務運行中的程式升級、配置項修改等操作,從而增強了整體的動態可擴展與熱更的能力,
2.Master行程
2.1核心邏輯
master行程的主邏輯在ngx_master_process_cycle,核心關注原始碼:
ngx_master_process_cycle(ngx_cycle_t *cycle)
{
...
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
...
for ( ;; ) {
if (delay) {...}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
sigsuspend(&set);
ngx_time_update();
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"wake up, sigio %i", sigio);
if (ngx_reap) {
ngx_reap = 0;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
live = ngx_reap_children(cycle);
}
if (!live && (ngx_terminate || ngx_quit)) {...}
if (ngx_terminate) {...}
if (ngx_quit) {...}
if (ngx_reconfigure) {...}
if (ngx_restart) {...}
if (ngx_reopen) {...}
if (ngx_change_binary) {...}
if (ngx_noaccept) {
ngx_noaccept = 0;
ngx_noaccepting = 1;
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
}
}
}
由上述代碼,可以理解,master行程主要用來管理worker行程,具體包括如下4個主要功能:
- 1.接受來自外界的信號,其中master回圈中的各項標志位就對應著各種信號,如:
ngx_quit代表QUIT信號,表示優雅的關閉整個服務, - 2.向各個worker行程發送信,比如
ngx_noaccept代表WINCH信號,表示所有子行程不再接受處理新的連接,由master向所有的子行程發送QUIT信號量, - 3.監控worker行程的運行狀態,比如
ngx_reap代表CHILD信號,表示有子行程意外結束,這時需要監控所有子行程的運行狀態,主要由ngx_reap_children完成, - 4.當woker行程退出后(例外情況下),會自動重新啟動新的woker行程,主要也是在
ngx_reap_children
2.2熱更
2.2.1熱多載-配置熱更

nginx熱更配置時,可以保持運行中平滑更新配置,具體流程如下:
- 1.更新nginx.conf組態檔,向master發送SIGHUP信號或執行nginx -s reload
- 2.master行程使用新配置,啟動新的worker行程
- 3.使用舊配置的worker行程,不再接受新的連接請求,并在完成已存在的連接后退出
2.2.2熱升級-程式熱更

nginx熱升級程序如下:
- 1.將舊Nginx檔案換成新Nginx檔案(注意備份)
- 2.向master行程發送USR2信號(平滑升級到新版本的Nginx程式)
- 3.master行程修改pid檔案號,加后綴.oldbin
- 4.master行程用新Nginx檔案啟動新master行程
- 5.向老master行程發送QUIT信號,關閉老master行程
- 6.回滾:向老master發送HUP(重讀組態檔),向新master發送QUIT
3.Worker行程
3.1核心邏輯
worker行程的主邏輯在ngx_worker_process_cycle,核心關注原始碼:
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
ngx_int_t worker = (intptr_t) data;
ngx_process = NGX_PROCESS_WORKER;
ngx_worker = worker;
ngx_worker_process_init(cycle, worker);
ngx_setproctitle("worker process");
for ( ;; ) {
if (ngx_exiting) {...}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
ngx_process_events_and_timers(cycle);
if (ngx_terminate) {...}
if (ngx_quit) {...}
if (ngx_reopen) {...}
}
}
由上述代碼,可以理解,worker行程主要在處理網路事件,通過ngx_process_events_and_timers方法實作,其中事件主要包括:網路事件、定時器事件,
3.2事件驅動-epoll
worker行程在處理網路事件時,依靠epoll模型,來管理并發連接,實作了事件驅動、異步、非阻塞等特性,如下圖所示:

通常海量并發連接程序中,每一時刻(相對較短的一段時間),往往只需要處理一小部分有事件的連接即活躍連接,基于以上現象,epoll通過將連接管理與活躍連接管理進行分離,實作了高效、穩定的網路IO處理能力,

其中,epoll利用紅黑樹高效的增刪查效率來管理連接,利用一個雙向鏈表來維護活躍連接,

3.3驚群
由于worker都是由master行程fork產生,所以worker都會監聽相同埠,這樣多個子行程在accept建立連接時會發生爭搶,帶來著名的“驚群”問題,
worker核心處理邏輯ngx_process_events_and_timers核心代碼如下:
void ngx_process_events_and_timers(ngx_cycle_t *cycle){
//這里面會對監聽socket處理
...
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
//獲得鎖則加入wait集合,
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
...
//設定網路讀寫事件延遲處理標志,即在釋放鎖后處理
if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS;
}
}
...
//這里面epollwait等待網路事件
//網路連接事件,放入ngx_posted_accept_events佇列
//網路讀寫事件,放入ngx_posted_events佇列
(void) ngx_process_events(cycle, timer, flags);
...
//先處理網路連接事件,只有獲取到鎖,這里才會有連接事件
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
//釋放鎖,讓其他行程也能夠拿到
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}
//處理網路讀寫事件
ngx_event_process_posted(cycle, &ngx_posted_events);
}
由上述代碼可知,Nginx解決驚群的方法:
- 1.將連接事件與讀寫事件進行分離,連接事件存放為
ngx_posted_accept_events,讀寫事件存放為ngx_posted_events, - 2.設定
ngx_accept_mutex鎖,只有獲得鎖的行程,才可以處理連接事件,
3.4負載均衡
worker間的負載關鍵在于各自接入了多少連接,其中接入連接搶鎖的前置條件是ngx_accept_disabled > 0,所以ngx_accept_disabled就是負載聚恒機制實作的關鍵閾值,
ngx_int_t ngx_accept_disabled;
ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
因此,在nginx啟動時,ngx_accept_disabled的值就是一個負數,其值為連接總數的7/8,當該行程的連接數達到總連接數的7/8時,該行程就不會再處理新的連接了,同時每次呼叫’ngx_process_events_and_timers’時,將ngx_accept_disabled減1,直到其值低于閾值時,才試圖重新處理新的連接,因此,nginx各worker子行程間的負載均衡僅在某個worker行程處理的連接數達到它最大處理總數的7/8時才會觸發,其負載均衡并不是在任意條件都滿足,如下圖所示:

其中’pid’為1211的行程為master行程,其余為worker行程
4.思考
4.1為什么不采用多執行緒模型管理連接?
- 1.無狀態服務,無需共享行程記憶體
- 2.采用獨立的行程,可以讓互相之間不會影響,一個行程例外崩潰,其他行程的服務不會中斷,提升了架構的可靠性,
- 3.行程之間不共享資源,不需要加鎖,所以省掉了鎖帶來的開銷,
4.2為什么不采用多執行緒處理邏輯業務?
- 1.行程數已經等于核心數,再新建執行緒處理任務,只會搶占現有行程,增加切換代價,
- 2.作為接入層,基本上都是資料轉發業務,網路IO任務的等待耗時部分,已經被處理為非阻塞/全異步/事件驅動模式,在沒有更多CPU的情況下,再利用多執行緒處理,意義不大,并且如果行程中有阻塞的處理邏輯,應該由各個業務進行解決,比如openResty中利用了Lua協程,對阻塞業務進行了優化,
PPT下載
Nginx架構淺析PPT
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/271963.html
標籤:其他
