[轉]深刻 NGINX: 爲性能和擴展所作之設計

NGINX在web性能上的表現尤其出衆,這徹底得益於其設計方式,許多web和應用服務器都是基於線程或進程這種簡單的架構,NGINX用了一種精妙的事件驅動架構,在現代的硬件上,它能夠處理成千上萬的併發鏈接。nginx

Inside NGINX中的信息圖對高級別的進程架構和NGINX如何在單個進程中處理多個鏈接進行了深刻探討。本文更進一步地闡述了NGINX的全部工做原理。web

背景——NGINX進程模型

pic1.png

要更好的理解這個設計,須要熟悉NGINX的運行過程。NGINX有一個主進程(該進程執行一些特權操做,例如讀取配置以及綁定端口)以及若干worker進程和helper進程。編程

pic2.jpg

在這個4核服務器上,NGINX主進程建立了4個worker進程以及一對用來管理磁盤內容緩存的緩存helper進程。瀏覽器

架構爲何重要?

任何Unix應用的基礎都是線程或進程。(從Linux操做系統的角度看,線程和進程幾乎是同樣的;最大的區別是內存共享的度。)一個線程或進程是一組自包含的指令,這些指令可由操做系統調度到某個CPU核心上運行。大部分複雜應用並行運行多個線程或進程通常有兩個緣由:緩存

  • 能夠同時使用多個CPU核心。
  • 線程和進程讓並行操做變得簡單(例如,同時處理多個鏈接)。

進程和線程會消耗資源。每一個進程或線程都會使用內存以及其餘操做系統資源,他們都須要切換CPU(稱做上下文切換)。大部分現代的服務器都能同時處理幾百個小的、活動的線程或進程,可是,一旦內存耗盡或是遇到高I/O負載致使大量上下文切換時性能就會急劇降低。安全

常規的網絡應用設計都是爲每一個鏈接分配一個線程或進程。這種架構簡單且容易實現,可是,當應用須要同時處理成千上萬的鏈接時,擴展性就很差了。服務器

NGINX是怎麼運行的?

NGINX用了一個可預測的進程模型,支持衆多硬件:網絡

  • 主進程執行一些特權操做,好比讀取配置以及綁定端口,而後建立少數子進程(下面的三種類型)。
  • 緩存加載進程在啓動時運行,用於將磁盤上的緩存加載到內存中,隨後退出。對這個進程的調度很保守,因此其資源需求比較低。
  • 緩存管理進程會週期性地運行,從磁盤緩存中刪除條目以保證緩存沒有超過配置的大小。
  • worker進程作了全部的工做!它們處理網絡鏈接,從磁盤讀取內容或往磁盤中寫入內容,以及與上游服務器通訊。

部分場景中推薦的NGINX配置是 —— 每一個CPU核心運行一個worker進程 —— 以充分利用硬件資源。在配置中加入worker_processes auto指令便可:架構

worker_processes auto;

當NGINX服務器活動時,只有worker進程是處於繁忙狀態的。每一個worker進程以非阻塞的方式處理多個鏈接,這減小了上下文切換的次數。併發

每一個worker進程都是單線程的而且是獨立運行的,它們捕獲新的鏈接而後進行處理。進程之間的共享緩存數據、會話持久數據以及其它共享資源的通訊經過共享內存實現。

深刻理解NGINX Worker進程

pic3.png

每一個worker進程都是用NGINX配置進行初始化的,而且由主進程提供了一組監聽套接字。

NGINX worker進程從等待監聽套接字上的事件開始(accept_mutex和內核套接字切分(kernel socket sharding))。事件由新進來的鏈接進行初始化。這些鏈接被分配給一個狀態機 —— HTTP狀態機是最經常使用的,但NGINX也爲流(原始TCP)流量以及一些郵件協議(SMTP,IMAP和POP3)實現了狀態機。

pic4.png

狀態機本質上是一組指令,由它們告訴NGINX如何處理請求。大部分執行與NGINX相同方法的web服務器也用的相似的狀態機 —— 區別在於實現。

狀態機的調度

將狀態機想象成象棋規則。每一個HTTP事務就是一盤象棋遊戲。棋盤的一側是web服務器 —— 一個能夠快速作決定的象棋大師。另外一側是遠程客戶端 —— 正在相對較慢的網絡中訪問站點或應用的web瀏覽器。

然而,遊戲的規則可能會很是複雜。好比,web服務器也許要與其它方(代理到上游應用)進行交流或是要與認證服務器對話。web服務器中的第三方模塊甚至還可能擴展遊戲規則。

阻塞模式的狀態機

前面咱們提到,一個線程或進程是一組自包含的指令,這些指令可由操做系統調度到某個CPU核心上運行。大部分web服務器以及web應用使用的是每一個鏈接分配一個進程或每一個鏈接分配一個線程的模式來處理的。每一個進程或線程都包含了從開始到結束須要執行的指令。在服務器運行進程期間,大部分時間都是「阻塞的」 —— 等待客戶端完成其下一個動做。

