Nginx架構分析

Nginx強勁的高性能表現來自其合理的軟件設計。傳統的web服務器和應用服務器架構設計上採用多進程或線程作爲其處理業務的基本單位,而Nginx更多的使用了事件驅動的架構。正是這種架構使得Nginx可以輕鬆支持數十萬的併發鏈接。【譯註:Nginx相比其他的web服務器使用了更少的進程,將IO事件集中在固定的進程內處理,減少了很多系統開銷,可以從下文理解到。】

The Inside NGINX infographic 較爲清晰的講訴了Nginx如何在一個進程內處理併發鏈接,下面我們深入看一下細節。

Nginx進程模型

這裏寫圖片描述
在講設計實現之前,有必要先看一下Ngxin如何在linux之上運行的。Nginx啓動會創建一個主進程(主管進程,負責讀取配置、綁定端口、管理其他子進程)和一些worker進程和輔助進程。
這裏寫圖片描述
這個示例運行在4核的server上,Nginx主進程創建4個worker進程和2個cahce輔助進程。

爲什麼架構很重要?

Unix應用程序的基本要素是進程或者線程。(Linux OS調度不區分進程還是線程,二者的最大區別在於它們對於memory的共享程度。)進程或線程是一個自包含的可以獨立運行的任務,OS可以調度它到某個CPU核上執行。有很多複雜的應用程序運行在多進程或線程模式下是基於以下兩點考慮:

可以使用更多CPU資源。【譯註:還有memory、IO等其他資源】
可以輕鬆做到並行處理(比如,同時處理多個鏈接)。
進程和線程都會消耗資源,需要佔用memory和其他OS資源,並且在運行時還有context switch的系統開銷。一般的server可以負擔幾百數量級的進程或者線程,當進程或線程數量繼續上升到更高的數量級,memory消耗和IO阻塞引起的系統負荷會很高,使得應用程序運行比較低效。

在設計網絡程序時,開發者會很自然的設計成每個進程或線程處理一個網絡連接。這種架構比較簡單容易實現,但是比較難以擴展,尤其是當網絡連接增長到上千以後。

Nginx怎樣工作的?

Nginx可配置數量的進程,推薦配置數量和CPU的核數量相當:

主進程讀配置,綁定端口,然後啓動一定數量的子進程。
cache loader子進程在啓動的時候運行,負責把硬盤上的數據搬進內存,然後就退出了。因爲它是一次性的任務,系統開銷很小。
cahce manager子進程啓動後監控維護cache區。
worker子進程是真正處理業務的進程,負責處理網絡連接,讀寫硬盤,跟上游server交互等等。
Nginx推薦配置worker的數量跟CPU核數量線性關係,每個CPU核運行一個worker進程。可以通過配置 worker_processes auto來使用該推薦設置。

當Nginx server處理業務時,worker進程們是最繁忙的,每個worker通過非阻塞的IO複用方式處理很多連接,儘量減少不必要的上下文切換。
每個worker進程都是單線程的進程,接收連接上的request並處理後迴應。進程間可以通過共享內存的方式進行進程間通信。

Nginx worker進程

這裏寫圖片描述
每個Nginx worker進程由主進程讀取配置創建,通過accept_mutex競爭獲得要listen的socket並加入自己的IO監聽列表中。
每來一個新的連接都會觸發新的事件,這些事件送給worker內的狀態機來處理。(Nginx支持各種類型的狀態機,如http/tcp/SMTP/IMAP/POP3等)。大部分的web server邏輯上都有這樣的狀態機,只是實現方式不一樣。
這裏寫圖片描述

狀態機調度

我們可以想象類比狀態機是象棋遊戲的規則。每個HTTP transaction(譯註:一組的Http請求,可以對應成某個socket上發生的所有http請求)就是一個象棋遊戲。對弈的一方是web server,可以類比爲象棋大師。另一方爲client,類比爲象棋愛好者。
遊戲的規則可以很複雜,比如web server需要跟其他application溝通協作完成業務處理,第三方的nginx模塊甚至可以擴展規則。

阻塞式狀態機

