[導(dǎo)讀]“Nginx(enginex)是一個(gè)高性能的HTTP和反向代理Web服務(wù)器,同時(shí)也提供了IMAP/POP3/SMTP服務(wù)。圖片來自PexelsNginx以高性能和高可用性備受廣大程序員的青睞,今天我們會(huì)從Nginx的整體架構(gòu)入手,介紹Nginx進(jìn)程結(jié)構(gòu),進(jìn)程之間的關(guān)系以及如何對進(jìn)...
“ Nginx(engine x)是一個(gè)高性能的 HTTP 和反向代理 Web 服務(wù)器,同時(shí)也提供了 IMAP/POP3/SMTP 服務(wù)。
圖片來自 Pexels
Nginx 以高性能和高可用性備受廣大程序員的青睞,今天我們會(huì)從 Nginx 的整體架構(gòu)入手,介紹 Nginx 進(jìn)程結(jié)構(gòu),進(jìn)程之間的關(guān)系以及如何對進(jìn)程進(jìn)行控制和管理。
今天大家會(huì)學(xué)到如下內(nèi)容:
-
Nginx 總體架構(gòu)
-
Nginx 進(jìn)程定義
-
Nginx 啟動(dòng)過程
-
Master 啟動(dòng)過程
-
進(jìn)程之間的信號發(fā)送方式
-
進(jìn)程協(xié)助處理網(wǎng)絡(luò)請求
Nginx 總體架構(gòu)
對于傳統(tǒng)的 HTTP 和反向代理服務(wù)器而言,在處理并發(fā)請求的時(shí)候會(huì)使用單進(jìn)程或線程的模式處理,同時(shí)會(huì)止網(wǎng)絡(luò)或輸入/輸出操作。
這種方式會(huì)消耗大量的內(nèi)存和 CPU 資源。因?yàn)槊慨a(chǎn)生一個(gè)單獨(dú)的進(jìn)程或線程需要準(zhǔn)備一套新的運(yùn)行時(shí)環(huán)境,包括分配堆和堆棧內(nèi)存,以及創(chuàng)建新的執(zhí)行上下文。
可以想象在處理多請求時(shí)會(huì)生成對應(yīng)數(shù)目的線程或進(jìn)程,導(dǎo)致由于線程在不斷上下文切換上耗費(fèi)大量資源。
由于上面的原因,Nginx 在設(shè)計(jì)之初就使用了模塊化、事件驅(qū)動(dòng)、異步處理,非阻塞的架構(gòu)。
圖 1:Nginx 總體架構(gòu)
讓我們通過一張圖來了解 Nginx 的總結(jié)架構(gòu),如圖 1 所示:
①Nginx 啟動(dòng)時(shí),并不會(huì)馬上處理網(wǎng)絡(luò)請求,負(fù)責(zé)調(diào)度工作進(jìn)程。包括 Load configuration(加載配置),Launch workers(啟動(dòng)工作進(jìn)程)以及 Non-stop upgrade(平滑升級)。
因此在 Nginx 啟動(dòng)以后,在操作系統(tǒng)中會(huì)看到 Master 和 Worker 兩類進(jìn)程。在圖上方的 Master 進(jìn)程負(fù)責(zé)加載分析配置文件、啟動(dòng)/管理 Worker 進(jìn)程以及平滑升級。
一個(gè) Master 進(jìn)程可以管理多個(gè) Worker 進(jìn)程,而 Worker 進(jìn)程負(fù)責(zé)處理并響應(yīng)用戶請求,也就是來自圖左邊的 HTTP/HTTPS 請求。
②由于網(wǎng)絡(luò)請求屬于 IO 請求,為了應(yīng)對高并發(fā) Nginx 采取了 kevent/epoll/ select 模式的多路復(fù)用技術(shù)。由于采取了這種技術(shù)每個(gè) Worker 進(jìn)程都可以同時(shí)處理數(shù)以千計(jì)的網(wǎng)絡(luò)請求。
③為了處理網(wǎng)絡(luò)請求在 Worker 中會(huì)包含模塊,分為核心模塊和功能性模塊。
核心模塊負(fù)責(zé)維持一個(gè)運(yùn)行循環(huán)(run-loop),執(zhí)行網(wǎng)絡(luò)請求處理的不同階段的模塊功能,如網(wǎng)絡(luò)讀寫、存儲(chǔ)讀寫、內(nèi)容傳輸、外出過濾,以及將請求發(fā)往上游服務(wù)器等。
而圍繞著核心模塊會(huì)有一些功能模塊,就是實(shí)現(xiàn)具體的請求處理功能的。例如有處理 http 請求的 ht_core 模塊,有實(shí)現(xiàn)負(fù)載均衡的 ht_upstream 模塊,以及實(shí)現(xiàn) FastCGI 的 ht_fastcgi 模塊。
這些模塊會(huì)負(fù)責(zé)與后端的服務(wù)器進(jìn)行交互,完成用戶請求,同時(shí)可以根據(jù)需要的功能自由加載模塊,甚至可以擴(kuò)展第三方的模塊。
④Worker 進(jìn)程可以和本地磁盤進(jìn)行數(shù)據(jù)通信,支持 Advanced I/O(高級I/O)、sendfile 機(jī)制、AIO 機(jī)制、mmap 等機(jī)制等。
通過上面的介紹發(fā)現(xiàn) Nginx 不會(huì)為每個(gè)連接生成一個(gè)進(jìn)程或線程,而是通過 Worker 進(jìn)程使用多路復(fù)用的方式處理多個(gè)請求。
這里會(huì)使用到共享監(jiān)聽套接字的方式接受新請求,并在每個(gè) Worker 內(nèi)執(zhí)行高效的運(yùn)行循環(huán),從而達(dá)到每個(gè) Worker 處理成千上萬的連接。
Work 啟動(dòng)后將創(chuàng)建一組偵聽套接字,并且在處理 HTTP 請求和響應(yīng)過程中不斷接受,讀取和寫入套接字信息。
運(yùn)行循環(huán)(run-loop)包括全面的內(nèi)部調(diào)用,并且在很大程度上依賴異步任務(wù)處理。
通過模塊化,事件通知,回調(diào)函數(shù)和定時(shí)器來支撐異步操作的實(shí)現(xiàn)。其目的是為了實(shí)現(xiàn)高并發(fā)請求下的不阻塞(盡可能不阻賽)。
基于上面的機(jī)制,Nginx 通過檢查網(wǎng)絡(luò)和存儲(chǔ)的狀態(tài)并初始化新連接,將其添加到運(yùn)行循環(huán)中,并異步處理直到其完成,處理完畢的連接會(huì)被重新分配并從運(yùn)行循環(huán)中刪除。因此 Nginx 可以在極端工作負(fù)載下實(shí)現(xiàn)較低的CPU使用率。
另外,由于 Work 會(huì)在磁盤上進(jìn)行寫入操作,為了避免磁盤 I/O 上的阻塞請求,特別是磁盤滿的情況。
可以設(shè)置機(jī)制和配置文件指令來減輕此類磁盤 I/O 阻塞情況的發(fā)生。例如使用 sendfile 和 AIO 組合選項(xiàng)提升磁盤性能。
Nginx 進(jìn)程定義
從架構(gòu)介紹我們知道 Nginx 是由不同的進(jìn)程組成的,這些進(jìn)程各司其職用來處理高并發(fā)下的網(wǎng)絡(luò)請求,接下來就看看他們的定義和如何工作的。
上面介紹了 Nginx 的總體架構(gòu),其中重點(diǎn)提到了 Master 進(jìn)程和 Worker 進(jìn)程,其實(shí)還有另外兩個(gè)進(jìn)程在架構(gòu)中也起到了重要的作用。
圖 2:Nginx 的四類進(jìn)程
這里我們一起通過圖 2 來認(rèn)識他們:
-
Master 進(jìn)程:它作為父進(jìn)程會(huì)在 Nginx 初始化的時(shí)候生成并啟動(dòng),其他的進(jìn)程都是它子進(jìn)程,Master 進(jìn)程對其他進(jìn)行進(jìn)行創(chuàng)建和管理。
-
Worker 進(jìn)程:是 Master 的子進(jìn)程負(fù)責(zé)處理網(wǎng)絡(luò)請求。這里需要說明一下 Nginx 為什么采用了多進(jìn)程而不是多線程的結(jié)構(gòu),其原因是為了保證高可用性,進(jìn)程不像線程那樣共享地址空間,也避免了當(dāng)一個(gè)線程中的第三方模塊出錯(cuò)引而影響其他其他線程的情況發(fā)生。
-
Cache Manager 和 Cache Loader 進(jìn)程:Cache Loader 負(fù)責(zé)緩存載入,Cache Manager 負(fù)責(zé)緩存管理,每一個(gè)請求所使用的緩存還是由 Worker 來決定的,而進(jìn)程間通信都是通過共享內(nèi)存來實(shí)現(xiàn)的。
從上圖大家一定注意到了只有 Worker 進(jìn)程是多個(gè),這是因?yàn)?Nginx 采用了事件驅(qū)動(dòng)的模型。
為了提高處理請求的效率每個(gè) Worker 進(jìn)程會(huì)找那個(gè)一個(gè) CPU內(nèi)核,提高 CPU 的緩存命中率,將某個(gè) Worker 進(jìn)程與一個(gè) CPU 核綁定在一起。
需要說明的是,我們需要根據(jù)具體的應(yīng)用場景來定義 Worker 進(jìn)程的數(shù)量:
-
CPU 密集型請求,例如,處理大量 TCP/IP,執(zhí)行 SSL 或壓縮,需要 Worker 數(shù)與 CPU 內(nèi)核數(shù)量相匹配。
-
IO 密集型請求,Worker 數(shù)需要是 CPU 內(nèi)核數(shù)量的一到兩倍。
上面提到了 Master 通過控制多個(gè) Worker 進(jìn)程來處理網(wǎng)絡(luò)請求,對于 Worker 的獨(dú)立進(jìn)程來說使用資源的時(shí)候不需要考慮不需要加鎖的問題,節(jié)省了因?yàn)榧渔i帶來的系統(tǒng)開銷。同時(shí)多進(jìn)程的設(shè)計(jì)讓進(jìn)程之間不會(huì)互相影響。
當(dāng)一個(gè)進(jìn)程退出后,其它進(jìn)程還在工作,Nginx 所提供的網(wǎng)絡(luò)請求服務(wù)不會(huì)因?yàn)槠渲幸粋€(gè)進(jìn)程的退出而中斷,Master 進(jìn)程一旦發(fā)現(xiàn)有 Worker 進(jìn)程退出會(huì)啟動(dòng)新的 Worker 進(jìn)程。
這里我們會(huì)發(fā)現(xiàn) Master 進(jìn)程為了控制 Worker 需要對其進(jìn)行通信,同時(shí) Worker 進(jìn)程也需要與 Master 進(jìn)程交換信息。
Nginx 啟動(dòng)過程
談到了 Master 進(jìn)程如此的重要,那么一起來看看 Nginx 進(jìn)程的啟動(dòng)過程。圖 3:Nginx 啟動(dòng) Master 進(jìn)程
如圖 3 所示,在 Nginx 啟動(dòng)的時(shí)候會(huì)根據(jù)配置文件進(jìn)行解析和初始化的工作,同時(shí)會(huì)從主進(jìn)程中 fork 出一個(gè) Master 進(jìn)程作為自己的子進(jìn)程,也就是啟動(dòng) Master 進(jìn)程,此時(shí) Master 進(jìn)程就誕生了。
Nginx 的主進(jìn)程在 fork 出 Master 進(jìn)程以后就退出了。然后 Master 進(jìn)程會(huì) fork 并啟動(dòng) Worker 進(jìn)程,以及 Cache Manager 、Cache Loader 進(jìn)程,接著 Master 進(jìn)程會(huì)進(jìn)入主循環(huán)。
需要注意的是,這里使用的 fork 會(huì)復(fù)制一個(gè)和當(dāng)前啟動(dòng)進(jìn)程具有相同代碼段、數(shù)據(jù)段、堆和棧、fd 等信息的子進(jìn)程。
也就是說我們說的四類進(jìn)程都是通過 Nginx 啟動(dòng)進(jìn)程復(fù)制出來的子進(jìn)程。
Master 啟動(dòng)過程
接著上面的流程 Master 進(jìn)程被 fork 之后,會(huì)執(zhí)行 ngx_master_process_cycle 函數(shù)。
圖 4:Master 進(jìn)程執(zhí)行 ngx_master_process_cycle 函數(shù)
如圖 4 所示,這個(gè)函數(shù)主要進(jìn)行如下操作:
-
設(shè)置進(jìn)程的初始信號掩碼,屏蔽相關(guān)信號。
-
Master 進(jìn)程 fork 出 Worker 、Cache Manager 以及 Cache Loader 等子進(jìn)程。
-
進(jìn)入主循環(huán),通過 sigsuspend 系統(tǒng)調(diào)用,等待著信號的到來。
-
一旦信號到來,會(huì)進(jìn)入信號處理程序 ngx_signal_handler。
-
信號處理程序執(zhí)行之后,程序執(zhí)行流程會(huì)判斷各種狀態(tài)位,來執(zhí)行不同的操作。
上面的流程中提到了幾個(gè)概念,這里對其進(jìn)行說明,以便我們更好的理解 Master 進(jìn)程的執(zhí)行過程。
①信號:用來完成進(jìn)程中信息傳遞的媒介。Master 進(jìn)程的主循環(huán)里面,一直通過等待各種信號事件,來處理不同的指令。
這個(gè)信號可以傳遞給 Master 進(jìn)程,也可以從 Master 進(jìn)程傳遞給其他的進(jìn)程。
信號分為標(biāo)準(zhǔn)信號和實(shí)時(shí)信號,標(biāo)準(zhǔn)信號是從 1-31,實(shí)時(shí)信號是從 32-64。例如:INT、QUIT、KILL 就是標(biāo)準(zhǔn)信號。Master 進(jìn)程監(jiān)聽的信號也是標(biāo)準(zhǔn)信號。
標(biāo)準(zhǔn)信號和實(shí)時(shí)信號的區(qū)別是:標(biāo)準(zhǔn)信號,是基于位的標(biāo)記,假設(shè)在阻塞等待的時(shí)候,多個(gè)相同的信號到來,最終解除阻塞時(shí),只會(huì)傳遞一次信號,無法統(tǒng)計(jì)等待期間信號的計(jì)數(shù)。
而實(shí)時(shí)信號是通過隊(duì)列來實(shí)現(xiàn),在阻塞等待的時(shí)候,多個(gè)相同的實(shí)時(shí)信號會(huì)存放到隊(duì)列中。一旦解除阻塞的時(shí)候,會(huì)將隊(duì)列中的信號都進(jìn)行傳遞,結(jié)果會(huì)收到多次信號。
②信號處理器:信號處理器是指當(dāng)捕獲指定信號時(shí)(傳遞給進(jìn)程)時(shí)將會(huì)調(diào)用的一個(gè)函數(shù),它存在與進(jìn)程中,它可以隨時(shí)打斷進(jìn)程的主程序流程。
③發(fā)送信號:發(fā)送信號的操作可以使用 kill 這個(gè) shell 命令完成。比如 kill -9 pid,就是發(fā)送 KILL 信號。
kill -INT pid 就是發(fā)送 INT 信號。與 shell 命令類似,可以使用 kill 系統(tǒng)調(diào)用來向進(jìn)程發(fā)送信號。
④信號掩碼:用來控制信號阻賽的編碼方式。每個(gè)進(jìn)程都有一個(gè)信號掩碼(signal mask),也稱為信號屏蔽字,它規(guī)定了當(dāng)前要屏蔽或要阻塞遞送到該進(jìn)程的信號集。
對于每種可能的信號,該掩碼中都有一位與之對應(yīng)。對于某種信號,若其對應(yīng)位(bit)已設(shè)置,則它當(dāng)前是被阻塞的。
簡單地說,信號掩碼是一個(gè)“位圖”,其中每一位都對應(yīng)著一種信號。如果位圖中的某一位為 1,就表示在執(zhí)行當(dāng)前信號集的處理程序期間相應(yīng)的信號暫時(shí)被“屏蔽”或“阻塞”,使得在執(zhí)行的過程中不會(huì)嵌套地響應(yīng)那個(gè)信號。
說白了就是使用信號編碼來阻賽信號,告訴其他發(fā)送信號的進(jìn)程說:“我在忙著處理事情,你先等等,等會(huì)我再處理你的信號?!?/span>
進(jìn)程之間的信號發(fā)送方式
有了上面的基礎(chǔ)以后再回頭看看 Nginx 中進(jìn)程中是如何進(jìn)行信息交互的,以及 Master 是如何通過信號與 Worker 進(jìn)行溝通的。圖 5:Master 與 Worker 通信
從圖 5 中可以看出,Master 可以接受 TERM,INT、QUIT、HUP、USR1、USR2、WINCH 這些信號,這些信號的含義會(huì)在后面提供一張表給大家解釋。
同時(shí) Master 進(jìn)程也可以給Worker進(jìn)程傳遞信號,于是 Worker 進(jìn)程可以接收以下信號:TERM,INT、QUIT、HUP、USR1、WINCH。之后 Worker 再去響應(yīng) Client 的請求。
這里先將信號的對應(yīng)的命令和含義通過表格的方式列出來,再來對其進(jìn)行講解。
從表格上面看,每個(gè)信號都有特定的含義。比如 QUIT 信號表示優(yōu)雅地關(guān)閉服務(wù),并且對應(yīng) quit 命令。
這里的 quit 命令指的是可以通過命令行的方式對 Master 進(jìn)程下命令,從而達(dá)到發(fā)送信號的效果,當(dāng)然 Master 接受到命令以后會(huì)轉(zhuǎn)化為信號發(fā)送給對應(yīng)的 Worker 達(dá)到關(guān)閉服務(wù)的效果。
需要說明的是,Worker 是不會(huì)接受命令的,而是通過 Worker 接受命令來統(tǒng)一管理所有 Worker 的行為。
進(jìn)程協(xié)助處理網(wǎng)絡(luò)請求
知道 Master 與 Worker 之間如何通信之后再來看看它們是如何合作完成客戶端請求的。圖 6:Client 請求流程
如圖 6 所示,這里描繪了 Master 創(chuàng)建 Listen 以及 fork 出 Worker 的過程,以及客戶端請求和 Worker 響應(yīng)請求的過程。
①先從最上面開始看,順著從上至下紅色的箭頭看,Master 進(jìn)程創(chuàng)建以后會(huì)通過 socket 方法創(chuàng)建 socket 的 IO 通道。
接著執(zhí)行 bind 方法將其與監(jiān)聽器 listen 進(jìn)行綁定,然后通過 fork 方法 fork 出多個(gè) Worker 進(jìn)程(綠色虛線)。
②在每個(gè) Worker 進(jìn)程中的 accept 方法就監(jiān)聽 socket 請求了,一旦 listen 監(jiān)聽到 socket 請求 Worker 進(jìn)程就可以通過 accept 接受到。
③再看最下面的 client 模塊,當(dāng) client 通過 connect 方法與 Nginx 發(fā)生連接時(shí),所有擁有 accept 方法的 Worker 進(jìn)程都會(huì)接受到來自 listen 的通知,但是只有一個(gè) Worker 進(jìn)程能夠成功 accept 到,其他的進(jìn)程則會(huì)失敗。
這里 Nginx 提供了一把共享鎖 accept_mutex 來保證同一時(shí)刻只有一個(gè) Worker 進(jìn)程在 accept 連接,從而解決驚群問題。
④當(dāng) Worker 進(jìn)程 accept 到 socket 請求以后,client 會(huì)通過 send 方法發(fā)送請求(綠色虛線)給 Worker。
而 Worker 使用 recv 方法接受請求你,同時(shí)通過 parse(解析)、process(處理)、generate(生成響應(yīng))幾個(gè)步驟將返回的響應(yīng)通過 send 方法傳送給 client,而 client 會(huì)使用 recv 方法接受響應(yīng)。
最后 Worker 調(diào)用 close 方法斷開和 client 的連接。
總結(jié)
本文從 Nginx 總體架構(gòu)開始,介紹了 Nginx 的主要組成部分和處理流程。然后介紹 Nginx 的 4 個(gè)進(jìn)程,以及 Nginx 在啟動(dòng)過程中這些進(jìn)程都是如何產(chǎn)生的。
然后聚焦到最為主要的 Master 進(jìn)程的啟動(dòng)過程做了哪些具體的事情,特別是 Master 進(jìn)程和 Worker、Cache Manager、Cache Load 之間的關(guān)系。
在進(jìn)程之間的信號發(fā)送方式的章節(jié)中,我們建立了信號、發(fā)送信號、信號處理、信號掩碼的概念,這有助于理解進(jìn)程之間的通信。
最后,趁熱打鐵把 Nginx 接受網(wǎng)絡(luò)請求以及進(jìn)程之間如何合作處理請求的過程進(jìn)行了講解。
作者:崔皓
簡介:十六年開發(fā)和架構(gòu)經(jīng)驗(yàn),曾擔(dān)任過惠普武漢交付中心技術(shù)專家,需求分析師,項(xiàng)目經(jīng)理,后在創(chuàng)業(yè)公司擔(dān)任技術(shù)/產(chǎn)品經(jīng)理。善于學(xué)習(xí),樂于分享。目前專注于技術(shù)架構(gòu)與研發(fā)管理。
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時(shí)聯(lián)系本站刪除。