pic5.png

  1. web服務器進程在監聽套接字上監聽新的鏈接(由客戶端初始化的新遊戲)。
  2. 當新遊戲準備好後,就開始遊戲,每一個動做以後都會阻塞,等待客戶端的響應。
  3. 一旦遊戲結束,web服務器進程可能還要等等看是否這個客戶端須要發起一輪新的遊戲(這至關於keepalive鏈接)。若是鏈接被關閉(客戶端離開或超時),web服務器進程返回去監聽新的遊戲請求。

關鍵的一點在於每一個活動的HTTP鏈接(每盤象棋遊戲)都須要一個專門的進程或線程(一個象棋大師)。這種架構在擴展第三方模塊(「新的規則」)時很是簡單方便。然而,存在一個巨大的失衡問題:至關輕量級的HTTP鏈接,本由一個文件描述符和少許的內存來表示,卻映射到了一個單獨的線程或進程這種很是重量級的操做系統對象。編程是便利了,但倒是個很大的浪費。

NGINX是真大師

也許你已經聽過simultaneous exhibition遊戲,一個象棋大師同時與幾十個對手對戰。

pic6.jpg

這就是NGINX worker進程下「象棋」的方式。每一個worker進程(記住 —— 一般是每一個CPU核心一個worker進程)都是一個大師,能夠同時處理幾百盤(其實是成千上萬)遊戲。

pic7.png

  1. worker進程等待監聽和鏈接套接字上的事件。
  2. 套接字上發生事件後,worker進程開始進行處理:

    • 監聽套接字上的事件意味着有個客戶端發起了一盤新的象棋遊戲。worker進程建立出一個新的鏈接套接字。
    • 鏈接套接字上的事件意味着客戶端開始有新的動做了。worker進程就迅速響應。

worker進程永遠不會由於網絡擁堵而阻塞來等待「對手」(客戶端)的響應。當處理完一個動做,worker進程當即去處理其餘遊戲中等待處理的動做,或是迎接新玩家的到來。

爲何這比阻塞的、多進程架構更快?

NGINX的可伸縮性很是好,每一個worker進程能夠支撐成千上萬個鏈接。每一個新的鏈接會建立一個文件描述符以及消耗worker進程中少許的額外內存。每一個鏈接的額外開銷極少。NGINX進程能夠綁定到CPU。上下文切換是比較罕見的,只有沒有任務要處理時纔會發生。

在阻塞的、每一個鏈接一個進程的方式下,每一個鏈接都須要大量的額外資源與開銷,且上下文切換(從一個進程切換到另外一個)很是頻繁。

更多細節解釋,看看這篇有關NGINX架構的文章。

經過適當的系統調優,NGINX worker進程能夠處理成千上萬的併發HTTP鏈接,可以承受流量峯值(新遊戲蜂擁而至)還不會錯過一個請求。

配置更新與NGINX升級

NGINX這種使用少數worker進程的進程架構,能夠很是高效的進行配置更新甚至是更新NGINX介質自己。

pic8.png

更新NGINX配置是個很簡單、輕量級且可靠的操做。一般只是意味着去運行一下nginx –s reload命令,這個命令會去檢查磁盤上的配置,給主進程發送一個SIGHUP信號。

當主進程收到SIGHUP信號,會作兩件事:

  1. 從新加載配置,而後fork出一組新的worker進程。這些新的worker進程會立馬開始接受鏈接並處理(用的是新的配置)。
  2. 發信號讓舊的worker進程優雅退出。舊的worker進程中止接受新的鏈接。一旦每一個當前的HTTP請求完成,worker進程將利索地結束鏈接(也就是,沒有lingering keeplive)。一旦全部鏈接都關閉了,worker進程就能夠退出了。

這個配置加載進程會形成CPU和內存使用上的一個小峯值,但相比活動的鏈接帶來的資源負載,這是極其微小的。能夠每秒從新加載配置屢次(有許多NGINX用戶的確是這麼幹的)。多代NGINX worker進程都在等待鏈接關閉極少會形成問題,但即使有問題也會很快解決。

NGINX介質升級過程達到了高可用性的標準 —— 能夠直接對線上運行的NGINX升級,而不會丟失任何鏈接,也不會有停機時間與服務中斷。

pic9.png

介質升級過程與從新加載配置的過程相似。一個新的NGINX主進程會與舊的主進程並行運行,它們共享監聽套接字。兩個進程都是活動的,而且各自對應的worker進程都還在處理請求。隨後能夠發信號讓舊的主進程及其worker進程優雅退出。

整個過程在Controlling NGINX中有更詳細的描述。

總結

這篇深刻NGINX信息圖從高層次概述了NGINX的功能,可是在這個簡單解釋的背後,是十多年的創新與優化,才使NGINX在保證安全和可靠性的同時,在衆多硬件上都能發揮出最佳性能。

本文來源地址:[譯]深刻 NGINX: 爲性能和擴展所作之設計

相關文章
相關標籤/搜索