為了更好地理解設計,你需要了解NGINX是如何工作的。NGINX之所以能在性能上如此優越,是由於其背後的設計。許多web伺服器和應用伺服器使用簡單的線程的(threaded)、或基於流程的(process-based)架構, NGINX則以一種複雜的事件驅動(event-driven)的架構脫穎而出,這種架構能支持現代硬體上成千上萬的並發連接。

Inside NGINX infographic涉及了從高層次進程架構的挖掘,到NGINX的單進程處理多連接的圖解。本篇文章講解了這些工作細節。

  設置場景——NGINX進程模型

  為了更好地理解設計,你需要了解NGINX是如何工作的。NGINX有一個主進程(master process)(執行特權操作,如讀取配置、綁定埠)和一系列工作進程(worker process)和輔助進程(helper process)。

  這個四核伺服器內,NGINX主進程創建了4個工作進程和2個緩存輔助進程(cachehelper processes)來管理磁碟內容緩存(on-disk content cache)。

  為什麼架構很重要?

  任何Unix應用程序的根本基礎都是線程或進程(從Linux操作系統的角度看,線程和進程基本上是相同的,主要區別是他們共享內存的程度)。進程或線程,是一組操作系統可調度的、運行在CPU內核上的獨立指令集。大多數複雜的應用程序都並行運行多個線程或進程,原因有兩個:

  • · 可以同時使用更多的計算機內核。
  • · 線程和進程使並行操作很容易實現(例如,同時處理多個連接)。

  進程和線程都消耗資源。它們都使用內存和其他OS資源,導致內核頻繁切換(被稱作上下文切換(context switch)的操作)。大多數現代伺服器可以同時處理數百個小的、活躍的(active)線程或進程,但一旦內存耗盡,或高I/O負載導致大量的上下文切換時,伺服器的性能就會嚴重下降。 對於網路應用,通常會為每個連接(connection)分配一個線程或進程。這種架構易於實現,但是當應用程序需要處理成千上萬的並發連接時,這種架構的擴展性就會出現問題。

NGINX是如何工作的?

  NGINX使用一個了可預見式的(predictable)進程模型,調度可用的硬體資源:

  1. 主進程執行特權操作,如讀取配置和綁定埠,還負責創建子進程(下面的三種類型)。

  2. 緩存載入進程(cache loader process)在啟動時運行,把基於磁碟的緩存(disk-based cache)載入到內存中,然後退出。對它的調度很謹慎,所以其資源需求很低。

  3. 緩存管理進程(cache manager process)週期性運行,並削減磁碟緩存(prunes entries from the disk caches),以使其保持在配置範圍內。

  4. 工作進程(worker processes)纔是執行所有實際任務的進程:處理網路連接、讀取和寫入內容到磁碟,與上游伺服器通信等。

  多數情況下,NGINX建議每1個CPU核心都運行1個工作進程,使硬體資源得到最有效的利用。你可以在配置中設置如下指令: worker_processes auto,當NGINX伺服器在運行時,只有工作進程在忙碌。每個工作進程都以非阻塞的方式處理多個連接,以削減上下文切換的開銷。 每個工作進程都是單線程且獨立運行的,抓取並處理新的連接。進程間通過共享內存的方式,來共享緩存數據、會話持久性數據(session persistence data)和其他共享資源。

NGINX內部的工作進程

  每一個NGINX的工作進程都是NGINX配置(NGINX configuration)初始化的,並被主進程設置了一組監聽套接字(listen sockets)。

  NGINX工作進程會監聽套接字上的事件(accept_mutex和kernel socketsharding),來決定什麼時候開始工作。事件是由新的連接初始化的。這些連接被會分配給狀態機(statemachine)—— HTTP狀態機是最常用的,但NGINX還為流(原生TCP)和大量的郵件協議(SMTP,IMAP和POP3)實現了狀態機。

  狀態機本質上是一組告知NGINX如何處理請求的指令。大多數和NGINX具有相同功能的web伺服器也使用類似的狀態機——只是實現不同。

  調度狀態機

  把狀態機想像成國際象棋的規則。每個HTTP事務(HTTP transaction)都是一局象棋比賽。棋盤的一邊是web伺服器——坐著一位可以迅速做出決定的大師級棋手。另一邊是遠程客戶端——在相對較慢的網路中,訪問站點或應用程序的web瀏覽器。 然而,比賽的規則可能會很複雜。例如,web伺服器可能需要與各方溝通(代理一個上游的應用程序),或者和認證伺服器交流。web伺服器的第三方模塊也可以拓展比賽規則。

  阻塞狀態機

  回憶一下我們之前對進程和線程的描述:是一組操作系統可調度的、運行在CPU內核上的獨立指令集。大多數web伺服器和web應用都使用一個連接/一個進程或一個連接/一個線程的模型來進行這局國際象棋比賽。每個進程或線程都包含一個將比賽玩到最後的指令。在這個過程中,進程是由伺服器來運行的,它的大部分時間都花在「阻塞(blocked)」上,等待客戶端完成其下一個動作。

  1. web伺服器進程(web server process)在監聽套接字上,監聽新的連接(客戶端發起的新比賽)。

  2. 一局新的比賽發起後,進程就開始工作,每一步棋下完後都進入阻塞狀態,等待客戶端走下一步棋。

  3. 一旦比賽結束,web伺服器進程會看看客戶是否想開始新的比賽(這相當於一個存活的連接)。如果連接被關閉(客戶端離開或者超時),web伺服器進程會回到監聽狀態,等待全新的比賽。

  記住重要的一點:每一個活躍的HTTP連接(每局象棋比賽)都需要一個專用的進程或線程(一位大師級棋手)。這種架構非常易於擴展第三方模塊(「新規則」)。然而,這裡存在著一個巨大的不平衡:一個以文件描述符(file descriptor)和少量內存為代表的輕量級HTTP連接,會映射到一個單獨的進程或線程——它們是非常重量級的操作系統對象。這在編程上是方便的,但它造成了巨大的浪費。

