目錄
原文出處:https://www.cnblogs.com/dz11/p/10215089.html
NGINX 在網絡應用中表現超羣,在於其獨特的設計。許多網絡或應用服務器大都是基於線程或者進程的簡單框架,NGINX突出的地方就在於其成熟的事件驅動框架,它能應對現代硬件上成千上萬的併發連接。
NGINX 內部信息圖從進程框架的頂層開始,向下逐步揭示NGINX如何處理單個進程中的多個連接,並進一步探討其工作機制。
一、場景設置 — NGINX進程模型
爲了更好地理解這種設計模式,我們需要明白NGINX是如何運行的。NGINX擁有一個主線程,用來處理配置文件的讀取、端口的綁定等特權操作,以及一組工作進程、輔助進程。
在這個四核服務器中,主線程創建了四個工作進程和一組緩存輔助進程(cache helper processes),後者用來管理硬盤緩存。
二、爲什麼框架如此重要?
任何Unix應用的基礎是線程或者進程-對於Linux操作系統,線程和進程幾乎相同;最大的區別在於線程間是內存共享的。一個線程或者進程是一套指令集(self-contained set of instructions ),操作系統調度這些指令在單個CPU內核上運行。許多複雜應用並行地運行在多個線程或者進程,原因有二:
- 應用可以同時使用計算機的多個CPU核
- 線程和進程易於並行操作,比如同時處理多個連接
進程和線程消耗資源,比如對內存以及其它操作系統資源的佔用、內核切換(wapped on and off the cores)(本操作叫做一次上下文切換(context switch))。如今的服務器需要同時處理成千個小的、活躍線程或者進程,一旦內存耗盡、或者過高的讀寫負載,這些都會導致大規模的上下文切換,性能會嚴重退化。
通常的設計思路是,網絡應用爲每個連接分派一個線程或者進程。這類框架簡單易於實現,不過在同時應對成千上萬個連接時難以擴展。
三、NGINX是如何運作的呢?
NGINX利用一個預測進程模型調度可用的硬件資源:
- 主進程處理配置文件讀取、端口綁定等特權操作,以及創建一小組子進程(接下來三種類型的進程)
- 啓動時緩存加載器進程加載硬盤中緩存到內存中,接着退出。對它的調度是保守的,所以資源開銷較低
- 緩存管理進程定時運行,清理來自硬盤緩存的實體到指定的大小
- 工作進程負責所有的工作,處理網絡連接、硬盤讀寫操作、以及上游服務器通信
NGINX推薦的配置是,一個工作進程對應一個CPU內核,確保硬件資源的有效利用,在配置文件中設置worker_processes auto:
- worker_processes auto;
一旦NGINX服務起來,僅有工作進程在忙,每個工作進程採用非阻塞地方式處理多個連接,降低上下文切換的次數。
每個工作進程都是單線程且獨立運行,負責獲取新連接並進行處理。進程之間通過共享內存進行通信,諸如緩存數據,會話持續化數據(ession persistence data),以及其他共享資源。NGINX1.7.11及以後的版本,有一個可選的線程池,工作進程將阻塞操作丟給它們。更多細節,參看《Nginx 引入線程池,提升 9 倍性能》(http://blog.jobbole.com/87988/)。對於NGINX Plus用戶,這些新特性會在今年的發佈版7中出現。
四、NGINX內部工作進程
每個NGINX工作進程由配置文件對其進行初始化,主進程爲其提供一組監聽socket。
工作進程起始於socket監聽事件(accept_mutex 和 kernel socket sharding),事件由新的連接進行初始化,接着這些連接被派發給某個狀態機—HTTP狀態機是其中最常用的一種,不過NGINX也實現了基於流的狀態機、基於通信協議的狀態機(SMTP, IMAP, and POP3)。
狀態機是一組重要的指令集,它會告訴NGINX怎樣處理每個請求。許多網絡服務器擁有NGINX的狀態機一樣的功能—區別就在於它們的實現不同。
調度狀態機
狀態機就像下象棋,單個HTTP事務如同一盤棋。棋盤的一端是網絡服務器—就像大師級棋手非常快地做出決定,另一端爲遠程客戶端—網絡瀏覽器通過相對較慢的網絡訪問某個站點或應用。
不過遊戲規則可能非常複雜,比如網絡服務可能需要和第三方、或者某個認證服務器通信,甚至服務器中的第三方模塊來擴展遊戲規則。
阻塞狀態機
回到前面的描述,進程或者線程作爲一套指令集,操作系統調度其運行在某個CPU內核上。大多數網絡服務器和網絡應用按照一個進程處理一個連接,或者一個線程處理一個連接的模型來玩象棋遊戲;每個包含指令的進程或者線程參與遊戲的整個過程。在這期間,運行在服務器上進程大多數時間被阻塞掉了,即等待某個客戶端去完成下一步棋。
【1】網絡服務器進程監聽socket上的新連接,此遊戲新連接由客戶端發起。
【2】一旦獲得新遊戲,進入遊戲環節,每一次移動都需等待客戶端響應,進程就被阻塞了。
【3】一旦遊戲結束,網絡服務器進程就會查看客戶端是否想再來一局(對應某個存活的連接)。一旦連接關閉(客戶端離開或者超時),網絡服務器進程就會返回監聽新的遊戲。
【4】記住每一個活躍的HTTP連接即每一局象棋遊戲,需要象棋大師一般的特定進程或者線程參與其中。這個架構簡單易於擴展第三方模型即新的規則。然而,這裏存在一個極不平衡的邏輯,對於相關輕量級的HTTP連接,由單個文件描述符和少量的內存表示,此連接會映射到某個線程或進程上,而線程或者進程是一個重量級的操作系統對象。儘管編程時很方便,但浪費卻是巨大的。
五、NGINX是一個真正的大師
或許你聽說過同時展示遊戲,一個象棋大師同時對陣十二個棋手。
NGINX工作進程也是這麼玩”象棋”的,每個工作進程-一個CPU內核上的工作者-即是一個可以同時應對成千上萬遊戲的大師。
【1】工作進程從已連接並開始監聽的套接字(socket)那裏獲取事件;
【2】一旦socket接收到事件,工作進程會立即處理此事件:
- socket上的某個監聽事件即客戶端開啓一個新的象棋遊戲,而工作進程創建一個新的socket連接。
- socket連接上的某個事件即客戶端走了一步棋,工作線程做出了恰當地響應。
工作進程從來不會阻塞在網絡傳輸上等待它的對手(客戶端)回覆應答。每走完一步棋後,工作進程會迅速處理其它等待的象棋遊戲,或者歡迎新的遊戲玩家進入。
爲何比阻塞、多進程框架快呢?
NGINX良好的擴展性在於其支持一個工作線程處理成千上萬個連接。每個新連接創建文件描述符,僅消耗工作進程很少一部分額外內存,額外的開銷很小。進程能夠一直綁定CPU(pinned to CPUs),這樣上下文切換相對沒有那麼頻繁,只有沒工作時纔會發生。
譯者注:cpu綁定是指綁定一個或者多個進程到一個或者多個處理器上.
使用阻塞方式,即一個連接對應一個進程,每個連接需要大量的額外資源以及開銷,上下文切換非常頻繁。
只要恰當的系統調優,NGINX每個工作進程可以處理成千上萬個併發HTTP連接,毫無差錯地應對網絡高峯,即同時可以玩更多的象棋遊戲。
六、更新配置文件升級NGINX
進程框架擁有少量工作進程,有利配置文件甚至二進制文件更新。
更新NGINX配置是一個簡單、輕量級的可靠操作。即只要運行nginx -s reload命令,就會檢查磁盤上的配置文件,並給主進程發送一個SIGHUB信號。
一旦主進程接受到一個SIGHUB,它會做兩件事:
- 重載配置文件、創建一組新的工作進程,新創建的工作進程立即接受連接、處理網絡通信( 採用新的配置環境)。
- 通知舊的工作進程優雅地推出,這些工作進程停止接受新連接。一旦當前處理的HTTP請求結束,工作進程會關閉連接。一旦所有連接關閉,工作進程就會退出。
重載進程會引起一個小的CPU和內存高峯,不過從活躍連接處加載的資源相比,開銷微乎其微。每一秒可以多次重載配置文件。產生諸多等待連接關閉的NGINX工作進程一般很少出問題,不過就算是有問題也可以迅速解決。
NGINX二進文件升級獲得極佳的高可用性-你可以在線升級文件,而且不會丟失任何連接、服務也不會停機或中斷。
譯者注: on the fly 程序在運行時,工作就可以完成。
二進制文件升級進程方式類似優雅的配置文件重載;新的NGINX主進程和原有的主進程並行,分享監聽socket。兩個進程都處於活躍狀態,處理它們各自的網絡通信。你可以通知原有的主進程以及它的工作進程優雅地退出。
七、結語
NGINX內部信息圖展示了NGINX的高標準功能全景圖,簡單解釋的背後是十多年來不斷創新優化,得益於此NGINX被廣泛應用於各種硬件平臺,並且取得了最優異的性能表現。即便是在現代,網絡應用需要對安全和可靠性作出維護,NGINX也表現不凡。
願你就像早晨八九點鐘的太陽,活力十足,永遠年輕。