畢業前,畢設完成後,我閒極無聊接觸了一下socket編程,用C++的Qt框架寫了玩具一樣的TCP和UDP通信客戶端。跟直系學長電話聊天時被建議深挖socket,嘗試走走後端或架構師路線。問該怎麼深挖,答研究源碼,要學習socket相關知識,研究伺服器源碼是最適合不過的了。至於選擇哪個伺服器,經過考慮調查,發現比起比較沉重龐大的apache,nginx更加小巧,也非常優秀。於是在開始正式吃源碼之前,我先開始了一番自我科普工作。
1、進程模型
首先,預設的,與其他伺服器一樣,Unix下的nginx也以持續守護機?雖然nginx也可以以調試為目的關掉後台模式,使用前台模式,甚至可以透過配置取消master進程(後面會詳細解釋),使nginx以單進程的形式工作。但這些與nginx引以為傲的架構關係不大,這裡按下不表。儘管nginx也支援多執行緒方式,我們還是著重來了解下其預設的多進程方式。
nginx在啟動後會建立一個master進程(主進程)和若干個worker進程(從進程)。 master進程主要負責管理worker進程,具體來說就是接收來自管理員的信號並轉發給對應worker進程;監控worker進程異常終止時重新建立並啟動worker進程。而worker進程負責處理基本的網路事件。 worker進程之間優先級對等、相互獨立,公平競爭來自客戶端的請求,每個請求只由一個worker進程處理。 nginx進程模型示意圖如圖1所示。 圖1 nginx
進程模型示意圖
worker進程 的事件處理模型有關。後面會繼續介紹
nginx的事件處理模型。 2、訊號與請求 nginx與外界互動無非透過兩種介面介面:來自管理員的訊號與來自客戶端的請求。下面我們舉例說明nginx是如何處理訊號與請求的。
管理員要控制nginx
需要與master進程通信,請向master
進程發送指令訊號即可。例如,nginx在0.8版本之前使用kill -HUP [pid]指令來重啟使用這個指令重啟nginx將實現從容地重啟過程,期間服務不中斷。 master進程在接到HUP指令後首先會重新加載配置文件,然後啟動新的worker進程,向舊的程序worker進程這時新的worker進程開始接收網路請求,舊的worker進程停止接收新的請求,等到處理完當前請求後,舊的worker進程就退出銷毀了。在0.8版本以後,nginx引入了一系列命令列參數以方便管理伺服器,例如./nginx -s reload和用來重啟和停止nginx。執行操作指令時,我們實際上啟動了一個新的nginx進程,這個進程在解析指令中的參數後,自行向master進程發送相應的訊號,達成與先前手動發送訊號相同的效果。 3、請求與事件
伺服器最常處理的就是80埠http協定的請求了, 以此為例說明一下nginx處理請求的過程。首先,每一個worker進程都是從master進程fork(分叉)而成的,master(分叉)而成的,master(分叉)。 (套接字,即IP位址+埠號)和對應的listenfd(監聽檔案描述子或句柄)。我們知道socket通信中每個進程都要分配一個端口號,worker進程的socket分配工作就由master進程來完成。所有worker進程的listenfd在新的連接到來時變得可讀,為確保只有一個worker進程處理連接,進程各行。 讀事件前先要搶accept_mutex(接受連接互斥鎖),一個worker進程搶注連線成功後,開始讀取請求、解析請求、處理請求並回饋資料給客戶端。 4、進程模型分析 nginx使用但不僅僅使用多進程請求處理模型(
PPC
),每個進程workerPPC),每個進程workerPPC)需要上鎖,進程間互不影響能並行處理請求。一個請求處理失敗導致一個worker進程異常退出,不會使服務中斷,而是由master進程立刻重新啟動一個新的worker進程,降低了伺服器面臨的整體風險,使服務更加穩定。但是相較於多執行緒模型(TPC),系統開銷略大,效率略低,需要藉助別的手段來改進。
5、nginx的高並發機制——非同步非阻塞事件機制
IIS的事件處理機制是多執行緒,每個要求獨佔一個工作執行緒。由於多線程比較佔用內存,線程間的上下文切換(反复的對寄存器組進行保護現場和恢復現場的操作)帶來的CPU開銷也很大,多線程機制的伺服器在面臨數千並發量時,會對系統造成很大的壓力,高並發性能並不算理想,當然如果硬體夠出色,能夠提供足夠的系統資源,系統壓力也就不再是問題了。
我們深入到系統層面討論多進程與多線程,阻塞式機制與非阻塞式機制的區別。熟悉作業系統的同學應該了解,多執行緒的出現是為了在資源充足的情況下更充分的調度使用CPU,尤其對提高多核心CPU的利用率十分有益。但是執行緒是系統任務的最小單位,而行程卻是系統分配資源的最小單位,這意味著多執行緒將面臨一個很大的問題:當執行緒數增多,資源需求變大,這些執行緒的母程序可能無法立即一口氣申請到足夠所有執行緒使用的資源,而當系統手上沒有足夠的資源滿足一個進程時,它會選擇讓整個進程都等著。這時即使系統資源足夠一部分執行緒正常運作,母進程也無法申請這些資源,導致所有執行緒一起等待。直白的說,使用多線程,進程內的線程間可以靈活的進行調度(雖然增加了線程死鎖的風險和線程切換的開銷),但是卻無法保證母進程在逐漸龐大沉重時還能夠在系統中得到合理的調度。由此可見,多執行緒確實可以提高CPU利用率,但是並不是解決伺服器高並發請求問題的理想解決方案,且不說在高並發狀態下CPU的高利用率也無法維持。以上是IIS的多執行緒同步阻塞式事件機制。
nginx的多進程機制保證了每個請求獨立申請系統資源,一旦滿足條件,每個請求都可以立即被處理,即非同步非阻塞處理。但是創建進程需要的資源開銷會比執行緒多一些,為了節省進程數,nginx使用了一些進程調度演算法,使I/O事件處理不僅僅靠多進程機制,而是異步非阻塞的多進程機制。下面我們就來具體的引入nginx的非同步非阻塞事件處理機制。
6、epoll🎜Linux下,言高並發的高效能網路必epoll,nginx也正是使用了epoll模型作為網路事件的處理機制。我們先來看看epoll是怎麼來的。
最早的調度方案是異步忙輪詢方式,即持續的輪詢I/O事件也就是遍歷socket集合的訪問狀態,顯然服務器空閒時這一方案造成了無謂的CPU開銷。
後來,select和poll作為調度進程和提高CPU🜎 ,一個是「投票」,它們的本質相同,都是輪詢socket集合並處理請求,與之前不同的地方在於,它們能夠監視事件,空閒時輪詢線程將被阻塞,而一個或多個I/O事件到來時則被喚醒,擺脫了“忙輪詢”的忙輪詢”的忙輪詢” 忙」,成為非同步輪詢方式。 select/poll模型輪詢的是整個FD(檔案描述子)集合即socket集合,網路事件處理效率隨著並發請求數線性降低,所以使用一個宏來限制集合,網路事件處理效率隨著並發請求數線性降低,所以使用一個宏來限制最大並發連線數。同時,select/poll模型的核心空間與使用者空間通訊方式為記憶體複製,帶來較高的開銷。以上缺點催生了新模型的產生。 epoll可以認為是event poll
的簡寫,是Linuxoll 下多路復用I/O介面select/poll的增強版本,它能顯著提高程式在大量並發連接中只有少量活躍的情況下的系統CPU利用率。 首先,epoll沒有最大並發連接數的限制,上限是可以打開的最大檔案數,這與硬體記憶體大小有關,1GB的機器上大約是10w的機器上大約是10w epoll最顯著的優點,它只對「活躍」的socket寫的socket才被放入ready隊列,準備進入worker進程被處理,這在實際生產環境中節省了大量輪詢開銷,極大的提高了事件處理效率;最後,epoll使用共享記憶體(MMAP)的方式實現內核空間與用戶空間的通信,省掉了記憶體複製的開銷。額外的,nginx中使用epoll的ET(邊緣觸發)工作模式即快速工作模式。 ET模式下,只支援非阻塞socket,FD就緒即由內核透過FDep狀態時也會發送通知,但如果一直沒有I/O操作導致FD變為未就緒狀態將不再發送通知。總的來說,nginx在Linux下是基於事件,利用epoll處理網路事件的。 版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 以上就介紹了nginx核心架構概述,包含了方面的內容,希望對PHP教學有興趣的朋友有幫助。