Nginx開發從入門到精通 nginx平台初探

2022-03-11 06:52:50 字數 4111 閱讀 7470

眾所周知,nginx效能高,而nginx的高效能與其架構是分不開的。那麼nginx究竟是怎麼樣的呢?這一節我們先來初識一下nginx框架吧。

nginx在啟動後,在unix系統中會以daemon的方式在後台執行,後台程序包含乙個master程序和多個worker程序。我們也可以手動地關掉後台模式,讓nginx在前台執行,並且通過配置讓nginx取消master程序,從而可以使nginx以單程序方式執行。很顯然,生產環境下 我們肯定不會這麼做,所以關閉後台模式,一般是用來除錯用的,在後面的章節裡面,我們會詳細地講解如何除錯nginx。所以,我們可以看到,nginx是以多程序的方式來工作的,當然nginx也是支援多執行緒的方式的,只是我們主流的方式還是多程序的方式,也是nginx的預設方式。nginx採用多程序的方式有諸多好處,所以我就主要講解nginx的多程序模式吧。

剛才講到,nginx在啟動後,會有乙個master程序和多個worker程序。master程序主要用來管理worker程序,包含:接收來自外界的訊號,向各worker程序傳送訊號,監控worker程序的執行狀態,當worker程序退出後(異常情況下),會自動重新啟動新的worker 程序。而基本的網路事件,則是放在worker程序中來處理了。多個worker程序之間是對等的,他們同等競爭來自客戶端的請求,各程序互相之間是獨立的。乙個請求,只可能在乙個worker程序中處理,乙個worker程序,不可能處理其它程序的請求。worker程序的個數是可以設定的,一般我們會設定與機器cpu核數一致,這裡面的原因與nginx的程序模型以及事件處理模型是分不開的。nginx的程序模型,可以由下圖來表示:

現在,我們知道了當我們在操作nginx的時候,nginx內部做了些什麼事情,那麼,worker程序又是如何處理請求的呢?我們前面有提到,worker程序之間是平等的,每個程序,處理請求的機會也是一樣的。當我們提供80埠的http服務時,乙個連線請求過來,每個程序都有可能處理這個連線,怎麼做到的呢?首先,每個worker程序都是從master程序fork過來,在master程序裡面,先建立好需要listen的 socket(listenfd)之後,然後再fork出多個worker程序。所有worker程序的listenfd會在新連線到來時變得可讀,為保證只有乙個程序處理該連線,所有worker程序在註冊listenfd讀事件前搶accept_mutex,搶到互斥鎖的那個程序註冊listenfd 讀事件,在讀事件裡呼叫accept接受該連線。當乙個worker程序在accept這個連線之後,就開始讀取請求,解析請求,處理請求,產生資料後, 再返回給客戶端,最後才斷開連線,這樣乙個完整的請求就是這樣的了。我們可以看到,乙個請求,完全由worker程序來處理,而且只在乙個worker程序中處理。

那麼,nginx採用這種程序模型有什麼好處呢?當然,好處肯定會很多了。首先,對於每個worker程序來說,獨立的程序,不需要加鎖,所以省掉了鎖帶來的開銷,同時在程式設計以及問題查詢時,也會方便很多。其次,採用獨立的程序,可以讓互相之間不會影響,乙個程序退出後,其它程序還在工作,服務不會中斷,master程序則很快啟動新的worker程序。當然,worker程序的異常退出,肯定是程式有bug了,異常退出,會導致當前worker上的所有請求失敗,不過不會影響到所有請求,所以降低了風險。當然,好處還有很多,大家可以慢慢體會。

上面講了很多關於nginx的程序模型,接下來,我們來看看nginx是如何處理事件的。

有人可能要問了,nginx採用多worker的方式來處理請求,每個worker裡面只有乙個主線程,那能夠處理的併發數很有限啊,多少個 worker就能處理多少個併發,何來高併發呢?非也,這就是nginx的高明之處,nginx採用了非同步非阻塞的方式來處理請求,也就是說,nginx 是可以同時處理成千上萬個請求的。想想apache的常用工作方式(apache也有非同步非阻塞版本,但因其與自帶某些模組衝突,所以不常用),每個請求會獨佔乙個工作執行緒,當併發數上到幾千時,就同時有幾千的執行緒在處理請求了。這對作業系統來說,是個不小的挑戰,執行緒帶來的記憶體占用非常大,執行緒的上下文切換帶來的cpu開銷很大,自然效能就上不去了,而這些開銷完全是沒有意義的。