大多數的web服務器和應用程序使用每個連接對應一個進程或線程的模式來玩象棋遊戲。每個進程或線程給一個client完成對弈直到遊戲結束。在整個過程中,進程大部分時間都是處在阻塞狀態–等待client完成下一步走棋。
這裏寫圖片描述
web服務器主進程在服務端口上監聽新的連接(客戶端發起的新遊戲的請求)。
有新的遊戲請求時,主進程創建子進程負責完成跟客戶端的對弈。主進程繼續監聽服務端口。
當遊戲結束時,子進程要麼等待client開始新遊戲(通過keepalive機制保活一段時間連接)要麼退出(keepalive超時後)。
這種模型每玩一局server都要創建一個對應的進程來完成對弈。這種架構簡單並且容易容易擴展新功能,但有些大炮打蚊子,殺雞用牛刀的感覺。進程是個重器,系統開銷比較大,而解決的問題是個輕量級的問題。容易編程實現但是浪費比較大。

Nginx纔是真正的併發大師

你可能聽說過一人同時對戰多人的象棋大賽
這裏寫圖片描述
這就是Nginx worker進程的工作方式。每個worker進程(一般每個CPU核有一個worker進程)都是一個象棋大師,可以同時對弈數十萬對手。
這裏寫圖片描述
worker進程等待listen和connection sockets的事件。(譯註:listen socket就是server用來監聽新建連接的socket,connection socket是accept系統調用返回的新建socket,詳細可參加accept的手冊)
事件發生後,worker進程來處理這些事件:
listen socket的事件表示有新的客戶端要開始新的遊戲。worker通過accept()創建新的connection socket,並加入監聽列表。
connection socket的事件表示客戶端走了一步棋,worker進程可以做下一步應對。
worker進程從不會在網絡IO上阻塞,當它應對完客戶端的走棋走出自己的一步後,可以馬上應對下一個客戶端的走棋或接收新的連接請求。

爲什麼這樣做比阻塞式的多進程架構更快?

Nginx的worker進程很容易擴展支持數十萬併發連接。每個新接入的連接只需要創建新的socket消耗少量的內存,每個連接的系統開銷相對要比進程開銷小很多。另外通過Nginx worker進程綁定CPU技術可以進一步減少上下文切換和cache失效等系統開銷。

而阻塞式每個進程服務一個連接的方式,每個連接都會消耗很多資源,而且進程切換比較頻繁導致系統開銷比較大。

更詳細的解釋,可以參考這篇文章–Nginx架構,作者是Ngxin的VP和共同創始人,Andrew Alexeev.

更新配置和升級Nginx

Nginx的這種少量進程的架構使得更新配置和升級Nginx版本很容易。
這裏寫圖片描述
更新Ngxin配置是一件非常容易事情而且非常可靠。很簡單的nginx -s reload就搞定了。運行這個命令實際上是給Nginx主進程發送了一個SIGHUP的信號,主進程收到該信號後做了兩件事情:

重新加載配置並且根據新的配置創建一組新的worker進程,這些新的進程可以馬上開始幹活。
通知老的worker進程優雅地退出。
重新裝載的過程會引起短暫的CPU和內存的使用高峯,但這種影響總體來說比較微小,你甚至可以每秒多次做這個操作。

Nginx程序的升級就更加漂亮了,根本不會影響正在處理的連接,輕輕鬆鬆升級完成用戶根本沒有感覺。
這裏寫圖片描述
Ngxin程序升級跟更新配置相似。啓動新的Nginx主進程,它會跟舊的主進程共享listen sockets。新的進程起來後,你可以發送信號給舊的進程退出。詳細過程可以參看Controlling Nginx(http://nginx.org/en/docs/control.html).

總結
The Inside NGINX infographic描述了Nginx的整體功能,其實它概括性描述的背後是Nginx開發人員十幾年的創新和優化。如果你想了解更多,可以參看這些材料:
Installing and Tuning Nginx for Performance(https://www.nginx.com/resources/webinars/installing-tuning-nginx/
Tuning Nginx for Performance(https://www.nginx.com/blog/tuning-nginx/)
The Architecture of Open Source Applications – NGINX(http://www.aosabook.org/en/nginx.html)
Socket Sharding in NGINX Release 1.9.1 (using the SO_REUSEPORT socket option)(https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/
原文地址:https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale/

發佈了43 篇原創文章 · 獲贊 14 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章