當(dāng)前位置:首頁 > 公眾號精選 > 架構(gòu)師社區(qū)
[導(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)系本站刪除。

架構(gòu)師社區(qū)

1739 篇文章

關(guān)注

發(fā)布文章

編輯精選

技術(shù)子站

關(guān)閉