深入瞭解 Nginx!

今天我們來談談什麼是 Nginx?

什麼是 Nginx?

Nginx 代碼完全用 C 語言從頭寫成,已經移植到許多體系結構和操作系統,包括:Linux、FreeBSD、Solaris、Mac OS X、AIX 以及 Microsoft Windows。

Nginx 有自己的函數庫,並且除了 zlib、PCRE 和 OpenSSL 之外,標準模塊只使用系統 C 庫函數。而且,如果不需要或者考慮到潛在的授權衝突,可以不使用這些第三方庫

Nginx (engine x) 是一個高性能的 HTTP 和反向代理 web 服務器,同時也提供了 IMAP/POP3/SMTP 服務。Nginx 是由伊戈爾 · 賽索耶夫爲俄羅斯訪問量第二的 Rambler.ru 站點(俄文:Рамблер)開發的,第一個公開版本 0.1.0 發佈於 2004 年 10 月 4 日。

其將源代碼以類 BSD 許可證的形式發佈,因它的穩定性、豐富的功能集、示例配置文件和低系統資源的消耗而聞名。2011 年 6 月 1 日,nginx 1.0.4 發佈。

Nginx 是一款輕量級的 Web 服務器 / 反向代理服務器及電子郵件(IMAP/POP3)代理服務器,在 BSD-like 協議下發行。其特點是佔有內存少,併發能力強,事實上 nginx 的併發能力在同類型的網頁服務器中表現較好,中國大陸使用 nginx 網站用戶有:百度、京東、新浪、網易、騰訊、淘寶等。

一句話 Nginx 就是牛逼,成熟,穩定 ,應用範圍廣 主要還是開源。

Nginx 架構原理

反向代理

概念

反向代理(Reverse Proxy)方式是指以代理服務器來接受 internet 上的連接請求,然後將請求轉發給內部網絡上的服務器,並將從服務器上得到的結果返回給 internet 上請求連接的客戶端,此時代理服務器對外就表現爲一個服務器。

舉個例子,比如我想訪問
http://www.test.com/readme,但 www.test.com 上並不存在 readme 頁面,於是他是偷偷從另外一臺服務器上取回來,然後作爲自己的內容返回用戶,但用戶並不知情。這裏所提到的 www.test.com 這個域名對應的服務器就設置了反向代理功能。

結論就是,反向代理服務器對於客戶端而言它就像是原始服務器,並且客戶端不需要進行任何特別的設置。客戶端向反向代理的命名空間 (name-space) 中的內容發送普通請求,接着反向代理服務器將判斷向何處 (原始服務器) 轉交請求,並將獲得的內容返回給客戶端,就像這些內容原本就是它自己的一樣。

正向代理,既然有反向代理,就肯定有正向代理。什麼叫正向代理呢?

正向代理(Forward Proxy)通常都被簡稱爲代理,就是在用戶無法正常訪問外部資源,比方說受到 GFW 的影響無法訪問 twitter 的時候,我們可以通過代理的方式,讓用戶繞過防火牆,從而連接到目標網絡或者服務。

正向代理的工作原理就像一個跳板,比如:我訪問不了 google.com,但是我能訪問一個代理服務器 A,A 能訪問 google.com,於是我先連上代理服務器 A,告訴他我需要 google.com 的內容,A 就去取回來,然後返回給我。從網站的角度,只在代理服務器來取內容的時候有一次記錄,有時候並不知道是用戶的請求,也隱藏了用戶的資料,這取決於代理告不告訴網站。

**結論就是,正向代理是一個位於客戶端和原始服務器 (origin server) 之間的服務器。**爲了從原始服務器取得內容,客戶端向代理發送一個請求並指定目標 (原始服務器),然後代理向原始服務器轉交請求並將獲得的內容返回給客戶端。

反向代理 VS 正向代理:

1.2 工作流程

1.3 優點

通常的代理服務器,只用於代理內部網絡對 Internet 外部網絡的連接請求,客戶機必須指定代理服務器,並將本來要直接發送到 Web 服務器上的 http 請求發送到代理服務器中。不支持外部網絡對內部網絡的連接請求,因爲內部網絡對外部網絡是不可見的。當一個代理服務器能夠代理外部網絡上的主機,訪問內部網絡時,這種代理服務的方式稱爲反向代理服務。此時代理服務器對外就表現爲一個 Web 服務器,外部網絡就可以簡單把它當作一個標準的 Web 服務器而不需要特定的配置。不同之處在於,這個服務器沒有保存任何網頁的真實數據,所有的靜態網頁或者 CGI 程序,都保存在內部的 Web 服務器上。因此對反向代理服務器的攻擊並不會使得網頁信息遭到破壞,這樣就增強了 Web 服務器的安全性。