為什麼nginx可以採用非同步非阻塞的方式來處理呢,或者非同步非阻塞到底是怎麼回事呢?我們先回到原點,看看乙個請求的完整過程。首先,請求過來, 要建立連線,然後再接收資料,接收資料後,再傳送資料。具體到系統底層,就是讀寫事件,而當讀寫事件沒有準備好時,必然不可操作,如果不用非阻塞的方式來呼叫,那就得阻塞呼叫了,事件沒有準備好,那就只能等了,等事件準備好了,你再繼續吧。阻塞呼叫會進入核心等待,cpu就會讓出去給別人用了,對單執行緒的worker來說,顯然不合適,當網路事件越多時,大家都在等待呢,cpu空閒下來沒人用,cpu利用率自然上不去了,更別談高併發了。好吧,你說加程序數,這跟apache的執行緒模型有什麼區別,注意,別增加無謂的上下文切換。所以,在nginx裡面,最忌諱阻塞的系統呼叫了。不要阻塞,那就非阻塞。 非阻塞就是,事件沒有準備好,馬上返回eagain,告訴你,事件還沒準備好呢,你慌什麼,過會再來吧。好吧,你過一會,再來檢查一下事件,直到事件準備 好了為止,在這期間,你就可以先去做其它事情,然後再來看看事件好了沒。雖然不阻塞了,但你得不時地過來檢查一下事件的狀態,你可以做更多的事情了,但帶來的開銷也是不小的。所以,才會有了非同步非阻塞的事件處理機制,具體到系統呼叫就是像select/poll/epoll/kqueue這樣的系統呼叫。 它們提供了一種機制,讓你可以同時監控多個事件,呼叫他們是阻塞的,但可以設定超時時間,在超時時間之內,如果有事件準備好了,就返回。這種機制正好解決 了我們上面的兩個問題,拿epoll為例(在後面的例子中,我們多以epoll為例子,以代表這一類函式),當事件沒準備好時,放到epoll裡面,事件 準備好了,我們就去讀寫,當讀寫返回eagain時,我們將它再次加入到epoll裡面。這樣,只要有事件準備好了,我們就去處理它,只有當所有事件都沒 準備好時,才在epoll裡面等著。這樣,我們就可以併發處理大量的併發了,當然,這裡的併發請求,是指未處理完的請求,執行緒只有乙個,所以同時能處理的 請求當然只有乙個了,只是在請求間進行不斷地切換而已,切換也是因為非同步事件未準備好,而主動讓出的。這裡的切換是沒有任何代價,你可以理解為迴圈處理多 個準備好的事件,事實上就是這樣的。與多執行緒相比,這種事件處理方式是有很大的優勢的,不需要建立執行緒,每個請求占用的記憶體也很少,沒有上下文切換,事件 處理非常的輕量級。併發數再多也不會導致無謂的資源浪費(上下文切換)。更多的併發數,只是會占用更多的記憶體而已。 我之前有對連線數進行過測試,在24g記憶體的機器上,處理的併發請求數達到過200萬。現在的網路伺服器基本都採用這種方式,這也是nginx效能高效的 主要原因。

我們之前說過,推薦設定worker的個數為cpu的核數,在這裡就很容易理解了,更多的worker數,只會導致程序來競爭cpu資源了,從而帶來不必要的上下文切換。而且,nginx為了更好的利用多核特性,提供了cpu親緣性的繫結選項,我們可以將某乙個程序繫結在某乙個核上,這樣就不會因為程序的切換帶來cache的失效。像這種小的優化在nginx中非常常見,同時也說明了nginx作者的苦心孤詣。比如,nginx在做4個位元組的字串比較時,會將4個字元轉換成乙個int型,再作比較,以減少cpu的指令數等等。

現在,知道了nginx為什麼會選擇這樣的程序模型與事件模型了。對於乙個基本的web伺服器來說,事件通常有三種型別,網路事件、訊號、定時器。從上面的講解中知道,網路事件通過非同步非阻塞可以很好的解決掉。如何處理訊號與定時器?

首先,訊號的處理。對nginx來說,有一些特定的訊號,代表著特定的意義。訊號會中斷掉程式當前的執行,在改變狀態後,繼續執行。如果是系統調 用,則可能會導致系統呼叫的失敗,需要重入。關於訊號的處理,大家可以學習一些專業書籍,這裡不多說。對於nginx來說,如果nginx正在等待事件 (epoll_wait時),如果程式收到訊號,在訊號處理函式處理完後,epoll_wait會返回錯誤,然後程式可再次進入epoll_wait調 用。

另外,再來看看定時器。由於epoll_wait等函式在呼叫的時候是可以設定乙個超時時間的,所以nginx借助這個超時時間來實現定時器。 nginx裡面的定時器事件是放在一顆維護定時器的紅黑樹裡面,每次在進入epoll_wait前,先從該紅黑樹裡面拿到所有定時器事件的最小時間,在計 算出epoll_wait的超時時間後進入epoll_wait。所以,當沒有事件產生,也沒有中斷訊號時,epoll_wait會超時,也就是說,定時 器事件到了。這時,nginx會檢查所有的超時事件,將他們的狀態設定為超時,然後再去處理網路事件。由此可以看出,當我們寫nginx**時,在處理網 絡事件的**函式時,通常做的第乙個事情就是判斷超時,然後再去處理網路事件。

我們可以用一段偽**來總結一下nginx的事件處理模型:

Nginx開發從入門到精通

更新歷史 日期描述 2012 03 01 建立目錄大綱 2012 03 28 增加了樣章 2012 05 25 更新樣章 2012 06 08 增加第5章 2012 06 11 增加第4章 2012 06 26 增加第6章 event module 2012 06 27 更新第5章部分內容 2012...

Nginx開發從入門到精通

nginx開發從入門到精通 nginx平台初探 100 基本資料結構 99 nginx的配置系統 100 nginx的模組化體系結構 模組概述 nginx的請求處理 handler模組 100 handler模組的基本結構 handler模組的掛載 handler的編寫步驟 示例 hello han...

Nginx從入門到精通

1 nginx配置檔案載入機制 採用nginx s reload命令載入nginx的配置檔案,master程序讀取配置檔案,建立新的worker程序,向老的worker程序傳送shutdown命令。老的worker程序不再接受新的請求,待老的請求處理完成後,就會停掉。2 location匹配機制 當...