透過現象看本質——回頭再看Nginx(進程模型、異步非阻塞、源碼目錄結構)

透過現象看本質——回頭再看Nginx

Nginx的進程模型

​ 使用過nginx的朋友都知道nginx的性能很高,而其原因可能少有人知。首先,nginx的架構就奠定了其高性能的基礎。那麼就先來看看nginx的基礎架構吧,如下圖所示:(不能完全理清楚所有內容也沒關係,因爲本小節講述的主要內容是Nginx的進程模型)

透過現象看本質——回頭再看Nginx(進程模型、異步非阻塞、源碼目錄結構)

​ 本小節先來說說Nginx基礎架構中的進程模型:

透過現象看本質——回頭再看Nginx(進程模型、異步非阻塞、源碼目錄結構)

​ 所謂進程模型,即Nginx響應請求或服務時程序運行(機器執行指令集)的方式,一般在nginx服務啓動後,在Unix系統中會以daemon的方式在後臺運行,後臺進程包含一個master進程以及多個worker進程。

​ 而在我們進行調試時,可以手動關閉後臺模式以及設置nginx取消master進程,使其以單進程方式運行,但是生產環境中肯定不允許這樣上線服務,主流的運行方式還是默認使用多進程。

這裏插一下關於進程和程序的區別:

其實這兩者完全不是一個概念,程序只是一堆等待執行的代碼和部分待處理的數據,只有被加到內存中,由CPU處理執行代碼,纔可以發揮其作用,從而形成一個真正的“活的”動態的進程。所以二者的典型區別不言而喻,程序未被執行時是靜態的,只有運行時才變成動態的進程,並且進程有始有終(進程的“生老病死”)。

​ 言歸正傳,還是回來說nginx的多進程模式。nginx啓動後,會有如上圖所示的一個master進程和多個worker進程。

​ master進程:負責管理worker進程,接收、發送信號,監控worker進程狀態;操作控制Nginx只需要通過與master進程通信就可以了。

​ worker進程:負責處理基本網絡事件,每個worker進程都是平等且獨立的(記住獨立兩個字)。一般worker進程數會設置爲機器(服務器)的CPU核心數(原因下文會講到)。

Nginx響應連接的工作原理

​ 我們從nginx啓動時的工作流程以及啓動後所響應外部的操作來理解nginx處理一個連接的工作原理及過程。

啓動時:

​ Nginx啓動時,會先解析配置文件,獲取需要監聽的ip地址與端口號(涉及網絡編程以及TCP/IP理論),然後在master進程中,首先初始化好這個監控的socket,然後fork多個子進程,子進程競爭(例如互斥鎖機制)accept新的連接。此時,Nginx以及啓動好,等待客戶端來連接自己。

fork——屬於系統編程內容,原意爲叉子,可能老外使用的是叉子,而決定使用這個單詞來比喻fork的作用,fork函數的功能就是創建(有時候理解爲派生)出多個子進程

啓動後:

​ 客戶機發起連接請求,通過TCP三次握手與Nginx建立一個連接,此時競爭成功的一個worker進程獲取到這個建立好連接的socket,開始創建nginx對連接的封裝(其實說白了就是結構體封裝)。隨之執行讀寫事件(本文所述的事件理解爲網絡事件即可)函數、添加讀寫事件來與客戶端進行數據交互。最後,其中一方主動斷開連接(四次揮手),到此,一個連接也就壽終正寢了(該worker進程被宣告退休)。

Nginx重啓流程

​ 當我們執行命令kill -HUP pid時,master進程是如何響應的呢?

​ 首先,master接收到kill(不要簡單理解爲殺死,很多人都保持着這樣的理解,但是這樣不準確,在linux系統中,kill表示的是用於向進程發送信號的,可以使用kill -l查看可以攜帶的信號)的信號時,首先會重載配置文件,然後啓動新的worker進程,並且向舊的worker進程發送信號,提示他們做完當前事件之後就可以光榮退休了。新的worker進程啓動後,開始接收新的請求。這種方式就是直接給mater進程發送信號。

​ 而在新的版本中,可以使用其他方式例如:./nginx -s reload就是重啓服務,./nginx -s stop就是停止服務,執行./nginx -s reload時會啓動一個新的進程(nginx),該進程解析到reload的時候,就控制nginx重載配置文件,向master進程發送信號了,隨後就和上面舊版本的方式一樣的過程了。

以上就是nginx內部工作(內部究竟幹了啥活)了,但是此時考慮一個問題:worker進程如何處理請求的呢?

worker進程工作方式

​ 既然worker進程是由master進程fork出來的,並且每個worker進程都是對等的,那麼當一個請求過來時,任何一個worker進程都有可能處理它,這個時候怎麼辦呢?

​ 爲了保證只有一個進程處理該連接請求,所有的worker進程會進行競爭,觸發鎖機制,一般是互斥鎖,搶到鎖的進程獲得權利來處理這個請求(讀事件開始調用accept接受該連接),進行讀取、解析、處理請求,將結果(數據信息)返回給客戶端,最後斷開連接,這個請求完整走完它的人生。因此,一個請求完全是由也僅僅由一個worker進程處理。

Nginx爲什麼選擇多進程模型?