企業內所有的網站共享一個在 internet 中註冊的 IP 地址,這些服務器分配私有地址,採用虛擬主機的方式對外提供服務。

反向代理就是通常所說的 web 服務器加速,它是一種通過在繁忙的 web 服務器和外部網絡之間增加一個高速的 web 緩衝服務器來降低實際的 web 服務器的負載的一種技術。反向代理是針對 web 服務器提高加速功能,作爲代理緩存,它並不是針對瀏覽器用戶,而針對一臺或多臺特定的 web 服務器,它可以代理外部網絡對內部網絡的訪問請求。

反向代理服務器會強制將外部網絡對要代理的服務器的訪問經過它,這樣反向代理服務器負責接收客戶端的請求,然後到源服務器上獲取內容,把內容返回給用戶,並把內容保存到本地,以便日後再收到同樣的信息請求時,它會把本地緩存裏的內容直接發給用戶,以減少後端 web 服務器的壓力,提高響應速度。因此 Nginx 還具有緩存功能。

(1)請求的統一控制,包括設置權限、過濾規則等;

(2)區分動態和靜態可緩存內容;

(3)實現負載均衡,內部可以採用多臺服務器來組成服務器集羣,外部還是可以採用一個地址訪問;

(4)解決 Ajax 跨域問題;

(5)作爲真實服務器的緩衝,解決瞬間負載量大的問題;

(6)支持其他插件廣泛應用 自帶豐富的庫文件 lib 底層 C 語言編寫,所以異常強大。

Nginx 模塊

Nginx 有五大優點:模塊化、事件驅動、異步、非阻塞、多進程單線程。由內核和模塊組成的,其中內核完成的工作比較簡單,僅僅通過查找配置文件將客戶端請求映射到一個 location block,然後又將這個 location block 中所配置的每個指令將會啓動不同的模塊去完成相應的工作。

模塊劃分

Nginx 的模塊從結構上分爲核心模塊、基礎模塊和第三方模塊:

Nginx 的模塊從功能上分爲如下四類:

Nginx 的核心模塊主要負責建立 nginx 服務模型、管理網絡層和應用層協議、以及啓動針對特定應用的一系列候選模塊。其他模塊負責分配給 web 服務器的實際工作:

模塊處理

1、當服務器啓動,每個 handlers(處理模塊) 都有機會映射到配置文件中定義的特定位置(location);如果有多個 handlers(處理模塊) 映射到特定位置時,只有一個會 “贏”(說明配置文件有衝突項,應該避免發生)。

處理模塊以三種形式返回:

2、如果 handlers(處理模塊) 把請求反向代理到後端的服務器,就變成另外一類的模塊:load-balancers(負載均衡模塊)。負載均衡模塊的配置中有一組後端服務器,當一個 HTTP 請求過來時,它決定哪臺服務器應當獲得這個請求。

Nginx 的負載均衡模塊採用兩種方法:

3、如果 handlers(處理模塊) 沒有產生錯誤,filters(過濾模塊)將被調用。多個 filters(過濾模塊)能映射到每個位置,所以(比如)每個請求都可以被壓縮成塊。它們的執行順序在編譯時決定。

filters(過濾模塊)是經典的 “接力鏈表(CHAIN OF RESPONSIBILITY)” 模型:一個 filters(過濾模塊)被調用,完成其工作,然後調用下一個 filters(過濾模塊),直到最後一個 filters(過濾模塊)。

過濾模塊鏈的特別之處在於:

過濾模塊能以 buffer(緩衝區)爲單位進行操作,這些 buffer 一般都是一頁(4K)大小,當然你也可以在 nginx.conf 文件中進行配置。這意味着,比如,模塊可以壓縮來自後端服務器的響應,然後像流一樣的到達客戶端,直到整個響應發送完成。

總之,過濾模塊鏈以流水線的方式高效率地向客戶端發送響應信息。

所以總結下上面的內容,一個典型的 HTTP 處理週期是這樣的:

下圖展示了 Nginx 模塊處理流程:

Nginx 本身做的工作實際很少,當它接到一個 HTTP 請求時,它僅僅是通過查找配置文件將此次請求映射到一個 location block,而此 location 中所配置的各個指令則會啓動不同的模塊去完成工作,因此模塊可以看做 Nginx 真正的勞動工作者。通常一個 location 中的指令會涉及一個 handler 模塊和多個 filter 模塊(當然,多個 location 可以複用同一個模塊)。handler 模塊負責處理請求,完成響應內容的生成,而 filter 模塊對響應內容進行處理。

Nginx 請求處理

