- 原文地址:The Open Source Project Nginx
- 原文做者:Andrew Alexeev
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:razertory
- 校對者:yqian1991
nginx(讀做 "engine x")是一位名叫 Igor Sysoev 的俄羅斯軟件工程師開發的。自 2004 年發佈以來,nginx 就一直專一於實現高性能,高併發和低內存佔用。nginx 的額外功能,好比:負載均衡、緩存和流量控制以及高效集成在 Web 服務上的能力,使得它成爲了當今網站架構的必選。現在,nginx 已經成爲互聯網中第二受歡迎的開源 Web 服務器。html
現在,互聯網早已無處不在,咱們已經很難想象十年前沒有互聯網的樣子。如今的互聯網發生了翻天覆地的變化,從基於 NSCA 的能夠點擊 HTML 頁面和基於 Apache 的 Web 服務,到現在可以實現超過 20 億人實時的溝通。隨着 PC、手機和平板的的蔓延,互聯網已經將全球經濟數字化。面向信息和娛樂的在線服務變得更加優質。線上商業活動的安全方面也發生了明顯變化。所以,網站也比之前更加的複雜而且須要大量的工程投入來確保魯棒性和可擴展性。前端
併發性成爲了網站架構設計的最大挑戰之一。自從 web 服務開始的時候,併發性的等級就在持續上升。對於一個熱門網站來講,支持幾百甚至是幾百萬用戶同時訪問來講也不是什麼稀罕事情。20 年前,產生併發的緣由主要仍是客戶端的 ADSL 或者撥號(dial-up)鏈接。現在,併發的產生來源於手機端和以及新型的應用架構,這些架構主要能夠支持長鏈接來提供新聞、信息流發佈和朋友間的 feed 流等等。另外一方面,致使高併發還因爲現代瀏覽器的工做發生變化,一般是爲了提升網頁加載速度同時打開 4 到 6 個鏈接。android
爲了表述清楚緩慢這種問題,設想一下,一個基於 Apache 的,能夠提供 100KB 大小帶有文字或者圖片的簡單 web 服務器。生成或者從新產生這個網頁只須要極少不到一秒的時間。可是在帶寬只有 80kps 的狀況下(下載速度 10kb/s),傳輸數據到客戶端卻會花掉 10s。本質上,服務器產生 100kb 數據的速度是相對較快的,隨後在傳輸數據到客戶端直至釋放鏈接的過程倒是相對較慢的。如今設想,你同時有 1,000 個獨立的客戶端連到你的服務器而且請求一樣的內容。若是對於每一個獨立的鏈接,都會佔用額外的 1MB 內存,那麼對於 1,000 個鏈接來講就對致使多佔用 1000 MB(1G)的內存,而這些僅僅是爲了給 1000 個客戶端提供 100kb 的內容。實際上,一個典型的 Apache 服務器一般會爲了一個鏈接佔用超過 1MB 的內存,遺憾的是幾十 k 的帶寬足夠讓手機之間高效通信。儘管從某種程度而言,發送數據給客戶端是慢的,提升操做系統內核的 socket 緩衝大小是能夠的,這個不是一個一般的解決方法,而且會有不良影響。ios
在持久鏈接中,處理併發會作起來總比提及來有更多的問題,由於要在新建 HTTP 鏈接的時候避免延遲,讓客戶端保持鏈接而且確保對於每一個鏈接服務端都可以保證有足夠內存可供使用。nginx
所以,爲了可以處理由於用戶量增加產生高併發由此帶來的負載上升,網站的就必須基於經過必定數目的高效模塊來架設。同時,在從得到客戶端鏈接請求,處處理完請求期間,像硬件(CPU,memory,disk),網絡容量以及數據存儲也是一樣重要的。所以,web 服務器須要能在同時請求數和每秒請求頻率這兩方面都擁有擴展性。git
Apache,開始於 1990s,現在依舊統治着互聯網。最初它的架構知足於當時的操做系統和硬件,同時也知足於當時的只有一個獨立的物理機運行一個 Apache 服務器的互聯網狀態。在 2000 年始,一個獨立的服務器難以知足增加起來的 Web 服務的狀況愈來愈明顯。儘管 Apache 提供了一個可靠的基金會用於將來發展,然而,它這種爲了每一個新鏈接複製自身的架構,已經再也不適用於非線性的網站擴張。最終,Apache 成爲了一個有着許多不一樣特性,第三方擴展,和一些廣泛用於 web 應用開發的功能的 web 服務器。然而,沒有什麼東西是十全十美的,Apache 有者豐富功能的同時,對於每一個鏈接產生的 CPU 和內存消耗使得它不能很好的擴展。程序員
所以,當服務器的硬件、操做系統和網絡條件成爲了網站增加的瓶頸時,全世界的 web 工程師開始尋找一種更加高效的方法。大約十年前,一位名叫 Daniel Kegel 的傑出工程師宣稱:"是時候讓 web 服務可以支持 10k 併發了。"同時他還預測了咱們如今會叫互聯網雲服務。c10k 問題一產生,就引來了許許多多的解決方案用以優化實時的高併發。nginx 成爲了其中最出色的解決方案之一。github
爲了解決 C10k 問題中的 10,000 個實時的鏈接,nginx 用了一種不同凡響的架構,這種架構會更適合在同時處理大量的鏈接和一秒鐘內完成屢次請求環境中,問題規模的增加是非線性的。nginx 是事件驅動的(event-based,因此它不會用 Apache 的那種爲每個 web 請求都申請一個進程或者線程。結果即是,即便負載升高,內存和 CPU 都仍是處於掌控之中。nginx 目前能夠在一臺普通的機器上,同時處理上萬的併發。web
nginx 的第一個版本主要是和 Apache 服務器一塊兒部署,用來單獨處理本來是 Apache 處理的 HTML, CSS, JavaScript 和圖片這樣的靜態資源。在隨後的迭代中,nginx 支持像 FastCGI, ,uswgi 或者 SCGI 協議集成到應用當中部署,而且能夠利用像 memcached 這樣的分佈式緩存系統。同時像反向代理,負載均衡這樣的特性也隨之加上。這些額外的特色讓 nginx 成爲了構建可擴展性 web 服務的高效的基礎組件的工具之一。正則表達式
2012 年二月,Apache 2.4.x 分支發佈。儘管,這個最新版本的 Apache 增長了多核處理器支持模塊和用於提高可擴展性和併發的模塊,然而它的性能,併發能力,以及資源利用能力與純事件驅動的 web 服務器比,依舊難以望其項背。 很樂意看到新版的 Apache 服務器有着更好的可擴展性,儘管這樣能夠減小自身的瓶頸,然而像典型的 nginx-plus-Apache 配置依舊會被使用。
可以高性能地處理高併發一直是部署了 nginx 以後得到的最主要的好處。然而,還有一些更有趣的東西。
在過去幾年中,網站架構就一直在擁抱解耦並從 web 服務器中拆分出一些基礎組件。然而,那些本來存在於 LAMP-based 的網站中的基礎組件,在 LEMP-based(E 表明着 Nginx 的讀音) 的網站中,卻能讓 web 服務器成爲基礎組件以及用一種不一樣的方式去集成相同的或者改進了的應用和數據庫工具。
nginx 很是適合作這個,由於它能夠方便提供一個併發支持,延遲超時處理,SSL 支持,靜態文件支持,壓縮和緩存,甚至是 http 流媒體的高效的層級,而這些功能本來處於應用層。nginx 也能夠直接集成一些像 Redis/memcached 這樣的 NoSQL 用以優化大用戶量場景。
當近代的開發語言和工具流行起來的時候,愈來愈多的公司正在改變他們的開發和部署方式。nginx 成爲了改變過程當中最重要的部分,同時,nginx 讓不少公司在有限的預算中,快速地啓動開發他們的服務。
nginx 是從 2002 年開始開發。到 2004 年,它以 two-clause BSD license 發佈。隨後,nginx 用戶量開始增高,修改建議,bug 報告,觀察報告等都在社區中不斷完善 ngix。
nginx 最初的源碼是用 C 完成的。nginx 已經能夠部署在許多架構和操做系統中,好比 Linux, FreeBSD, Solaris, Mac OS X, AIX and Microsoft Windows。nginx 擁有本身的庫而且並無大量使用 C 標準庫,一些像 zlib, PCRE and OpenSSL 這一類的庫由於有證書衝突而沒有被採用。
在 Windows 上部署 nginx 更像是一個實現 nginx 的理論證實而不是一個功能完善的項目。因爲內核限制,nginx 的一些功能特性並不能發揮出來。在 windows 上的 nginx 併發能力、性能會更低,也沒有緩存和帶寬策略。未來 windows 上的 nginx 版本會繼續完善。
傳統的解決併發的方式是每一個單獨的請求一個進程或者線程,而且網絡和 io 操做都是阻塞式的。在傳統的應用當中,這種作法會因爲 CPU 和內存開銷致使低效。開啓一個獨立的進程或者線程會須要預加載一個新的運行時環境和上下文。這些東西也會佔用一些額外的 CPU 時間,線程頻繁輪換致使的上下文切換帶來的開銷最終致使了低性能。這些問題在一些舊的 web 服務架構,好比 Apache 中獲得了證明。這是在提供豐富廣泛特性與優化服務器開銷以前的一種權衡。
從最先開始,nginx 就被設定爲在網站用戶動態增加期間,用來提升網站性能和服務器資源利用率的工具,以致於它擁有一種不同凡響的模型。這是受一些操做系統的事件驅動概念啓發。這也產生了 nginx 的核心架構:模塊化,事件驅動,異步,單線程,非阻塞。
nginx 大量採用多路複用(multiplex)和事件通知,並對每一個 nginx 進程分配了特定的任務。鏈接被有限個數單線程的 worker 進程高效輪詢(run-loop)處理。 每一個 worker 均可以同時處理數千個併發鏈接和每秒請求。
worker 代碼包含了核心和功能模塊。nginx 核心負責維護一個緊湊的輪詢,並在處理請求的每一個階段都執行模塊中對應的部分。模塊構成了大部分表示層和應用層功能。模塊從網絡和存儲介質中進行數據的讀寫,傳輸內容,過濾出站內容,執行服務端的動做和當代理功能被打開的時候傳遞請求到被代理的(upstream)服務器。
nginx 模塊化的架構可讓開發者在不修改核心代碼的狀況下加入一些自定也的擴展。nginx 模塊稍微有點不一樣,好比核心模塊、事件模塊、階段處理器、協議、變量處理器、filter,upstream 和負載均衡。目前,nginx 再也不支持動態加載模塊。模塊在 nginx build 階段就會被編譯。然而,在未來 nginx 會在主版本上提供 loadable 模塊和 ABI。更多關於不一樣模塊的信息詳見 Section 14.4.
在處理一些關於網絡接收,處理和管理以及內容檢索的時候,nginx 使用了事件通知(event notification)機制以及一些操做系統( Linux, Solaris and BSD-based)的磁盤 IO 優化,好比:kqueue
, epoll
, and event ports
。目的是爲操做系統提供儘量多的提示,以便爲入站和出站流量、磁盤操做、socket 讀寫、超時等獲取及時的異步反饋。針對 nginx 運行的每一個 unix-like 的操做系統,對多路複用和高級 I/O 操做使用不一樣的方法進行了大量優化。
更多 nginx 架構高級概述詳見 Figure 14.1.
Figure 14.1: Diagram of nginx's architecture
正如以前提到的,nginx 並不爲每一個鏈接開一個進程或者線程。相反,worker 進程爲每一個新鏈接都採用一個共用的監聽 socket 並在輪詢中高效處理着數千個鏈接。對於 nginx 的 worker,沒有采用一些特別的鏈接機制,都是由操做系統內核來完成的。一旦啓動,一些監聽 socket 就會被建立。worker 就會持續地接受鏈接,處理 http 請求和從對應的這些 socket 中讀寫數據。
輪詢是 nginx 代碼中最複雜的部分。它包括了綜合(comprehensive)的內部調用和依賴大量的異步任務處理思想。異步操做經過模塊化,事件通知,函數回調和計時器實現。整體上,關鍵在於儘量的非阻塞。惟一讓 nginx worker 阻塞的只有磁盤不足的狀況。
由於 nginx 不會爲每一個鏈接新開進程或者線程,內存佔用在不少場景下都不會高。nginx 節約了 cpu 佔用也是由於沒有進程線程的建立和銷燬。nginx 要作的就是檢查網絡和存儲,建立新鏈接,把新鏈接加入到輪詢,而且在完成以前都異步處理。nginx 謹慎採用了一些系統調用好比資源池化和內存分配,以致於在極端的狀況下也不會有很高的 CPU 佔用。
因爲 nginx 處理鏈接就開了幾個 worker,在多核狀況下能夠很好的擴展。大體就是一個核心一個 worker,這樣每一個 worker 充分利用 cpu 核心,避免了線程切換和鎖等待。不會產生資源不足而且每一個單線程的 worker 進程中都存在資源管理策略。這種模型容許在不一樣存儲設備之間有更好的擴展性,促進磁盤利用而且避免了磁盤 IO 阻塞。總的來講,服務器資源在多個 worker 工做的狀況下被更高效使利用了。
對於某些磁盤使用和 CPU 負載模式,應該調整 nginx worker 的數量。這些規則在這裏有點基礎,系統管理員應該基於他們的工做負載嘗試一些配置。通常建議以下:若是負載模式是 CPU 密集型的—例如,處理大量 TCP/IP、執行 SSL 或壓縮,nginx worker 的數量應該與 CPU 核心的數量相匹配;若是負載主要是磁盤 I/O 限制。例如,從存儲中提供不一樣的內容,或者大量的反向代理,workers 的數量多是內核數量的 1.5 到 2 倍。有些工程師根據單個存儲單元(磁盤分區)的數量來選擇 workers 的數量,這種方法的效率取決於磁盤存儲的類型和配置。
nginx 開發人員在即將發佈的版本中要解決的一個主要問題是如何避免磁盤 I/O 上的大部分阻塞。目前,若是沒有足夠的存儲性能來服務於由特定的 worker 生成的磁盤操做,那麼 worker 仍然可能阻塞從磁盤讀取 / 寫入。存在許多機制和配置文件指令來減輕此類磁盤 I/O 阻塞場景。最值得注意的是,sendfile 和 AIO 等選項的組合一般會爲磁盤性能帶來很大的空間。應該根據數據存儲、可用的內存大小和底層存儲體系結構來計劃 nginx 的安裝。
現有 worker 模型的另外一個問題是關於內嵌腳本支持的限制。首先,使用標準的 nginx 發行版,只支持嵌入 Perl 腳本。對此有一個簡單的解釋:關鍵問題是內嵌腳本可能阻止任何操做或意外退出。這兩種類型的行爲都會當即致使 worker 被掛起,同時影響數千個鏈接。須要更多的工做來讓 nginx 的嵌入式腳本更簡單、更可靠、適合更多的應用程序。
nginx 在內存中運行幾個進程;有一個 master 進程和幾個 worker 進程。還有一些特殊用途的進程,特別是緩存加載器和緩存管理器。版本 1.x 中的全部進程都是單線程的。全部進程主要使用共享內存機制進行進程間通訊。主進程做爲 root 用戶運行。緩存加載器、緩存管理器和 worker 做爲非特權用戶運行。
master 進程主要有如下任務
worker 進程接受和處理來自客戶機的鏈接,提供反向代理和過濾功能,並完成 nginx 可以作的幾乎全部其餘事情。關於監視 nginx 實例的情況,系統管理員應該關注 worker 進程,由於他們是反映 web 服務器實際平常操做的過程。
緩存加載器進程負責檢查磁盤上的緩存項,並使用緩存元數據填充 nginx 的內存數據庫。實際上,緩存加載器準備 nginx 實例來處理已經存儲在磁盤上的文件,這些文件位於一個特別分配的目錄結構中。它遍歷目錄,檢查緩存內容元數據,更新共享內存中的相關條目,而後在一切都乾淨且可使用時退出。
緩存管理器主要負責緩存過時和失效。在正常的 nginx 操做過程當中,它保持在內存中,在失敗的狀況下由主進程從新啓動。
nginx 中的緩存是以文件系統上分層數據存儲的形式實現的。緩存 key 是可配置的,可使用不一樣的特定於請求的參數來控制進入緩存的內容。緩存 key 和緩存元數據存儲在共享內存段中,緩存加載器、緩存管理器和 worker 進程能夠訪問共享內存段。目前,除了操做系統的虛擬文件系統機制產生的優化以外,沒有任何文件的是緩存在內存當中。每一個緩存的讀取都放在文件系統上的不一樣文件中。層次結構(級別和命名細節)是經過 nginx 配置指令控制的。當將響應寫入緩存目錄結構時,路徑和文件的名稱來自代理 URL 的 MD5 值。
在緩存中放置內容的過程以下:當 nginx 從 upstream 服務器讀取響應時,內容首先被寫入緩存目錄結構以外的臨時文件中。當 nginx 完成對請求的處理後,它會重命名臨時文件並將其移動到緩存目錄中。若是用於代理的臨時文件目錄位於另外一個文件系統上,則會複製該文件,所以建議將臨時目錄和緩存目錄保存在同一個文件系統上。當須要顯式清除緩存目錄結構中的文件時,從緩存目錄結構中刪除文件也是至關安全的。nginx 有第三方的擴展,能夠遠程控制緩存的內容,而且計劃了更多的工做來讓這個功能能夠集成到主發行版中。
nginx 的配置系統受到了 Igor Sysoev 使用 Apache 的經驗的啓發。他的主要觀點是,對於 web 服務器來講,可伸縮的配置系統是必不可少的。當使用大量虛擬服務器、目錄、位置和數據集維護大型複雜配置時,會遇到擴展問題。在一個相對較大的 web 設置中,若是在應用程序和系統工程師都沒有正確地完成,那麼它多是一個噩夢。
所以,nginx 配置的目的是簡化平常操做,並提供進一步擴展 web 服務器配置的簡單方法。
nginx 的配置保存在許多純文本文件中,這些文件一般位於 /usr/local/etc/nginx
或/etc/nginx
。主配置文件一般稱爲 nginx.conf
。爲了保持它的整潔,部分配置能夠放在單獨的文件中,這些文件能夠自動包含在主文件中。然而,這裏應該注意到 nginx 目前不支持 apache 風格的分佈式配置(即」。htaccess 文件)。全部與 nginx web 服務器行爲相關的配置都應該駐留在一組集中的配置文件中。
配置文件最初由 master 進程讀取和驗證。當 worker 進程從 master 進程 fork 時,worker 進程可使用編譯後的只讀形式 nginx 配置。配置結構由一般的虛擬內存管理機制自動共享。
nginx 配置有幾個不一樣的內容:main
, http
, server
, upstream
, location
(同時 mail
至關於郵件服務代理)。配置文件內容不重疊。例如,在 main 中不存在 location。此外,爲了不沒必要要的歧義,沒有任何相似於「全局 web 服務器」配置的東西。nginx 的配置是乾淨和合乎邏輯的,容許用戶維護包含數千個指令的複雜配置文件。在一次私人談話中,Sysoev 說,「全局服務器配置中的 location、directory 和其餘塊是我在 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
directive
如前所述,nginx 代碼庫由核心和許多模塊組成。 nginx 的核心是負責提供 Web 服務器,Web 和郵件反向代理功能的基礎;它支持使用底層網絡協議,構建必要的運行時環境,並確保不一樣模塊之間的無縫交互。可是,大多數協議和特定的應用程都是由 nginx 功能模塊完成的,而不是核心模塊。
在內部,nginx 經過由模塊組成的的管道或模塊鏈來處理鏈接。換句話說,對於每一個操做,都有一個正在進行相關工做的模塊;例如,壓縮,修改內容,執行服務器端,經過 FastCGI 或 uwsgi 協議與 upstream 應用服務器通訊,或與 memcached 通訊。
有幾個 nginx 模塊位於核心和真正的「功能」模塊之間。這些模塊是http
和mail
。這兩個模塊在覈心和較低級別組件之間提供了額外的抽象級別。在這些模塊中,實現了與諸如 HTTP,SMTP 或 IMAP 的相應應用層協議相關聯的事件序列的處理。結合 nginx 核心,這些上層模塊負責維護對各個功能模塊的正確調用順序。雖然 HTTP 協議目前是做爲http
模塊的一部分實現的,但因爲須要支持 SPDY 等其餘協議,所以計劃未來將其分離爲功能模塊。更多 SPDY 協議詳見 SPDY: An experimental protocol for a faster web
功能模塊可分爲事件模塊,階段處理程序,輸出 filter,變量處理程序,協議,上游和負載平衡器。大多數這些模塊補充了 nginx 的 HTTP 功能,但事件模塊和協議也用於mail
。事件模塊提供特定的 OS 依賴事件通知機制,如kqueue
或epoll
。 nginx 使用的事件模塊取決於操做系統功能和構建配置。協議模塊容許 nginx 經過 HTTPS,TLS / SSL,SMTP,POP3 和 IMAP 進行通訊。
典型的 HTTP 請求處理週期以下所示。
nginx 模塊調用是很是可定製的。它使用指向可執行函數的指針來執行一系列回調。然而,這樣作的缺點是它可能給想要編寫本身的模塊的程序員帶來很大的負擔,由於他們必須準肯定義模塊應該如何以及什麼時候運行。 nginx API 和開發人員的文檔都在不斷改進,而且能夠更多地用來緩解這個問題。
下面這些列子是能夠添加模塊的位置:
在 worker 進程中,致使生成響應的運行循環的 action 序列以下所示:
ngx_worker_process_cycle()
.epoll
or kqueue
)輪詢自己(步驟 5 和 6)確保增量生成響應並將其流式傳輸到客戶端。
處理 HTTP 請求的更詳細過程可能以下所示
這將咱們帶到了每一個階段。當 nginx 處理 HTTP 請求時,它會將其傳遞給許多處理階段。在每一個階段都有處理程序能夠調用。一般,階段處理程序處理請求並生成相關輸出。階段處理程序被附加到配置文件中定義的位置。
階段處理程序一般執行如下四項操做:獲取位置配置,生成適當的響應,發送 header 以及發送 body。處理程序有一個參數:描述請求的特定結構。請求結構有不少關於客戶端請求的有用信息,例如請求 method,URI 和 header。
讀取 HTTP 請求 header 時,nginx 會查找關聯的虛擬服務器配置。若是找到虛擬服務器,請求將經歷六個階段:
爲了響應請求生成必要的內容,nginx 將請求傳遞給合適的內容處理程序。根據確切的位置配置,nginx 可能首先嚐試所謂的無條件處理程序,如perl
,proxy_pass
,flv
,mp4
等。若是請求與上述任何內容處理程序都不匹配,則由如下處理程序之一按照如下順序選擇:random index
,index
,autoindex
,gzip_static
,static
。
索引模塊的詳細信息能夠在 nginx 文檔中找到,但這些是使用尾部斜槓處理請求的模塊。若是像mp4
或autoindex
這樣的專用模塊則不合適,內容被認爲只是磁盤上的文件或目錄(即靜態),並由static
內容處理程序提供服務。對於目錄,它會自動重寫 URI,以便始終存在尾部斜槓(而後發出 HTTP 重定向)。
而後將內容處理程序的內容傳遞給 filter。filter 也附加到 location,而且能夠爲 location 配置多個 filter。filter 執行操做處理程序生成的輸出的任務。對於預先定義的開箱即用 filter,執行的順序在編譯時就肯定。對於第三方 filter,能夠在構建階段對其進行配置。在現有的 nginx 實現中,filter 只能進行出站更改,而且目前沒有機制來編寫和附加 filter 來進行輸入內容轉換。輸入過濾將出如今 nginx 的將來版本中。
filter 遵循特定的設計模式。調用 filter,開始工做,並調用下一個 filter,直到調用鏈中的最終 filter。以後,nginx 完成響應。filter 沒必要等待前一個 filter 完成。調用鏈中的下一個 filter 能夠在上一個 filter 的輸入可用時當即開始工做(功能上與 Unix 管道很是類似)。反過來,生成的輸出響應能夠在接收到來自上游服務器的整個響應以前傳遞給客戶端。
還有 header filter 和 body filter;nginx 會分別用相關的 filter 來給相應 header 和 body 添加數據
header filter 主要有下面三個步驟
body filter 修改生成的數據,下面是 body filter 的一些案例
gzip
壓縮在 filter chain 以後,響應將傳遞給 writer。除了 writer 以外,還有一些額外特殊用途的 filter,即copy
和postpone
filter。 copy
filter 負責使用可能存儲在代理臨時目錄中的相關響應內容填充內存緩衝區。 postpone
filter 用於子請求。
子請求是請求 / 響應處理的很是重要的機制。子請求也是 nginx 最強大的方面之一。對於子請求,nginx 能夠從與客戶端最初請求的 URL 不一樣的 URL 返回結果。一些 Web 框架將此稱爲內部重定向。可是,nginx 更進一步 - 過濾器不只能夠執行多個子請求,並且能夠將輸出組合成單個響應,但子請求也能夠嵌套和分層。子請求能夠執行其本身的子子請求,而且子子請求能夠發起子子子請求。子請求能夠映射到硬盤,其餘處理程序或上游服務器上的文件。子請求對於根據原始響應中的數據插入其餘內容很是有用。例如,SSI(服務器端包含)模塊使用過濾器來解析返回文檔的內容,而後將「include」指令替換爲指定 URL 的內容。或者,它能夠是一個過濾器,將文檔的整個內容視爲要檢索的 URL,而後將新文檔附加到 URL 自己
upstream 和負載均衡器也值得簡要描述。upstream 用於實現能夠被識別爲反向代理(proxy_pass
處理程序的內容。upstream 模塊主要準備將請求發送到 upstream 服務器(或「後端」)並接收響應。這裏沒有調用輸出 filter。當 upstream 服務器準備好被寫入和讀取時,upstream 模塊確切地作的是設置要調用的回調。存在實現如下功能的回調:
負載均衡器模塊鏈接到proxy_pass
處理程序,以便在多個 upstream 服務器符合條件時提供選擇上游服務器的功能。負載均衡器註冊啓用配置文件指令,提供額外的上游初始化函數(以解析 DNS 中的上游名稱等),初始化鏈接結構,決定在何處路由請求以及更新統計信息。目前,nginx 支持兩種標準規則,用於對 upstream 服務器進行負載均衡:循環和 ip-hash。
upstream 和負載均衡處理機制包括用於檢測失敗的上游服務器以及將新請求從新路由到其他服務器的算法 - 儘管計劃進行大量額外工做以加強此功能。總的來講,nginx 開發團隊計劃對負載均衡器進行更多的工做,而且在下一版本的 nginx 中,將大大改進跨不一樣上游服務器分配負載以及運行情況檢查的機制。
還有一些其餘有趣的模塊提供了一組額外的變量供配置文件使用。雖然 nginx 中的變量是在不一樣的模塊中建立和更新的,但有兩個模塊徹底專用於變量:geo
和map
。 geo
模塊用於根據客戶端的 IP 地址進行跟蹤。此模塊能夠建立依賴於客戶端 IP 地址的任意變量。另外一個模塊map
容許從其餘變量建立變量,實質上提供了對主機名和其餘運行時變量進行靈活映射的能力。這種模塊能夠稱爲變量處理程序。
在單個 nginx worker 中實現的內存分配機制在某種程度上受到了 Apache 的啓發。nginx 內存管理的高度概述以下:對於每一個鏈接,必要的內存緩衝區被動態分配,連接,用於存儲和操做請求和響應的頭部和主體,而後在鏈接釋放時釋放。值得注意的是,nginx 試圖儘量避免在內存中複製數據,而且大多數數據都是經過指針值傳遞的,而不是經過調用memcpy
。
更深刻一點,當模塊生成響應時,將檢索到的內容放入內存緩衝區,而後將其添加到緩衝鏈連接中。後續處理也適用於此緩衝鏈連接。緩衝鏈在 nginx 中很是複雜,由於有幾種處理方案因模塊類型而異。例如,在實現 body filter 模塊時精確管理緩衝區可能很是棘手。這樣的模塊一次只能在一個緩衝區(鏈路的鏈路)上運行,它必須決定是否覆蓋輸入緩衝區,用新分配的緩衝區替換緩衝區,或者在有問題的緩衝區以前或以後插入新的緩衝區。更復雜的是,有時模塊會收到幾個緩衝區,所以它必須有一個不完整的緩衝區鏈。可是,此時 nginx 只提供了一個用於操做緩衝區鏈的低級 API,所以在進行任何實際實現以前,第三方模塊開發人員應該可以熟練使用 nginx 這個神祕的部分。
關於上述方法的註釋是在鏈接的整個生命週期中分配了內存緩衝區,所以對於長期鏈接,保留了一些額外的內存。同時,在空閒的 keepalive 鏈接上,nginx 只花費 550 個字節的內存。對 nginx 的將來版本進行可能的優化將是重用和共享內存緩衝區以實現長期鏈接。
管理內存分配的任務由 nginx 池分配器完成。共享內存區域用於接受互斥鎖,緩存元數據,SSL 會話緩存以及與帶寬管制和管理(限制)相關的信息。在 nginx 中實現了一個 slab 分配器來管理共享內存分配。爲了同時安全地使用共享內存,可使用許多鎖定機制(互斥鎖和信號量)。爲了組織複雜的數據結構,nginx 還提供了一個紅黑樹實現。紅黑樹用於將緩存元數據保存在共享內存中,跟蹤非正則表達式位置定義以及其餘幾項任務。
遺憾的是,上述全部內容從未以一致和簡單的方式描述,所以開發 nginx 的第三方擴展的工做很是複雜。雖然存在關於 nginx 內部的一些好的文檔 - 例如,由 Evan Mille r 生成的那些文檔 - 須要大量的逆向工程工做,而且 nginx 模塊的實現仍然是許多人的黑科技。
儘管與第三方模塊開發相關的某些困難,nginx 用戶社區最近看到了許多有用的第三方模塊。例如,有一個用於 nginx 的嵌入式 Lua 解釋器模塊,用於負載均衡的附加模塊,完整的 WebDAV 支持,高級緩存控制以及本章做者鼓勵並將在將來支持的其餘有趣的第三方工做。(參考 Open Resty -- 譯者注)
當 Igor Sysoev 開始編寫 nginx 時,大多數給互聯網賦能的軟件已經存在,而且這種軟件的體系結構一般遵循傳統服務器和網絡硬件,操做系統和舊的互聯網體系結構。然而,這並無阻止 Igor 認爲他可以繼續改進 Web 服務器領域的東西。因此,雖然第一課可能看起來很簡單,但事實是:總有改進的餘地。
考慮到更好的 Web 軟件的想法,Igor 花了不少時間開發初始代碼結構並研究爲各類操做系統優化代碼的不一樣方法。十年後,參考在版本 1 上的多年積極開發,他現在正在開發 nginx 版本 2.0 的原型。很明顯,一個軟件產品的新架構的初始原型和初始代碼結構對於將來的重要性是很是重要的。
值得一提的另外一點是發展應該集中。Windows 版本的 nginx 多是一個很好的例子,說明如何避免在既不是開發人員的核心競爭力或目標應用程序的狀況下稀釋開發工做。它一樣適用於重寫引擎,該引擎在屢次嘗試加強 nginx 時出現,具備更多功能以便與現有的舊設置向後兼容。
但值得一提的是,儘管 nginx 開發者社區不是很大,但 nginx 的第三方模塊和擴展一直是其受歡迎程度的重要組成部分。 Evan Miller,Piotr Sikora,Valery Kholodkov,Zhang Yichun(agentzh 中文名:章亦春)以及其餘才華橫溢的軟件工程師所作的工做獲得了 nginx 用戶社區及其原始開發人員的讚揚。
This work is made available under the Creative Commons Attribution 3.0 Unported license. Please see the full description of the license for details.
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。