​ 這個問題其實不是特別容易說,沒有進程線程的理論基礎可能也無法理解原理,這邊給出其具備的”獨立“特徵所帶來的優勢吧。

​ ”獨立“的進程,意味着不需要加鎖,節省開銷,並且多個worker進程之間互不影響,某一個出現bug後,會有新的worker進程開始工作,從而降低風險,也不會中斷其他服務,同時也簡化了編程和檢查問題。

​ 那麼,問題又來了,使用多進程模型,每個worker進程中也只有一個主線程,如何可以處理高併發呢?

且看下節。

Nginx異步非阻塞的處理請求方式(簡單說說)

簡單理解阻塞與非阻塞

  • 阻塞就是線程在執行IO操作獲取數據時,這個IO可能會需要一定的時間才能等到數據返回,然後才能接着執行下面的命令。那麼,此時,這個線程的等待狀態(一般在nginx中稱作內核等待,而nginx中最忌諱阻塞的系統調用)我們就把它稱爲阻塞。沒有充分利用起cpu的資源。
  • 非阻塞還是這個線程在進行 IO操作時,無需等待數據的返回,可以接着往下執行代碼命令,會返回一個結果給你,你可以使用cpu資源做其他的事情。

舉個例子:阻塞就好比下課你去食堂吃飯,但去的時候人太多了,你就傻傻地在原地排隊等。

非阻塞就好比是你去食堂吃飯,人依舊很多,但是你可以先去上個洗手間,看會兒資訊,不影響你幹這些事情。充分利用時間,這個時間就好比是CPU的使用率,非阻塞的存在可以避免浪費CPU資源。

爲什麼需要異步方式?

上面說的非阻塞,在nginx應用時,雖然不阻塞了,但你得不時地過來檢查一下事件的狀態,你可以做更多的事情了,但帶來的開銷也是不小的。所以,纔會採取異步方式。

  • 同步:同步指的當線程進行IO操作請求數據時,是你主動"關心"數據的返回。
  • 異步:當前線程無需主動關心數據是否返回,當數據返回時,會有相關的事件通知你。

舉個例子:同步就是你有不懂的問題問同事,他給你開始講解解決思路或方案,你一直在主動聽取他的內容,異步就是同樣你問同事問題,他可能說我先考慮考慮,想出來了主動來告訴你。

異步方式+非阻塞處理請求可以避免浪費CPU資源,同時提高響應速度,工作效率。其實本質上就是說worker進程,在循環執行異步請求(事件),從而處理高併發。

因此,設置worker的個數爲cpu的核數,在這裏就很容易理解了,更多的worker數,只會導致進程來競爭cpu資源了,從而帶來不必要的資源浪費。

結尾給出Nginx源碼目錄介紹吧。

Nginx代碼的目錄結構

解壓nginx軟件,進入其目錄就可以看到它的目錄結構如下:

[root@localhost nginx-1.16.1]# tree -d
.
├── auto                        #自動編譯安裝相關目錄
│   ├── cc                      #針對各種編譯器進行相應的編譯配置目錄,包括gcc、ccc等
│   ├── lib                     #程序依賴的各種庫,包括openssl、pcre 、perl等
│   │   ├── geoip               
│   │   ├── google-perftools    
│   │   ├── libatomic           
│   │   ├── libgd               
│   │   ├── libxslt             
│   │   ├── openssl             
│   │   ├── pcre                
│   │   ├── perl                
│   │   └── zlib                
│   ├── os                      #針對不同的操作系統所做的編譯配置目錄
│   └── types                   #與數據類型相關的一些輔助腳本
├── conf                        #存放默認配置文件,在make install後,會拷貝到安裝目錄中去
├── contrib                     #存放一些實用工具,如geo配置生成工具(geo2nginx.pl
│   ├── unicode2nginx           #
│   └── vim                     #
│       ├── ftdetect            #
│       ├── ftplugin            #
│       ├── indent              #
│       └── syntax              #
├── html                        #存放默認的網頁文件,在make install後,會拷貝到安裝目錄中去
├── man                         #手冊
└── src                         #存放nginx的源代碼
    ├── core                    #nginx的核心源代碼,包括常用數據結構的定義,以及nginx初始化運行的核心代碼如main函數
    ├── event                   #對系統事件處理機制的封裝,以及定時器的實現相關代碼
    │   └── modules             #不同事件處理方式的模塊化,如select、poll、epoll、kqueue等
    ├── http                    #nginx作爲http服務器相關的代碼
    │   ├── modules             #包含http的各種功能模塊
    │   │   └── perl            #
    │   └── v2                  #
    ├── mail                    #nginx作爲郵件代理服務器相關的代碼
    ├── misc                    #一些輔助代碼,測試c++頭的兼容性,以及對google_perftools的支持
    ├── os                      #主要是對各種不同體系統結構所提供的系統函數的封裝,對外提供統一的系統調用接口
    │   └── unix                #
    └── stream                  #實現四層協議的轉發、代理或者負載均衡等第三方模塊

對於使用者而言最關鍵的是conf目錄,html目錄,對於開發者而言可能需要看其源碼文件:目錄爲src,這就涉及到nginx的核心部分,包括模塊、模塊對應的功能等。

參考鏈接:tengine.taobao.org

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