Nginx 在啓動時會以 daemon 形式在後臺運行,採用多進程 + 異步非阻塞 IO 事件模型來處理各種連接請求。多進程模型包括一個 master 進程,多個 worker 進程,一般 worker 進程個數是根據服務器 CPU 核數來決定的。master 進程負責管理 Nginx 本身和其他 worker 進程。如下圖:

Master 進程負責管理 Nginx 本身和其他 worker 進程

從上圖中可以很明顯地看到,4 個 worker 進程的父進程都是 master 進程,表明 worker 進程都是從父進程 fork 出來的,並且父進程的 ppid 爲 1,表示其爲 daemon 進程。

需要說明的是,在 nginx 多進程中,每個 worker 都是平等的,因此每個進程處理外部請求的機會權重都是一致的。

Master 進程的作用是?

讀取並驗證配置文件 nginx.conf;管理 worker 進程;

Worker 進程的作用是?

每一個 Worker 進程都維護一個線程(避免線程切換),處理連接和請求;注意 Worker 進程的個數由配置文件決定,一般和 CPU 個數相關(有利於進程切換),配置幾個就有幾個 Worker 進程。

Nginx 如何做到熱部署?

所謂熱部署,就是配置文件 nginx.conf 修改後,不需要 stop Nginx,不需要中斷請求,就能讓配置文件生效!(nginx -s reload 重新加載 / nginx -t 檢查配置 / nginx -s stop)

通過上文我們已經知道 worker 進程負責處理具體的請求,那麼如果想達到熱部署的效果,可以想象:

方案一:

修改配置文件 nginx.conf 後,主進程 master 負責推送給 woker 進程更新配置信息,woker 進程收到信息後,更新進程內部的線程信息。

方案二:

修改配置文件 nginx.conf 後,重新生成新的 worker 進程,當然會以新的配置進行處理請求,而且新的請求必須都交給新的 worker 進程,至於老的 worker 進程,等把那些以前的請求處理完畢後,kill 掉即可。

Nginx 採用的就是方案二來達到熱部署的!

Nginx 如何做到高併發下的高效處理?

上文已經提及 Nginx 的 worker 進程個數與 CPU 綁定、worker 進程內部包含一個線程高效迴環處理請求,這的確有助於效率,但這是不夠的。

作爲專業的程序員,我們可以開一下腦洞:BIO/NIO/AIO、異步 / 同步、阻塞 / 非阻塞…

要同時處理那麼多的請求,要知道,有的請求需要發生 IO,可能需要很長時間,如果等着它,就會拖慢 worker 的處理速度。

Nginx 採用了 Linux 的 epoll 模型,epoll 模型基於事件驅動機制,它可以監控多個事件是否準備完畢,如果 OK,那麼放入 epoll 隊列中,這個過程是異步的。worker 只需要從 epoll 隊列循環處理即可。

Nginx 掛了怎麼辦?

Nginx 既然作爲入口網關,很重要,如果出現單點問題,顯然是不可接受的。

答案是:Keepalived+Nginx 實現高可用。

Keepalived 是一個高可用解決方案,主要是用來防止服務器單點發生故障,可以通過和 Nginx 配合來實現 Web 服務的高可用。(其實,Keepalived 不僅僅可以和 Nginx 配合,還可以和很多其他服務配合)

Keepalived+Nginx 實現高可用的思路:

第一:請求不要直接打到 Nginx 上,應該先通過 Keepalived(這就是所謂虛擬 IP,VIP)

第二:Keepalived 應該能監控 Nginx 的生命狀態(提供一個用戶自定義的腳本,定期檢查 Nginx 進程狀態,進行權重變化,,從而實現 Nginx 故障切換)

可以通過其他工具做動態負載均衡。

Nginx 真正處理請求業務的是 Worker 之下的線程。worker 進程中有一個 ngx_worker_process_cycle() 函數,執行無限循環,不斷處理收到的來自客戶端的請求,並進行處理,直到整個 Nginx 服務被停止。

worker 進程中,ngx_worker_process_cycle() 函數就是這個無限循環的處理函數。在這個函數中,一個請求的簡單處理流程如下:

多進程處理模型

下面來介紹一個請求進來,多進程模型的處理方式:

首先,master 進程一開始就會根據我們的配置,來建立需要 listen 的網絡 socket fd,然後 fork 出多個 worker 進程。

其次,根據進程的特性,新建立的 worker 進程,也會和 master 進程一樣,具有相同的設置。因此,其也會去監聽相同 ip 端口的套接字 socket fd。

然後,這個時候有多個 worker 進程都在監聽同樣設置的 socket fd,意味着當有一個請求進來的時候,所有的 worker 都會感知到。這樣就會產生所謂的 “驚羣現象”。爲了保證只會有一個進程成功註冊到 listenfd 的讀事件,nginx 中實現了一個“accept_mutex” 類似互斥鎖,只有獲取到這個鎖的進程,纔可以去註冊讀事件。其他進程全部 accept 失敗。