NGINX是真正的大師

NGINX is a True Grandmaster

  也許你聽說過車輪表演賽,在比賽中一個象棋大師要在同一時間對付幾十個對手。

Kiril Georgiev在保加利亞首都索菲亞同時對陣360名棋手,最終取得284勝,70平,6負的戰績。

  這就是NGINX工作進程玩「國際象棋」的方式。每一個工作進程都是一位大師(記住:通常情況下,每個工作進程佔用一個CPU內核),能夠同時對戰上百棋手(實際上是成千上萬)。

  1. 工作進程在監聽套接字和連接套接字上等待事件。

  2. 事件發生在套接字上,工作進程會處理這些事件。

  • · 監聽套接字上的事件意味著:客戶端開始了一局新的遊戲。工作進程創建了一個新的連接套接字。
  • · 連接套接字上的事件意味著:客戶端移動了棋子。工作進程會迅速響應。

  工作進程從不會在網路上停止,它時時刻刻都在等待其「對手」(客戶端)做出回應。當它已經移動了這局比賽的棋子,它會立即去處理下一局比賽,或者迎接新的對手。

  為什麼它會比阻塞式多進程的架構更快?

  NGINX的規模可以很好地支持每個工作進程上數以萬計的連接。每個新連接都會創建另一個文件描述符,並消耗工作進程中少量的額外內存。每一個連接的額外消耗都很少。NGINX進程可以保持固定的CPU佔用率。當沒有工作時,上下文切換也較少。

  在阻塞式的、一個連接/一個進程的模式中,每個連接需要大量的額外資源和開銷,並且上下文切換(從一個進程到另一個進程)非常頻繁。

  如果想了解更多,請查看由NGINX公司發展和聯合創始人副總裁Andrew Alexeev編寫的有關NGINX體系結構的文章。

  通過適當的系統調優,NGINX能大規模地處理每個工作進程數十萬並發的HTTP連接,並且能在流量高峯期間不丟失任何信息(新比賽開始)。

  配置更新和NGINX升級

  僅包含少量工作進程的NGINX進程架構,使得配置、甚至是二進位文件本身的更新都非常高效。

  更新NGINX的配置,是一個非常簡單的、輕量級的、可靠的操作。運行nginx –s reload命令即可,該命令會檢查磁碟上的配置,並給主進程發送一個SIGHUP信號。

  當主進程接收到SIGHUP信號後,會做兩件事:

  1. 重新載入配置,fork一套新的工作進程。這些新的工作進程會立即開始接受連接和處理流量(traffic)(使用新的配置)。

  2. 發出信號,通知舊的工作進程安靜地退出。這些舊進程不會再接受新的連接了。只要它們處理的HTTP請求結束了,它們就會幹凈地關閉連接。一旦所有的連接都被關閉,工作進程也就退出了。

  這個過程會導致CPU佔用率和內存使用的一個小高峯,但相比於從活動連接中載入資源,這個小高峯可忽略不計。你可以在一秒內重新載入配置多次。極少情況下,一代又一代工作進程等待連接關閉時會出現問題,但即便出現問題,它們也會被立即解決掉。

  NGINX的二進位升級過程更加神奇——你可以飛速地升級NGINX本身,伺服器不會有任何的丟連接、宕機、或服務中斷等情況。

  二進位升級過程與配置更新相似。新的NGINX主進程與原來的主進程並行,它們共享監聽套接字。兩個進程都是活躍的(active),它們各自的工作進程處理各自的流量(traffic)。然後,你可以通知舊的主進程與其工作進程完美退出。

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

  結論

NGINX的內部圖表高度概述了NGINX是如何運作的,但在這簡單的解釋背後是超過十年的創新與優化。這些創新與優化,使NGINX在多種硬體上表現出良好的性能,同時還具備現代web應用所需要的安全性和可靠性。

  如果你想閱讀更多關於NGINX優化的資料,可以看看這些很棒的資源:

NGINX安裝和性能調優(webinar; slides at Speaker Deck)

NGINX的性能調優

開源應用架構——NGINX

NGINX1.9.1套接字切分(Socket Sharding)(使用SO_REUSEPORT套接字選項)

推薦閱讀:

相關文章