深入 NGINX: 我們如何設計性能和擴展
知識 – NGINX 進程模型
為了更好的理解這個設計,你需要理解 NGINX 如何運行的。NGINX 有一個主進程(它執行特權操作,如讀取配置和綁定埠)和一些工作進程與輔助進程。
# service nginx restart
* Restarting nginx
# ps -ef --forest | grep nginx
root 32475 1 0 13:36 ? 00:00:00 nginx: master process /usr/sbin/nginx
-c /etc/nginx/nginx.conf
nginx 32476 32475 0 13:36 ? 00:00:00 _ nginx: worker process
nginx 32477 32475 0 13:36 ? 00:00:00 _ nginx: worker process
nginx 32479 32475 0 13:36 ? 00:00:00 _ nginx: worker process
nginx 32480 32475 0 13:36 ? 00:00:00 _ nginx: worker process
nginx 32481 32475 0 13:36 ? 00:00:00 _ nginx: cache manager process
nginx 32482 32475 0 13:36 ? 00:00:00 _ nginx: cache loader process
在四核伺服器,NGINX 主進程創建了4個工作進程和兩個管理磁碟內容緩存的緩存輔助進程。
為什麼架構很重要?
任何 Unix 應用程序的根本基礎是線程或進程。(從 Linux 操作系統的角度來看,線程和進程大多是相同的,主要的區別是他們共享內存的程度。)一個線程或進程是一個自包含的指令集,操作系統可以在一個 CPU 核心上調度運行它們。大多數複雜的應用程序並行運行多個線程或進程有兩個原因:
- 它們可以同時使用更多的計算核心。
- 線程或進程可以輕鬆實現並行操作。(例如,在同一時刻保持多連接)。
進程和線程消耗資源。他們每個都使用內存和其他系統資源,他們會在 CPU 核心中換入和換出(這個操作叫做上下文切換)。大多數現代伺服器可以並行保持上百個小型的、活動的線程或進程,但是一旦內存耗盡或高 I/O 壓力引起大量的上下文切換會導致性能嚴重下降。
網路應用程序設計的常用方法是為每個連接分配一個線程或進程。此體系結構簡單、容易實現,但是當應用程序需要處理成千上萬的並發連接時這種結構就不具備擴展性。
NGINX 如何工作?
NGINX 使用一種可預測的進程模式來分配可使用的硬體資源:
- 主進程(master)執行特權操作,如讀取配置和綁定埠,然後創建少量的子進程(如下的三種類型)。
- 緩存載入器進程(cache loader)在載入磁碟緩存到內存中時開始運行,然後退出。適當的調度,所以其資源需求很低。
- 緩存管理器進程(cache manager)定期裁剪磁碟緩存中的記錄來保持他們在配置的大小之內。
- 工作進程(worker)做所有的工作!他們保持網路連接、讀寫內容到磁碟,與上游伺服器通信。
在大多數情況下 NGINX 的配置建議:每個 CPU 核心運行一個工作進程,這樣最有效地利用硬體資源。你可以在配置中包含 worker_processes auto指令配置:
worker_processes auto;
當一個 NGINX 服務處於活動狀態,只有工作進程在忙碌。每個工作進程以非阻塞方式保持多連接,以減少上下文交換。
每個工作進程是一個單一線程並且獨立運行,它們會獲取新連接並處理之。這些進程可以使用共享內存通信來共享緩存數據、會話持久性數據及其它共享資源。(在 NGINX 1.7.11 及其以後版本,還有一個可選的線程池,工作進程可以轉讓阻塞的操作給它。更多的細節,參見「NGINX 線程池可以爆增9倍性能!」。對於 NGINX Plus 用戶,該功能計劃在今年晚些時候加入到 R7 版本中。)
NGINX 工作進程內部
每個 NGINX 工作進程按照 NGINX 配置初始化,並由主進程提供一組監聽埠。
NGINX 工作進程首先在監聽套接字上等待事件(accept_mutex 和內核套接字分片)。事件被新進來的連接初始化。這些連接被分配到一個狀態機 – HTTP 狀態機是最常用的,但 NGINX 也實現了流式(原始 TCP )狀態機和幾種郵件協議(SMTP、IMAP和POP3)的狀態機。
狀態機本質上是一組指令,告訴 NGINX 如何處理一個請求。大多數 web 伺服器像 NGINX 一樣使用類似的狀態機來實現相同的功能 - 區別在於實現。
調度狀態機
把狀態機想像成國際象棋的規則。每個 HTTP 事務是一個象棋遊戲。一方面棋盤是 web 伺服器 —— 一位大師可以非常迅速地做出決定。另一方面是遠程客戶端 —— 在一個相對較慢的網路下 web 瀏覽器訪問網站或應用程序。
不管怎樣,這個遊戲規則很複雜。例如,web 伺服器可能需要與各方溝通(代理一個上游的應用程序)或與身份驗證伺服器對話。web 伺服器的第三方模塊甚至可以擴展遊戲規則。
一個阻塞狀態機
回憶我們之前的描述,一個進程或線程就像一套獨立的指令集,操作系統可以在一個 CPU 核心上調度運行它。大多數 web 伺服器和 web 應用使用每個連接一個進程或者每個連接一個線程的模式來玩這個「象棋遊戲」。每個進程或線程都包含玩完「一個遊戲」的指令。在伺服器運行該進程的期間,其大部分的時間都是「阻塞的」 —— 等待客戶端完成它的下一步行動。
- web 伺服器進程在監聽套接字上監聽新連接(客戶端發起新「遊戲」)
- 當它獲得一個新遊戲,就玩這個遊戲,每走一步去等待客戶端響應時就阻塞了。
- 遊戲完成後,web 伺服器進程可能會等待是否有客戶機想要開始一個新遊戲(這裡指的是一個「保持的」連接)。如果這個連接關閉了(客戶端斷開或者發生超時),web 伺服器進程會返回並監聽一個新「遊戲」。
要記住最重要的一點是每個活動的 HTTP 連接(每局棋)需要一個專用的進程或線程(象棋高手)。這個結構簡單容並且易擴展第三方模塊(「新規則」)。然而,還是有巨大的不平衡:尤其是輕量級 HTTP 連接其實就是一個文件描述符和小塊內存,映射到一個單獨的線程或進程,這是一個非常重量級的系統對象。這種方式易於編程,但太過浪費。
NGINX是一個真正的象棋大師
也許你聽過車輪表演賽遊戲,有一個象棋大師同時對戰許多對手?
列夫·吉奧吉夫在保加利亞的索非亞同時對陣360人。他的最終成績是284勝70平6負。
這就是 NGINX 工作進程如何「下棋」的。每個工作進程(記住 - 通常每個CPU核心上有一個工作進程)是一個可同時對戰上百人(事實是,成百上千)的象棋大師。
- 工作進程在監聽和連接套接字上等待事件。
- 事件發生在套接字上,並且由工作進程處理它們:
- 在監聽套接字的事件意味著一個客戶端已經開始了一局新棋局。工作進程創建了一個新連接套接字。
- 在連接套接字的事件意味著客戶端已經下了一步棋。工作進程及時響應。
一個工作進程在網路流量上從不阻塞,等待它的「對手」(客戶端)做出反應。當它下了一步,工作進程立即繼續其他的遊戲,在那裡工作進程正在處理下一步,或者在門口歡迎一個新玩家。
為什麼這個比阻塞式多進程架構更快?
NGINX 每個工作進程很好的擴展支撐了成百上千的連接。每個連接在工作進程中創建另外一個文件描述符和消耗一小部分額外內存。每個連接有很少的額外開銷。NGINX 進程可以固定在某個 CPU 上。上下文交換非常罕見,一般只發生在沒有工作要做時。
在阻塞方式,每個進程一個連接的方法中,每個連接需要大量額外的資源和開銷,並且上下文切換(從一個進程切換到另一個)非常頻繁。
更詳細的解釋,看看這篇關於 NGINX 架構的文章,它由NGINX公司開發副總裁及共同創始人 Andrew Alexeev 寫的。
通過適當的系統優化,NGINX 的每個工作進程可以擴展來處理成千上萬的並發 HTTP 連接,並能臉不紅心不跳的承受峰值流量(大量湧入的新「遊戲」)。
更新配置和升級 NGINX
NGINX 的進程體系架構使用少量的工作進程,有助於有效的更新配置文件甚至 NGINX 程序本身。
更新 NGINX 配置文件是非常簡單、輕量、可靠的操作。典型的就是運行命令 nginx –s reload
,所做的就是檢查磁碟上的配置並發送 SIGHUP 信號給主進程。
當主進程接收到一個 SIGHUP 信號,它會做兩件事:
- 重載配置文件和分支出一組新的工作進程。這些新的工作進程立即開始接受連接和處理流量(使用新的配置設置)
- 通知舊的工作進程優雅的退出。工作進程停止接受新的連接。當前的 http 請求一旦完成,工作進程就徹底關閉這個連接(那就是,沒有殘存的「保持」連接)。一旦所有連接關閉,這個工作進程就退出。
這個重載過程能引發一個 CPU 和內存使用的小峰值,但是跟活動連接載入的資源相比它一般不易察覺。每秒鐘你可以多次重載配置(很多 NGINX 用戶都這麼做)。非常罕見的情況下,有很多世代的工作進程等待關閉連接時會發生問題,但即使是那樣也很快被解決了。
NGINX 的程序升級過程中拿到了高可用性聖杯 —— 你可以隨時更新這個軟體,不會丟失連接,停機,或者中斷服務。
程序升級過程類似於平滑重載配置的方法。一個新的 NGINX 主進程與原主進程並行運行,然後他們共享監聽套接字。兩個進程都是活動的,並且各自的工作進程處理流量。然後你可以通知舊的主進程和它的工作進程優雅的退出。
整個過程的詳細描述在 NGINX 管理。
結論
深入 NGINX 信息圖提供一個 NGINX 功能實現的高層面概覽,但在這簡單的解釋的背後是超過十年的創新和優化,使得 NGINX 在廣泛的硬體上提供儘可能最好的性能同時保持了現代 Web 應用程序所需要的安全性和可靠性。
如果你想閱讀更多關於NGINX的優化,查看這些優秀的資源:
- NGINX 安裝和性能調優 (webinar; Speaker Deck 上的講義)
- NGINX 性能調優
- 開源應用架構: NGINX 篇
- NGINX 1.9.1 中的套接字分片 (使用 SO_REUSEPORT 套接字選項)
via: http://nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale/
作者:Owen Garrett 譯者:wyangsun 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive