Nginx(發音為“ engine x”)是由俄羅斯軟件工程師Igor Sysoev編寫的免費開源Web服務器。自2004年公開發布以來,nginx一直致力于高性能,高并發性和低內存使用率。Web服務器功能之上的其他功能,例如負載平衡,緩存,訪問和帶寬控制,以及與各種應用程序高效集成的能力,已使Nginx成為現代網站體系結構的理想選擇。當前,nginx是Internet上第二受歡迎的開源Web服務器。
為什么高并發很重要?
如今,互聯網是如此的廣泛和無所不在,很難想象十年前它并不完全存在。它已經從基于NCSA的簡單html產生可點擊文本,然后基于Apache Web服務器發展到了始終在線的通信介質,該通信介質已被全世界超過20億用戶使用。隨著永久連接的PC,移動設備和最近的平板電腦的普及,互聯網格局正在迅速變化,整個經濟體已經實現了數字化連接。在線服務變得更加復雜,并且對即時可用的實時信息和娛樂有明顯的偏見。運行在線業務的安全性方面也已發生重大變化。因此,網站現在比以前復雜得多,
并發一直是網站架構師面臨的最大挑戰之一。自Web服務開始以來,并發水平一直在不斷提高。一個受歡迎的網站為成千上萬甚至數百萬的同時用戶提供服務的情況并不少見。十年前,并發的主要原因是客戶端速度較慢,即具有ADSL或撥號連接的用戶。如今,并發是由移動客戶端和較新的應用程序體系結構的結合引起的,這些體系結構通常基于維護持久性連接,該持久性連接允許使用新聞,推文,朋友供稿等更新客戶端。導致并發性增加的另一個重要因素是現代瀏覽器的行為發生了變化,現代瀏覽器打開了網站的四到六個同時連接,以提高頁面加載速度。
為了說明客戶端速度慢的問題,請想象一個簡單的基于Apache的Web服務器,它會產生相對較短的100 KB響應,即帶有文本或圖像的Web頁面。生成或檢索此頁面僅需不到一秒鐘的時間,但是將其傳輸到帶寬為80 kbps(10 KB / s)的客戶端需要10秒鐘。從本質上講,Web服務器將相對快速地提取100 KB的內容,然后在繁忙的10秒鐘之內緩慢地將此內容發送到客戶端,然后再釋放其連接。現在,假設您有1,000個同時連接的客戶端,他們請求了類似的內容。如果每個客戶端僅分配1 MB的額外內存,則將導致1000 MB(約1 GB)的額外內存專用于僅為1000個客戶端提供100 KB的內容。事實上,典型的基于Apache的Web服務器通常為每個連接分配超過1 MB的額外內存,令人遺憾的是,數十kbps仍然經常是移動通信的有效速度。盡管可以通過增加操作系統內核套接字緩沖區的大小在某種程度上改善將內容發送到速度較慢的客戶端的情況,但這并不是解決問題的一般方法,并且可能會帶來不良的副作用。
對于持久連接,處理并發性的問題更加明顯,因為要避免與建立新的HTTP連接相關的延遲,客戶端將保持連接狀態,并且對于每個連接的客戶端,Web服務器都會分配一定數量的內存。
因此,為了處理與不斷增長的受眾相關的工作量增加以及更高級別的并發性并能夠持續做到這一點,網站應基于許多非常有效的構建基塊。雖然等式的其他部分(例如硬件(CPU,內存,磁盤),網絡容量,應用程序和數據存儲體系結構)很重要,但在Web服務器軟件中,接受并處理了客戶端連接。因此,Web服務器應該能夠隨著每秒同時發生的連接和請求數量的增長而非線性擴展。
Apache不適合嗎
Apache,至今仍在互聯網上占主導地位的Web服務器軟件,其起源可追溯到1990年代初。最初,它的體系結構與當時存在的操作系統和硬件相匹配,但也與Internet的狀態相匹配,在Internet上,網站通常是運行單個Apache實例的獨立物理服務器。到2000年代初,很明顯,無法輕松地復制獨立的Web服務器模型來滿足不斷增長的Web服務的需求。盡管Apache為將來的開發提供了堅實的基礎,但它的架構是為每個新連接生成自己的副本,這不適用于網站的非線性可伸縮性。最終,Apache成為了通用Web服務器,致力于提供許多不同的功能,各種第三方擴展,以及對幾乎任何類型的Web應用程序開發的普遍適用性。但是,價格不菲,而且在單個軟件中擁有如此豐富而通用的工具組合的缺點是可伸縮性較差,這是因為每個連接的CPU和內存使用量增加了。
因此,當服務器硬件,操作系統和網絡資源不再是網站增長的主要限制時,全世界的Web開發人員開始四處尋找運行Web服務器的更有效方法。大約十年前,著名的軟件工程師Daniel Kegel宣稱“現在是Web服務器同時處理一萬個客戶端的時候了”,并預測了我們現在所說的Internet云服務。凱格爾(Kegel)的C10K清單激發了許多嘗試來解決Web服務器優化問題,以同時處理大量客戶端,而Nginx證明是最成功的解決方案之一。
為了解決10,000個并發連接的C10K問題,nginx在編寫時就考慮了另一種體系結構,該體系結構更適合于同時連接數和每秒請求數方面的非線性可伸縮性。nginx是基于事件的,因此它不遵循Apache的樣式來為每個網頁請求生成新的進程或線程。最終結果是,即使負載增加,內存和CPU使用率仍可控制。現在,nginx可以在具有典型硬件的服務器上提供數以萬計的并發連接。
Nginx的第一個版本發布時,它打算與Apache一起部署,以便Nginx處理諸如HTML,css,JAVAScript和圖像之類的靜態內容,以減輕基于Apache的應用程序服務器的并發性和延遲處理。在其開發過程中,nginx通過使用FastCGI,uswgi或SCGI協議以及與分布式內存對象緩存系統(如memcached),增加了與應用程序的集成。還添加了其他有用的功能,例如帶有負載平衡和緩存的反向代理。這些附加功能使Nginx變成了可在其上構建可伸縮Web基礎結構的工具的有效組合。
2012年2月,Apache 2.4.x分支向公眾發布。盡管此最新版本的Apache添加了旨在增強可伸縮性和性能的新的多處理核心模塊和新的代理模塊,但現在判斷其性能,并發性和資源利用率是否與純事件同等或尚好還為時過早。驅動的Web服務器。不過,很高興看到Apache應用服務器在新版本上可以更好地擴展,因為它可以緩解后端方面的瓶頸,而瓶頸通常在典型的nginx-plus-Apache Web配置中仍然無法解決。
使用nginx還有更多優勢嗎
始終以高性能和高效率來處理高并發性一直是部署nginx的主要好處。但是,現在有更多有趣的好處。
在過去的幾年中,Web架構師接受了將應用程序基礎結構與Web服務器分離和分離的想法。但是,以前以基于LAMP(linux,Apache,MySQL,php,Python或Perl)的網站形式存在的網站現在可能不再僅僅是基于LEMP的網站(“ E”代表“ Engine x”)。 ,但越來越多的做法是將Web服務器推向基礎架構的邊緣,并以不同的方式在其周圍集成相同或經過改進的一組應用程序和數據庫工具。
nginx非常適合于此,因為它提供了方便的卸載并發,延遲處理,SSL(安全套接字層),靜態內容,壓縮和緩存,連接和請求限制以及甚至來自應用程序的HTTP媒體流傳輸所需的關鍵功能。層到效率更高的邊緣Web服務器層。它還允許直接與memcached / redis或其他“ NoSQL”解決方案集成,以在為大量并發用戶提供服務時提高性能。
隨著近來開發工具包和編程語言的廣泛使用,越來越多的公司正在改變其應用程序開發和部署習慣。nginx已成為這些不斷變化的范例中最重要的組成部分之一,它已經幫助許多公司在預算范圍內快速啟動和開發其Web服務。
nginx的第一行寫于2002年。2004年,根據兩節BSD許可向公眾發布。從那時起,nginx用戶的數量一直在增長,他們提供了很多想法,并提交了對整個社區都非常有用和有益的錯誤報告,建議和觀察結果。
Nginx代碼庫是原始的,完全是用C編程語言完全從頭編寫的。nginx已被移植到許多體系結構和操作系統,包括Linux,FreeBSD,Solaris,mac OS X,AIX和Microsoft windows。nginx擁有自己的庫,并且其標準模塊在系統的C庫之外使用很少,除了zlib,PCRE和OpenSSL外,如果不需要或由于潛在的許可證沖突,可以選擇將其從構建中排除。
關于Windows版本的Nginx的幾句話。盡管nginx在Windows環境中工作,但Windows版本的nginx更像是概念驗證,而不是功能齊全的端口。Nginx和Windows內核體系結構存在某些局限性,目前無法很好地進行交互。Windows的nginx版本的已知問題包括并發連接數少得多,性能下降,沒有緩存和沒有帶寬策略。適用于Windows的Nginx的未來版本將更接近主流功能。
Nginx架構概述
傳統的基于進程或線程的處理并發連接的模型涉及使用單獨的進程或線程處理每個連接,并阻塞網絡或輸入/輸出操作。根據應用程序的不同,它在內存和CPU消耗方面可能效率很低。生成單獨的進程或線程需要準備新的運行時環境,包括分配堆和堆棧內存以及創建新的執行上下文。創建這些項目也要花費額外的CPU時間,由于過度的上下文切換導致線程崩潰,最終可能導致性能下降。所有這些復雜性都在Apache之類的較舊的Web服務器體系結構中體現出來。這是在提供豐富的一組普遍適用的功能與優化使用服務器資源之間的權衡。
從一開始,nginx就是一種專用工具,可實現更高的性能,密度和服務器資源的經濟使用,同時實現網站的動態增長,因此它采用了不同的模型。實際上,它受到了各種操作系統中基于事件的高級機制的持續開發的啟發。結果就是模塊化,事件驅動,異步,單線程,無阻塞的體系結構,該體系結構成為nginx代碼的基礎。
nginx大量使用多路復用和事件通知,并將特定任務專用于分離進程。連接在數量有限的稱為workers的單線程進程中以高效的運行循環進行處理。每個workernginx內每秒可以處理數千個并發連接和請求。
代碼結構
Nginxworker代碼包括核心和功能模塊。nginx的核心負責維護緊密的運行循環,并在請求處理的每個階段執行模塊代碼的適當部分。模塊構成了大多數表示層和應用程序層功能。激活代理后,模塊可從網絡讀取和寫入網絡和存儲,轉換內容,進行出站篩選,應用服務器端包含操作并將請求傳遞給上游服務器。
nginx的模塊化體系結構通常允許開發人員在不修改nginx核心的情況下擴展Web服務器功能集。nginx模塊的形式略有不同,即核心模塊,事件模塊,階段處理程序,協議,變量處理程序,過濾器,上游和負載平衡器。目前,nginx不支持動態加載的模塊。即,在構建階段將模塊與核心一起進行編譯。但是,計劃在將來的主要版本中支持可裝載模塊和ABI。有關不同模塊的角色的更多詳細信息,請參見第14.4節。
同時處理各種具有接受,處理和管理的網絡連接和內容檢索,nginx的用途事件通知機制和多個磁盤I /在Linux,Solaris和O性能增強基于BSD操作系統,等等相關聯的動作kqueue,epoll和event ports。目標是為操作系統提供盡可能多的提示,以獲取針對入站和出站流量,磁盤操作,套接字的讀寫操作,超時等的及時異步反饋。對于nginx所運行的每個基于Unix的操作系統,都對使用不同方法進行多路復用和高級I / O操作進行了優化。
下圖給出了Nginx架構的高級概述。

