深入 Nginx:我們是如何爲性能和規模做設計的

NGINX 在網絡應用中表現超羣,在於其獨特的設計。許多網絡或應用服務器大都是基於線程或者進程的簡單框架,NGINX突出的地方就在於其成熟的事件驅動框架,它能應對現代硬件上成千上萬的併發連接。

NGINX 內部信息圖從進程框架的頂層開始,向下逐步揭示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

在這個四核服務器中,主線程創建了四個工作進程和一組緩存輔助進程(cache helper processes),後者用來管理硬盤緩存。

爲什麼框架如此重要?

任何Unix應用的基礎是線程或者進程-對於Linux操作系統,線程和進程幾乎相同;最大的區別在於線程間是內存共享的。一個線程或者進程是一套指令集(self-contained set of instructions ),操作系統調度這些指令在單個CPU內核上運行。許多複雜應用並行地運行在多個線程或者進程,原因有二:

  • 應用可以同時使用計算機的多個CPU核
  • 線程和進程易於並行操作,比如同時處理多個連接

進程和線程消耗資源,比如對內存以及其它操作系統資源的佔用、內核切換(wapped on and off the cores)(本操作叫做一次上下文切換(context switch))。如今的服務器需要同時處理成千個小的、活躍線程或者進程,一旦內存耗盡、或者過高的讀寫負載,這些都會導致大規模的上下文切換,性能會嚴重退化。

通常的設計思路是,網絡應用爲每個連接分派一個線程或者進程。這類框架簡單易於實現,不過在同時應對成千上萬個連接時難以擴展。

NGINX是如何運作的呢?

NGINX利用一個預測進程模型調度可用的硬件資源:

  • 主進程處理配置文件讀取、端口綁定等特權操作,以及創建一小組子進程(接下來三種類型的進程)
  • 啓動時緩存加載器進程加載硬盤中緩存到內存中,接着退出。對它的調度是保守的,所以資源開銷較低
  • 緩存管理進程定時運行,清理來自硬盤緩存的實體到指定的大小
  • 工作進程負責所有的工作,處理網絡連接、硬盤讀寫操作、以及上游服務器通信

NGINX推薦的配置是,一個工作進程對應一個CPU內核,確保硬件資源的有效利用,在配置文件中設置worker_processes auto:

worker_processes auto;

一旦NGINX服務起來,僅有工作進程在忙,每個工作進程採用非阻塞地方式處理多個連接,降低上下文切換的次數。

每個工作進程都是單線程且獨立運行,負責獲取新連接並進行處理。進程之間通過共享內存進行通信,諸如緩存數據,會話持續化數據(ession persistence data),以及其他共享資源。NGINX1.7.11及以後的版本,有一個可選的線程池,工作進程將阻塞操作丟給它們。更多細節,參看《Nginx 引入線程池,提升 9 倍性能》。對於NGINX Plus用戶,這些新特性會在今年的發佈版7中出現。

NGINX內部工作進程

每個NGINX工作進程由配置文件對其進行初始化,主進程爲其提供一組監聽socket。

工作進程起始於socket監聽事件(accept_mutex 和 kernel socket sharding),事件由新的連接進行初始化,接着這些連接被派發給某個狀態機—HTTP狀態機是其中最常用的一種,不過NGINX也實現了基於流的狀態機、基於通信協議的狀態機(SMTP, IMAP, and POP3)。

狀態機是一組重要的指令集,它會告訴NGINX怎樣處理每個請求。許多網絡服務器擁有NGINX的狀態機一樣的功能—區別就在於它們的實現不同。

調度狀態機

狀態機就像下象棋,單個HTTP事務如同一盤棋。棋盤的一端是網絡服務器—就像大師級棋手非常快地做出決定,另一端爲遠程客戶端—網絡瀏覽器通過相對較慢的網絡訪問某個站點或應用。

不過遊戲規則可能非常複雜,比如網絡服務可能需要和第三方、或者某個認證服務器通信,甚至服務器中的第三方模塊來擴展遊戲規則。

阻塞狀態機