最後,監聽成功的 worker 進程,讀取請求,解析處理,響應數據返回給客戶端,斷開連接,結束。因此,一個 request 請求,只需要 worker 進程就可以完成。

進程模型的處理方式帶來的一些好處就是:進程之間是獨立的,也就是一個 worker 進程出現異常退出,其他 worker 進程是不會受到影響的;此外,獨立進程也會避免一些不需要的鎖操作,這樣子會提高處理效率,並且開發調試也更容易。

如前文所述,多進程模型 + 異步非阻塞模型纔是勝出的方案。單純的多進程模型會導致連接併發數量的降低,而採用異步非阻塞 IO 模型很好的解決了這個問題;並且還因此避免的多線程的上下文切換導致的性能損失。

worker 進程會競爭監聽客戶端的連接請求:這種方式可能會帶來一個問題,就是可能所有的請求都被一個 worker 進程給競爭獲取了,導致其他進程都比較空閒,而某一個進程會處於忙碌的狀態,這種狀態可能還會導致無法及時響應連接而丟棄 discard 掉本有能力處理的請求。這種不公平的現象,是需要避免的,尤其是在高可靠 web 服務器環境下。

針對這種現象,Nginx 採用了一個是否打開 accept_mutex 選項的值,ngx_accept_disabled 標識控制一個 worker 進程是否需要去競爭獲取 accept_mutex 選項,進而獲取 accept 事件。

ngx_accept_disabled 值:nginx 單進程的所有連接總數的八分之一,減去剩下的空閒連接數量,得到的這個 ngx_accept_disabled。

當 ngx_accept_disabled 大於 0 時,不會去嘗試獲取 accept_mutex 鎖,並且將 ngx_accept_disabled 減 1,於是,每次執行到此處時,都會去減 1,直到小於 0。不去獲取 accept_mutex 鎖,就是等於讓出獲取連接的機會,很顯然可以看出,當空閒連接越少時,ngx_accept_disable 越大,於是讓出的機會就越多,這樣其它進程獲取鎖的機會也就越大。不去 accept,自己的連接就控制下來了,其它進程的連接池就會得到利用,這樣,nginx 就控制了多進程間連接的平衡了。

一個簡單的 HTTP 請求

從 Nginx 的內部來看,一個 HTTP Request 的處理過程涉及到以下幾個階段:

在建立連接過程中,對於 nginx 監聽到的每個客戶端連接,都會將它的讀事件的 handler 設置爲 ngx_http_init_request 函數,這個函數就是請求處理的入口。在處理請求時,主要就是要解析 http 請求,比如:uri,請求行等,然後再根據請求生成響應。下面看一下 nginx 處理的具體過程:

在這裏,我們需要了解一下 phase handler 這個概念。phase 字面的意思,就是階段。所以 phase handlers 也就好理解了,就是包含若干個處理階段的一些 handler。

在每一個階段,包含有若干個 handler,再處理到某個階段的時候,依次調用該階段的 handler 對 HTTP Request 進行處理。

通常情況下,一個 phase handler 對這個 request 進行處理,併產生一些輸出。通常 phase handler 是與定義在配置文件中的某個 location 相關聯的。

一個 phase handler 通常執行以下幾項任務:

當 Nginx 讀取到一個 HTTP Request 的 header 的時候,Nginx 首先查找與這個請求關聯的虛擬主機的配置。如果找到了這個虛擬主機的配置,那麼通常情況下,這個 HTTP Request 將會經過以下幾個階段的處理(phase handlers):

在內容產生階段,爲了給一個 request 產生正確的響應,Nginx 必須把這個 request 交給一個合適的 content handler 去處理。如果這個 request 對應的 location 在配置文件中被明確指定了一個 content handler,那麼 Nginx 就可以通過對 location 的匹配,直接找到這個對應的 handler,並把這個 request 交給這個 content handler 去處理。這樣的配置指令包括像,perl,flv,proxy_pass,mp4 等。

如果一個 request 對應的 location 並沒有直接有配置的 content handler,那麼 Nginx 依次嘗試:

內容產生階段完成以後,生成的輸出會被傳遞到 filter 模塊去進行處理。filter 模塊也是與 location 相關的。所有的 filter 模塊都被組織成一條鏈。輸出會依次穿越所有的 filter,直到有一個 filter 模塊的返回值表明已經處理完成。

這裏列舉幾個常見的 filter 模塊,例如:

在所有的 filter 中,有幾個 filter 模塊需要關注一下。按照調用的順序依次說明如下:

請求完整處理過程

根據以上請求步驟所述,請求完整的處理過程如下圖所示:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/q7f-5x6iinF0ET37nhSe8w