Nginx的架構圖
Worker模型
如前所述,nginx不會為每個連接生成一個進程或線程。取而代之的是,worker進程接受來自共享“偵聽”套接字的新請求,并在每個worker進程內部執行高效的運行循環,以每個進程處理數千個連接worker。worker在nginx中,沒有專門的仲裁或分配到s的連接;這項工作是由OS內核機制完成的。啟動時,將創建一組初始的偵聽套接字。worker然后在處理HTTP請求和響應的同時不斷接受,讀取和寫入套接字。
運行循環是Nginxworker代碼中最復雜的部分。它包括全面的內部調用,并且在很大程度上依賴于異步任務處理的思想。異步操作是通過模塊化,事件通知,回調函數的廣泛使用和經過微調的計時器來實現的。總體而言,關鍵原則是盡可能不阻塞。nginx仍然可以阻止的唯一情況是,當worker進程的磁盤存儲性能不足時。
由于nginx不會為每個連接派生一個進程或線程,因此在大多數情況下,內存使用非常保守且非常高效。nginx還可以節省CPU周期,因為進程或線程沒有持續的create-destroy模式。nginx的作用是檢查網絡和存儲的狀態,初始化新連接,將它們添加到運行循環中,并異步處理直到完成,這時將連接釋放并從運行循環中刪除。結合使用syscalls的謹慎使用和對接口(如池和平板內存分配器)的支持的準確實現,即使在極端的工作負載下,nginx通常也可以實現中等到低的CPU使用率。
由于nginx產生幾個workers來處理連接,因此它可以在多個內核之間很好地擴展。通常,worker每個內核使用單獨的內核可以充分利用多核體系結構,并防止線程崩潰和鎖定。沒有資源匱乏,并且資源控制機制被隔離在單線程worker進程中。該模型還允許跨物理存儲設備更大的可伸縮性,促進更多的磁盤利用率,并避免阻塞磁盤I / O。結果,服務器資源可以更有效地利用,并且在多個工作人員之間共享工作負載。
對于某些磁盤使用情況和CPU負載模式,worker應調整nginx的數量。這些規則在這里有些基本,系統管理員應針對其工作負載嘗試一些配置。一般建議如下:如果負載模式是CPU密集型的(例如,處理大量TCP / IP,執行SSL或壓縮),則nginxworker的數量應與CPU內核的數量匹配;如果負載主要是受磁盤I / O約束的(例如,從存儲服務中提供不同的內容集或進行大量代理),則workers的數量可能是內核數量的一半到兩倍。有些工程師worker根據單個存儲單元的數量來選擇s的數量,盡管這種方法的效率取決于磁盤存儲的類型和配置。
Nginx開發人員將在即將發布的版本中解決的一個主要問題是如何避免磁盤I / O的大部分阻塞。目前,如果沒有足夠的存儲性能來滿足特定磁盤所生成的磁盤操作worker,則worker可能仍無法從磁盤進行讀/寫操作。存在許多機制和配置文件指令來減輕此類磁盤I / O阻塞的情況。最值得注意的是,諸如sendfile和AIO之類的選項組合通常會為磁盤性能產生很大的擴展空間。應該基于數據集,可用于nginx的內存量以及基礎存儲架構來規劃nginx的安裝。
現有worker模型的另一個問題與對嵌入式腳本的有限支持有關。首先,使用標準的nginx發行版,僅支持嵌入Perl腳本。對此有一個簡單的解釋:關鍵問題是嵌入式腳本可能阻塞任何操作或意外退出。兩種類型的行為都將立即導致工人被吊死的情況,同時影響成千上萬的連接。計劃進行更多工作,以使使用nginx的嵌入式腳本更簡單,更可靠,并適合更廣泛的應用程序。
nginx流程角色
nginx在內存中運行多個進程;有一個主過程和幾個worker過程。還有兩個特殊目的的過程,特別是緩存加載器和緩存管理器。所有進程在nginx的1.x版本中都是單線程的。所有進程主要使用共享內存機制進行進程間通信。主進程以root用戶身份運行。緩存加載器,緩存管理器和worker均以非特權用戶身份運行。
主流程負責以下任務:
- 讀取并驗證配置
- 創建,綁定和關閉套接字
- 啟動,終止和維護配置的worker進程數
- 重新配置而不會中斷服務
- 控制不間斷二進制升級(啟動新二進制并在必要時回滾)
- 重新打開日志文件
- 編譯嵌入式Perl腳本
該worker過程接受來自客戶端的處理和過程連接,提供反向代理和過濾功能,并做幾乎其他一切nginx的是可以勝任的。關于監視nginx實例的行為,系統管理員應密切注意workers,因為它們是反映Web服務器實際日常操作的過程。
緩存加載器進程負責檢查磁盤上的緩存項,并使用緩存元數據填充nginx的內存數據庫。本質上,緩存加載器準備nginx實例,以與已經存儲在磁盤上的文件(以特別分配的目錄結構)一起工作。它遍歷目錄,檢查緩存內容元數據,更新共享內存中的相關條目,然后在一切準備就緒且可用時退出。
緩存管理器主要負責緩存的過期和失效。在正常的Nginx操作期間,它會保留在內存中,如果發生故障,它將由主進程重新啟動。
Nginx緩存概述
nginx中的緩存以文件系統上分層數據存儲的形式實現。高速緩存鍵是可配置的,并且可以使用不同的特定于請求的參數來控制進入高速緩存的內容。緩存鍵和緩存元數據存儲在共享內存段中,緩存加載器,緩存管理器和worker可以訪問這些共享內存段。當前,除了操作系統的虛擬文件系統機制所隱含的優化之外,沒有任何文件內的內存緩存。每個緩存的響應都放置在文件系統上的不同文件中。層次結構(級別和命名詳細信息)由nginx配置指令控制。將響應寫入高速緩存目錄結構時,文件的路徑和名稱是從代理URL的MD5哈希得出的。
將內容放置在緩存中的過程如下:當nginx從上游服務器讀取響應時,首先將內容寫入到緩存目錄結構之外的臨時文件中。當nginx完成對請求的處理后,它將重命名臨時文件并將其移至緩存目錄。如果用于代理的臨時文件目錄位于另一個文件系統上,則將復制該文件,因此建議將臨時目錄和緩存目錄都保留在同一文件系統上。當需要顯式清除文件時,從緩存目錄結構中刪除文件也是非常安全的。Nginx有第三方擴展,可以遠程控制緩存的內容,并且計劃將更多功能集成到主發行版中。
nginx配置
Nginx的配置系統受到Igor Sysoev在Apache方面的經驗的啟發。他的主要見解是,可伸縮的配置系統對于Web服務器至關重要。在維護具有許多虛擬服務器,目錄,位置和數據集的大型復雜配置時,遇到了主要的擴展問題。在相對較大的Web設置中,如果在應用程序級別和系統工程師本人均未正確執行的話,這可能是一場噩夢。
因此,nginx配置旨在簡化日常操作,并為進一步擴展Web服務器配置提供簡便的方法。
nginx配置保存在許多通常位于/usr/local/etc/nginx或中的純文本文件中/etc/nginx。主要配置文件通常稱為nginx.conf。為了使它整潔,可以將部分配置放在單獨的文件中,這些文件可以自動包含在主文件中。但是,此處應注意,nginx當前不支持Apache樣式的分布式配置(即.htaccess文件)。與nginx Web服務器行為有關的所有配置都應駐留在一組集中的配置文件中。
最初由主進程讀取并驗證配置文件。worker從主進程派生出來的進程可以使用nginx配置的已編譯只讀形式。配置結構由通常的虛擬內存管理機制自動共享。
nginx的配置有幾種不同的情況下main,http,server,upstream,location(也mail指示為郵件代理)塊。上下文永遠不會重疊。例如,沒有將location塊放在main指令塊中的事情。另外,為避免不必要的歧義,沒有類似“全局Web服務器”的配置。nginx配置的目的是干凈邏輯,允許用戶維護包含數千個指令的復雜配置文件。Sysoev在一次私下交談中說:“全局服務器配置中的位置,目錄和其他塊是我在Apache中從未喜歡的功能,因此這就是為什么它們從未在nginx中實現的原因。”
配置語法,格式和定義遵循所謂的C樣式約定。各種各樣的開放源代碼和商業軟件應用程序已經使用了這種制作配置文件的特殊方法。通過設計,C風格的配置非常適合嵌套的描述,邏輯性強,易于創建,閱讀和維護,并且受到許多工程師的喜愛。Nginx的C樣式配置也可以輕松實現自動化。
盡管某些nginx指令類似于Apache配置的某些部分,但是設置nginx實例卻是完全不同的體驗。例如,nginx支持重寫規則,盡管它需要管理員手動調整舊版Apache重寫配置以匹配nginx樣式。重寫引擎的實現也有所不同。
通常,nginx設置還支持多種原始機制,這些機制作為精益Web服務器配置的一部分非常有用。簡要提及變量和try_files指令是有意義的,這在nginx中是唯一的。開發nginx中的變量是為了提供附加的甚至更強大的機制來控制Web服務器的運行時配置。優化了變量以進行快速評估,并在內部將其預編譯為索引。評估是按需完成的;也就是說,變量的值通常只計算一次,并在特定請求的生存期內進行緩存。變量可以與不同的配置指令一起使用,從而為描述條件請求處理行為提供了額外的靈活性。
該try_files指令最初旨在以if一種更適當的方式逐漸替換條件配置語句,并且該指令旨在快速有效地嘗試/匹配不同的URI到內容的映射。總體而言,該try_files指令運行良好,并且可能非常有效和有用。建議讀者徹底檢查該try_files指令,并在適用的情況下采用該指令。
nginx內部
如前所述,nginx代碼庫由一個核心和許多模塊組成。Nginx的核心負責提供Web服務器,Web和郵件反向代理功能的基礎;它啟用了底層網絡協議的使用,構建了必要的運行時環境,并確保了不同模塊之間的無縫交互。但是,大多數特定于協議和應用程序的功能是由nginx模塊而不是核心完成的。
在內部,nginx通過模塊的管道或鏈來處理連接。換句話說,對于每個操作,都有一個模塊在做相關的工作。例如,壓縮,修改內容,執行服務器端包含,通過FastCGI或uwsgi協議與上游應用程序服務器進行通信,或與memcached進行通信。
在內核和真正的“功能”模塊之間的某個位置有幾個nginx模塊。這些模塊是http和mail。這兩個模塊在核心組件和較低級別的組件之間提供了附加的抽象級別。在這些模塊中,實現了與相應的應用程序層協議(如HTTP,SMTP或IMAP)關聯的事件序列的處理。這些上層模塊與nginx核心結合,負責維護對各個功能模塊的正確調用順序。目前,HTTP協議已作為該http模塊的一部分實現,但由于需要支持SPDY等其他協議,因此計劃在將來將其分成功能模塊。SPDY:一種用于更快Web的實驗性協議”)。
功能模塊可分為事件模塊,階段處理程序,輸出過濾器,變量處理程序,協議,上游和負載平衡器。盡管事件模塊和協議也用于,但大多數這些模塊都是nginx的HTTP功能的補充mail。事件模塊提供了特定于OS的事件通知機制,例如kqueue或epoll。Nginx使用的事件模塊取決于操作系統功能和構建配置。協議模塊允許nginx通過HTTPS,TLS / SSL,SMTP,POP3和IMAP進行通信。
一個典型的HTTP請求處理周期如下所示。
- 客戶端發送HTTP請求。
- nginx核心根據與請求匹配的已配置位置選擇適當的階段處理程序。
- 如果配置為這樣做,則負載平衡器將選擇上游服務器進行代理。
- 階段處理程序執行其工作,并將每個輸出緩沖區傳遞給第一個過濾器。
- 第一個過濾器將輸出傳遞到第二個過濾器。
- 第二個過濾器將輸出傳遞到第三個(依此類推)。
- 最終響應將發送給客戶端。
nginx模塊的調用是高度可定制的。它通過使用指向可執行函數的指針的一系列回調來執行。但是,這樣做的缺點是可能給希望編寫自己的模塊的程序員帶來沉重負擔,因為他們必須準確定義模塊的運行方式和運行時間。nginx API和開發人員的文檔都得到了改進,可以緩解這種情況。
模塊可以連接的位置的一些示例是:
- 在讀取和處理配置文件之前
- 對于該位置及其所在的服務器的每個配置指令
- 初始化主配置時
- 服務器(即主機/端口)初始化時
- 服務器配置與主配置合并時
- 當配置配置被初始化或與父服務器配置合并時
- 主進程開始或退出時
- 當新的工作進程啟動或退出時
- 處理請求時
- 過濾響應標頭和正文時
- 在選擇,啟動和重新啟動對上游服務器的請求時
- 處理上游服務器的響應時
- 完成與上游服務器的交互時
在內worker,導致運行循環(在其中生成響應)的操作序列如下所示:
- 開始ngx_worker_process_cycle()。
- 使用操作系統特定的機制(例如epoll或kqueue)處理事件。
- 接受事件并調度相關動作。
- 流程/代理請求標頭和正文。
- 生成響應內容(標題,正文)并將其流式傳輸到客戶端。
- 完成請求。
- 重新初始化計時器和事件。
運行循環本身(第5步和第6步)確保增量生成響應并將其流傳輸到客戶端。
處理HTTP請求的更詳細的視圖可能如下所示:
- 初始化請求處理。
- 進程頭。
- 工藝體。
- 調用關聯的處理程序。
- 運行處理階段。
這使我們進入了階段。當nginx處理HTTP請求時,它將通過多個處理階段傳遞它。在每個階段都有處理程序要調用。通常,階段處理程序處理請求并產生相關輸出。相位處理程序將附加到配置文件中定義的位置。
階段處理程序通常做四件事:獲取位置配置,生成適當的響應,發送標頭和發送正文。處理程序只有一個參數:描述請求的特定結構。請求結構具有許多有關客戶端請求的有用信息,例如請求方法,URI和標頭。
讀取HTTP請求標頭后,nginx會查找關聯的虛擬服務器配置。如果找到了虛擬服務器,則請求將經歷六個階段:
- 服務器重寫階段
- 定位階段
- 位置重寫階段(可以將請求帶回到上一個階段)
- 訪問控制階段
- try_files階段
- 對數階段
為了響應請求生成必要的內容,nginx將請求傳遞給合適的內容處理程序。根據確切位置的配置,nginx的可先試用所謂的無條件處理,如perl,proxy_pass,flv,mp4,等。如果請求不通過下面的處理程序之一匹配任何內容處理器之上,這是挑選的,在這個確切順序為:random index,index,autoindex,gzip_static,static。
可以在Nginx文檔中找到索引模塊的詳細信息,但是這些模塊處理帶有尾部斜杠的請求。如果像這樣的專用模塊mp4或autoindex不合適的模塊,則將內容視為磁盤上的文件或目錄(即靜態),并由static內容處理程序提供服務。對于目錄,它將自動重寫URI,以使結尾的斜杠始終存在(然后發出HTTP重定向)。
然后,內容處理程序的內容將傳遞到過濾器。過濾器也附加到位置,并且可以為一個位置配置多個過濾器。過濾器執行操作處理程序產生的輸出的任務。過濾器的執行順序在編譯時確定。對于開箱即用的過濾器,它是預定義的,對于第三方過濾器,可以在構建階段進行配置。在現有的nginx實現中,過濾器只能進行出站更改,并且目前沒有機制可以編寫和附加過濾器來進行輸入內容轉換。輸入過濾將出現在Nginx的未來版本中。
過濾器遵循特定的設計模式。一個過濾器被調用,開始工作,并調用下一個過濾器,直到調用鏈中的最后一個過濾器為止。之后,nginx完成響應。過濾器不必等待上一個過濾器完成。一旦前一個過濾器的輸入可用(功能非常類似于Unix管道),鏈中的下一個過濾器就可以開始自己的工作。反過來,可以在接收到來自上游服務器的整個響應之前,將生成的輸出響應傳遞給客戶端。
有標題過濾器和主體過濾器。nginx將響應的標頭和正文分別饋送到關聯的過濾器。
標頭過濾器包含三個基本步驟:
- 決定是否對此響應進行操作。
- 根據響應進行操作。
- 調用下一個過濾器。
主體過濾器可轉換生成的內容。身體過濾器的示例包括:
- 服務器端包含
- XSLT過濾
- 圖像過濾(例如,即時調整圖像大小)
- 字符集修改
- gzip 壓縮
- 分塊編碼
在過濾器鏈之后,響應將傳遞到編寫器。除編寫器外,還有其他幾個特殊用途的過濾器,即copy過濾器和postpone過濾器。該copy過濾器是負責填充內存緩沖區有可能被存儲在一個代理臨時目錄相關的響應內容。該postpone過濾器用于子請求。
子請求是用于請求/響應處理的非常重要的機制。子請求也是Nginx最強大的方面之一。通過子請求,nginx可以從與客戶端最初請求的URL不同的URL返回結果。一些Web框架將其稱為內部重定向。但是,nginx更進一步-過濾器不僅可以執行多個子請求并將輸出組合到單個響應中,而且子請求還可以嵌套和分層。子請求可以執行自己的子子請求,并且子子請求可以發起子子子請求。子請求可以映射到硬盤,其他處理程序或上游服務器上的文件。子請求對于基于原始響應中的數據插入其他內容最有用。例如,include帶有指定URL內容的指令。或者,它可能是制作過濾器的示例,該過濾器將文檔的全部內容視為要檢索的URL,然后將新文檔附加到URL本身。
上游和負載均衡器也值得簡要描述。上游用于實現可以識別為內容處理程序的內容,該內容處理程序是反向代理(proxy_pass處理程序)。上游模塊通常會準備要發送到上游服務器(或“后端”)的請求,并從上游服務器接收響應。這里沒有對輸出過濾器的調用。上游模塊確切執行的操作是在準備好寫入和讀取上游服務器時調用設置的回調。存在實現以下功能的回調:
- 制作請求緩沖區(或它們的鏈)以發送到上游服務器
- 重新初始化/重置與上游服務器的連接(在再次創建請求之前發生)
- 處理上游響應的第一位并保存指向從上游服務器接收的有效負載的指針
- 終止請求(在客戶端過早終止時發生)
- 當Nginx完成從上游服務器的讀取時完成請求
- 整理響應主體(例如,移除拖車)
負載平衡器模塊連接到proxy_pass處理程序,以在有多個上游服務器符合條件時提供選擇上游服務器的能力。負載平衡器注冊一個啟用配置文件指令,提供其他上游初始化功能(以解析DNS中的上游名稱等),初始化連接結構,決定將請求路由到何處,并更新統計信息。當前,nginx支持兩種標準規范來平衡上游服務器的負載:輪詢和ip-hash。
上游和負載平衡處理機制包括用于檢測故障上游服務器并將新請求重新路由到其余請求的算法,盡管計劃進行許多其他工作來增強此功能。通常,計劃在負載平衡器上進行更多工作,并且在Nginx的下一版本中,將大大改善跨不同上游服務器分配負載以及運行狀況檢查的機制。
還有兩個其他有趣的模塊,它們提供了一組在配置文件中使用的變量。雖然nginx中的變量是在不同的模塊之間創建和更新的,但有兩個完全專用于變量的模塊:geo和map。該geo模塊用于根據客戶端的IP地址促進對客戶端的跟蹤。該模塊可以創建依賴于客戶端IP地址的任意變量。另一個模塊map允許從其他變量創建變量,從本質上講,它提供了對主機名和其他運行時變量進行靈活映射的功能。這種模塊可以稱為變量處理程序。
在單個nginx內實現的內存分配機制在worker某種程度上受到Apache的啟發。Nginx內存管理的高級描述如下:對于每個連接,動態分配,鏈接必需的內存緩沖區,將其用于存儲和處理請求和響應的標頭和主體,然后在連接釋放時釋放它們。請務必注意,nginx會盡量避免在內存中復制數據,并且大多數數據是通過指針值傳遞的,而不是通過調用傳遞的memcpy。
更深入一點,當模塊生成響應時,將檢索到的內容放入內存緩沖區中,然后將其添加到緩沖區鏈鏈接中。隨后的處理也與此緩沖區鏈鏈接一起工作。在Nginx中,緩沖區鏈非常復雜,因為根據模塊類型的不同,有多種處理方案。例如,在實現主體過濾器模塊時精確地管理緩沖區可能非常棘手。這樣的模塊一次只能在一個緩沖區(鏈鏈接)上運行,并且必須決定是覆蓋輸入緩沖區,用新分配的緩沖區替換該緩沖區,還是在相關緩沖區之前或之后插入新緩沖區。使事情復雜化的是,有時模塊會接收多個緩沖區,因此它具有不完整的緩沖區鏈,必須對其進行操作。然而,
關于上述方法的注釋是,在連接的整個生命周期中都分配了內存緩沖區,因此對于壽命長的連接,會保留一些額外的內存。同時,在空閑的keepalive連接上,nginx僅花費550字節的內存。對于Nginx的將來版本,可能的優化方法是重用和共享內存緩沖區以用于長期連接。
管理內存分配的任務由Nginx池分配器完成。共享內存區域用于接受互斥,高速緩存元數據,SSL會話高速緩存以及與帶寬管制和管理(限制)相關的信息。在nginx中實現了一個平板分配器來管理共享內存分配。為了允許同時安全地使用共享內存,可以使用多種鎖定機制(互斥體和信號量)。為了組織復雜的數據結構,nginx還提供了一個紅黑樹實現。紅黑樹用于將緩存元數據保留在共享內存中,跟蹤非正則表達式位置定義以及執行其他一些任務。
不幸的是,上述所有內容從未以一致和簡單的方式進行描述,這使得為nginx開發第三方擴展的工作變得相當復雜。盡管存在一些有關nginx內部的良好文檔(例如,由Evan Miller編寫的文檔),但此類文檔仍需要大量的逆向工程工作,并且nginx模塊的實現對于許多人來說仍然是妖術。
盡管與第三方模塊開發相關的某些困難,但Nginx用戶社區最近看到了許多有用的第三方模塊。例如,有一個用于nginx的嵌入式Lua解釋器模塊,用于負載平衡的其他模塊,全面的WebDAV支持,高級緩存控制以及本章的作者鼓勵并將來會支持的其他有趣的第三方工作。
教訓
當Igor Sysoev開始編寫nginx時,支持Internet的大多數軟件已經存在,并且此類軟件的體系結構通常遵循傳統服務器和網絡硬件,操作系統以及舊的Internet體系結構的定義。但是,這并沒有阻止Igor認為他可能能夠改進Web服務器領域的功能。因此,盡管第一課似乎很明顯,但事實是:總有改進的余地。
考慮到更好的Web軟件的想法,Igor花了很多時間來開發初始代碼結構,并研究了針對各種操作系統優化代碼的不同方法。十年后,考慮到對版本1的積極開發,他正在開發nginx版本2.0的原型。很顯然,新架構的初始原型和初始代碼結構對于未來的發展至關重要。軟件產品。
值得一提的另一點是,應重點發展。Windows版本的nginx可能是一個很好的例子,它說明了如何避免將開發工作浪費在既不是開發人員的核心能力也不是目標應用程序的東西上是值得的。它同樣適用于在嘗試使用更多功能增強nginx以便與現有舊式設置向后兼容的過程中出現的重寫引擎。
最后但并非最不重要的一點是,值得一提的是,盡管Nginx開發人員社區不是很大,但Nginx的第三方模塊和擴展一直是其受歡迎程度的重要組成部分。Nginx用戶社區及其原始開發人員對Evan Miller,Piotr Sikora,Valery Kholodkov,Zhang Yichun(agentzh)和其他才華橫溢的軟件工程師所做的工作表示贊賞。
(本文由聞數起舞翻譯自Andrew Alexeev的文章《Nginx》,轉載請注明出處,原文鏈接:
https://www.aosabook.org/en/nginx.html)