回到前面的描述,進程或者線程作爲一套指令集,操作系統調度其運行在某個CPU內核上。大多數網絡服務器和網絡應用按照一個進程處理一個連接,或者一個線程處理一個連接的模型來玩象棋遊戲;每個包含指令的進程或者線程參與遊戲的整個過程。在這期間,運行在服務器上進程大多數時間被阻塞掉了,即等待某個客戶端去完成下一步棋。

  1. 網絡服務器進程監聽socket上的新連接,此遊戲新連接由客戶端發起。
  2. 一旦獲得新遊戲,進入遊戲環節,每一次移動都需等待客戶端響應,進程就被阻塞了。
  3. 一旦遊戲結束,網絡服務器進程就會查看客戶端是否想再來一局(對應某個存活的連接)。一旦連接關閉(客戶端離開或者超時),網絡服務器進程就會返回監聽新的遊戲。

記住每一個活躍的HTTP連接即每一局象棋遊戲,需要象棋大師一般的特定進程或者線程參與其中。這個架構簡單易於擴展第三方模型即新的規則。然而,這裏存在一個極不平衡的邏輯,對於相關輕量級的HTTP連接,由單個文件描述符和少量的內存表示,此連接會映射到某個線程或進程上,而線程或者進程是一個重量級的操作系統對象。儘管編程時很方便,但浪費卻是巨大的。

NGINX是一個真正的大師

或許你聽說過同時展示遊戲,一個象棋大師同時對陣十二個棋手。 NGINX工作進程也是這麼玩”象棋”的,每個工作進程-一個CPU內核上的工作者-即是一個可以同時應對成千上萬遊戲的大師。

  1. 工作進程從已連接並開始監聽的套接字(socket)那裏獲取事件;
  2. 一旦socket接收到事件,工作進程會立即處理此事件:
  • socket上的某個監聽事件即客戶端開啓一個新的象棋遊戲,而工作進程創建一個新的socket連接。
  • socket連接上的某個事件即客戶端走了一步棋,工作線程做出了恰當地響應。

工作進程從來不會阻塞在網絡傳輸上等待它的對手(客戶端)回覆應答。每走完一步棋後,工作進程會迅速處理其它等待的象棋遊戲,或者歡迎新的遊戲玩家進入。

爲何比阻塞、多進程框架快呢?

NGINX良好的擴展性在於其支持一個工作線程處理成千上萬個連接。每個新連接創建文件描述符,僅消耗工作進程很少一部分額外內存,額外的開銷很小。進程能夠一直綁定CPU(pinned to CPUs),這樣上下文切換相對沒有那麼頻繁,只有沒工作時纔會發生。

注:cpu綁定是指綁定一個或者多個進程到一個或者多個處理器上.

使用阻塞方式,即一個連接對應一個進程,每個連接需要大量的額外資源以及開銷,上下文切換非常頻繁。

只要恰當的系統調優,NGINX每個工作進程可以處理成千上萬個併發HTTP連接,毫無差錯地應對網絡高峯,即同時可以玩更多的象棋遊戲。

更新配置文件升級NGINX

進程框架擁有少量工作進程,有利配置文件甚至二進制文件更新。

更新NGINX配置是一個簡單、輕量級的可靠操作。即只要運行nginx -s reload命令,就會檢查磁盤上的配置文件,並給主進程發送一個SIGHUB信號。

一旦主進程接受到一個SIGHUB,它會做兩件事:

  1. 重載配置文件、創建一組新的工作進程,新創建的工作進程立即接受連接、處理網絡通信( 採用新的配置環境)。
  2. 通知舊的工作進程優雅地推出,這些工作進程停止接受新連接。一旦當前處理的HTTP請求結束,工作進程會關閉連接。一旦所有連接關閉,工作進程就會退出。

重載進程會引起一個小的CPU和內存高峯,不過從活躍連接處加載的資源相比,開銷微乎其微。每一秒可以多次重載配置文件。產生諸多等待連接關閉的NGINX工作進程一般很少出問題,不過就算是有問題也可以迅速解決。

NGINX二進文件升級獲得極佳的高可用性-你可以在線升級文件,而且不會丟失任何連接、服務也不會停機或中斷。

注: on the fly 程序在運行時,工作就可以完成。

二進制文件升級進程方式類似優雅的配置文件重載;新的NGINX主進程和原有的主進程並行,分享監聽socket。兩個進程都處於活躍狀態,處理它們各自的網絡通信。你可以通知原有的主進程以及它的工作進程優雅地退出。

最後結語

NGINX內部信息圖展示了NGINX的高標準功能全景圖,簡單解釋的背後是十多年來不斷創新優化,得益於此NGINX被廣泛應用於各種硬件平臺,並且取得了最優異的性能表現。即便是在現代,網絡應用需要對安全和可靠性作出維護,NGINX也表現不凡。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章