Nginx實現內參

Nginx在web開發者眼中就是高併發高性能的代名詞,其基於事件的架構也被衆多開發者效仿。我從Nginx的網站找到一篇技術文章將Nginx是怎樣實現的,文章是Nginx的產品老大Owen
Garrett在加入公司22個月時寫的,深刻簡出。這篇博客後面的內容儘可能保證是對原文的翻譯,若是有我的理解或者延伸閱讀我會加標「譯註」。原文地址Inside NGINX: How We Designed for Performance & Scalehtml

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

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

Nginx進程模型

clipboard.png

在講設計實現以前,有必要先看一下Ngxin如何在linux之上運行的。Nginx啓動會建立一個主進程(主管進程,負責讀取配置、綁定端口、管理其餘子進程)和一些worker進程和輔助進程。web

# service nginx restart
# 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

這個示例運行在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的核數量至關:app

  • 主進程讀配置,綁定端口,而後啓動必定數量的子進程。

  • cache loader子進程在啓動的時候運行,負責把硬盤上的數據搬進內存,而後就退出了。由於它是一次性的任務,系統開銷很小。

  • cahce manager子進程啓動後監控維護cache區。

  • worker子進程是真正處理業務的進程,負責處理網絡鏈接,讀寫硬盤,跟上游server交互等等。
    Nginx推薦配置worker的數量跟CPU核數量線性關係,每一個CPU核運行一個worker進程。能夠經過配置 worker_processes auto來使用該推薦設置。

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

Nginx worker進程

clipboard.png

每一個Nginx worker進程由主進程讀取配置建立,經過accept_mutex競爭得到要listen的socket並加入本身的IO監聽列表中。
每來一個新的鏈接都會觸發新的事件,這些事件送給worker內的狀態機來處理。(Nginx支持各類類型的狀態機,如http/tcp/SMTP/IMAP/POP3等)。大部分的web server邏輯上都有這樣的狀態機,只是實現方式不同。
clipboard.png

狀態機調度

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

阻塞式狀態機

大多數的web服務器和應用程序使用每一個鏈接對應一個進程或線程的模式來玩象棋遊戲。每一個進程或線程給一個client完成對弈直到遊戲結束。在整個過程當中,進程大部分時間都是處在阻塞狀態--等待client完成下一步走棋。

clipboard.png

  1. web服務器主進程在服務端口上監聽新的鏈接(客戶端發起的新遊戲的請求)。

  2. 有新的遊戲請求時,主進程建立子進程負責完成跟客戶端的對弈。主進程繼續監聽服務端口。

  3. 當遊戲結束時,子進程要麼等待client開始新遊戲(經過keepalive機制保活一段時間鏈接)要麼退出(keepalive超時後)。
    這種模型每玩一局server都要建立一個對應的進程來完成對弈。這種架構簡單而且容易容易擴展新功能,但有些大炮打蚊子,殺雞用牛刀的感受。進程是個重器,系統開銷比較大,而解決的問題是個輕量級的問題。容易編程實現可是浪費比較大。

Nginx纔是真正的併發大師

你可能據說過一人同時對戰多人的象棋大賽
clipboard.png

這就是Nginx worker進程的工做方式。每一個worker進程(通常每一個CPU核有一個worker進程)都是一個象棋大師,能夠同時對弈數十萬對手。

clipboard.png

  1. worker進程等待listen和connection sockets的事件。(譯註:listen socket就是server用來監聽新建鏈接的socket,connection socket是accept系統調用返回的新建socket,詳細可參加accept的手冊)

  2. 事件發生後,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版本很容易。

clipboard.png

更新Ngxin配置是一件很是容易事情並且很是可靠。很簡單的nginx -s reload就搞定了。運行這個命令其實是給Nginx主進程發送了一個SIGHUP的信號,主進程收到該信號後作了兩件事情:

  1. 從新加載配置而且根據新的配置建立一組新的worker進程,這些新的進程能夠立刻開始幹活。

  2. 通知老的worker進程優雅地退出。
    從新裝載的過程會引發短暫的CPU和內存的使用高峯,但這種影響整體來講比較微小,你甚至能夠每秒屢次作這個操做。

Nginx程序的升級就更加漂亮了,根本不會影響正在處理的鏈接,輕輕鬆鬆升級完成用戶根本沒有感受。

clipboard.png

Ngxin程序升級跟更新配置類似。啓動新的Nginx主進程,它會跟舊的主進程共享listen sockets。新的進程起來後,你能夠發送信號給舊的進程退出。詳細過程能夠參看Controlling Nginx.

總結

The Inside NGINX infographic描述了Nginx的總體功能,其實它歸納性描述的背後是Nginx開發人員十幾年的創新和優化。若是你想了解更多,能夠參看這些材料:
Installing and Tuning Nginx for Performance
Tuning Nginx for Performance
The Architecture of Open Source Applications – NGINX
Socket Sharding in NGINX Release 1.9.1 (using the SO_REUSEPORT socket option)

相關文章
相關標籤/搜索