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

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

Nginx的進程模型

​ 使用過nginx的朋友都知道nginx的性能很高,而其緣由可能少有人知。首先,nginx的架構就奠基了其高性能的基礎。那麼就先來看看nginx的基礎架構吧,以下圖所示:(不能徹底理清楚全部內容也不要緊,由於本小節講述的主要內容是Nginx的進程模型)html

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

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

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

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

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

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

其實這二者徹底不是一個概念,程序只是一堆等待執行的代碼和部分待處理的數據,只有被加到內存中,由CPU處理執行代碼,才能夠發揮其做用,從而造成一個真正的「活的」動態的進程。因此兩者的典型區別不言而喻,程序未被執行時是靜態的,只有運行時才變成動態的進程,而且進程善始善終(進程的「生老病死」)。編程

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

​ 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

相關文章
相關標籤/搜索