一張腦圖說清 Nginx 的主流程

一張腦圖說清 Nginx 的主流程

這個腦圖在 nginx-1.14.0-research 上。這是我在研究nginx的http模塊的時候畫的。基本上把 Nginx 主流程(特別是 HTTP 的部分)的關鍵函數和關鍵設置畫了下來,瞭解了這個腦圖,就對整個 Nginx 的主流程有了定性的瞭解了。css

Nginx 的啓動過程分爲兩個部分,一個部分是讀取配置文件,作配置文件中配置的一些事情(好比監聽端口等)。第二個部分是造成 Master-Worker 的多進程模型。這兩個過程就是 Nginx 代碼中最重要的兩個函數:ngx_init_cyclengx_master_process_cyclehtml

ngx_init_cycle

ngx_init_cycle 是 Nginx 中最重要的函數,沒有之一。咱們能夠想一想,若是咱們寫一個和 Nginx 同樣的 Web 服務,咱們會怎麼作?咱們大體的思路必定是解析配置文件,把配置文件存入到一個數據結構中,而後根據數據結構,進行端口監聽。是的,差很少,Nginx 就是這麼一個流程。不過 Nginx 裏面有個模塊的概念,全部的功能都是用模塊的方式進行加載的。nginx

Nginx 的模塊

Nginx 的模塊分爲幾類,這幾類分別爲 Core,Event,Conf,Http,Mail。看名字就知道 Core 模塊是最重要的。模塊是什麼意思呢?它包含一堆命令(cmd)和命令對應的處理函數(cmd->handler),咱們根據配置文件中的配置(token)就知道這個配置是屬於哪一個模塊的哪一個命令,而後調用命令對應的處理函數來處理或者設置咱們的服務。git

這幾類模塊中,Core 模塊是 Nginx 啓動的時候必定會加載的,其餘的模塊,只有在解析配置的時候,遇到了這個模塊的命令,纔會加載對應的模塊。
這個也是體現了 Nginx 按需加載的理念。(昨天還和小組成員討論,若是咱們寫的話,可能就會先把全部模塊都加載,而後根據配置文件進行匹配,這樣可能 Nginx 的啓動過程和進程資源就變大了)。github

模塊的另外一個問題是我這個 Nginx 最多有哪些模塊的能力呢?這個是編譯的時候就決定了,Nginx 的編譯過程能夠參考這篇文章 。咱們能夠不用管./configure 的時候的具體內容,可是咱們最關注的就是 objs/ngx_modules.c 這個編譯出來的文件,裏面有個ngx_modules全局變量,這個變量裏面就存放了咱們此次編譯的 Nginx 最多能夠支持的模塊。centos

模塊的結構是咱們須要關注的另一個問題。 Nginx 中模塊的結構叫作ngx_module_s(你或許會看到ngx_module_t,其實就是struct ngx_moudle_s的縮寫)緩存

裏面有個結構*ctx,對於不一樣的模塊類型,這個ctx指向的結構是不同的,咱們這裏最主要是研究 HTTP 類型的模塊,因此咱們就記得 HTTP 模塊指向的結構是ngx_http_module_t數據結構

主流程

瞭解了 Nginx 的模塊概念,咱們再回到ngx_init_cycle函數socket

這個函數裏面作了幾個事情:函數

  • ngx_cycle_modules,它本質就是把objs/ngx_modules.c裏面的全局變量拷貝到cycle這個全局變量裏面
  • 調用了每一個 Core 類型模塊的create_conf方法
  • ngx_conf_parse 解析配置文件,調用每一個Core 類型模塊的init_conf方法
  • 調用了每一個 Core 類型模塊的init_conf方法
  • ngx_open_listening_sockets 打開配置文件中設置的監聽端口和IP
  • ngx_init_modules 調用每一個加載模塊的init_module方法

create_conf是建立一些模塊須要初始化的結構,可是這個結構裏面並無具體的值。init_conf是往這些初始化結構裏面填寫配置文件中解析出來的信息。

其中的ngx_conf_parse是真正解析配置文件的。

在代碼ngx_open_listening_sockets裏面咱們看到熟悉的bind,listen的命令。因此 Nginx 是如何多個進程同時監聽一個80端口的?本質是啓動了一個master進程,在ngx_init_cycle裏面監聽了端口,而後在ngx_master_process_cycle裏面 fork 出來多個 worker 子進程。

ngx_conf_parse

這個函數是很是很是重要的。

它的邏輯,就是這兩步,首先使用函數ngx_conf_read_token先循環逐行逐字符查找,看匹配的字符,獲取出cmd, 而後去全部的模塊查找對應的cmd,調用那個查找後的cmd->set方法。用Http模塊舉例子,咱們的配置文件中必定有且只有一個關鍵字叫http

http{

}

先解析這個配置的時候發現了http這個關鍵字,而後去各個模塊匹配,發現ngx_http_module這個模塊包含了http命令。它對應的set方法是ngx_http_block。這個方法就是http模塊很是重要的方法了。固然,這裏順帶提一下,event模塊也有相似的方法,ngx_events_block。它具體作的事情就是解析

event epoll

這樣的命令,並建立出事件驅動的模型。

ngx_http_block

這個函數是http模塊加載的時候最重要的函數,首先,它會遍歷modules.c中的全部 http 模塊,還記得上文說的,HTTP 模塊結構ngx_module_s中的**ctx 指向的是 ngx_http_module

第一步,模塊對三個層級的回調

它內部有這個 HTTP 模塊定義的,在各個層級(http,server,location)所須要加載回調的方法。

咱們這裏再附帶說一下 HTTP 的三個層級,這三個層級對應咱們配置文件裏面的三個不一樣的 Block 語句。

http {
  server {
    listen       80;
    location {
      root   html;
      index  index.html;
    }
  }
}

這三個層次裏面的命令有可能會有重複,有衝突。好比,root這個命令,在 location 中能夠有,在 server 中也能夠有,若是賦值不一致的化,是上層覆蓋下層,仍是下層覆蓋上層(固然大部分都是下層覆蓋上層)。這個就在具體的模塊定義的ngx_http_module結構中定義了 create_main_conf, create_srv_conf,...,merge_srv_conf等方法。這些方法的調用就是在ngx_http_block方法的第一步進行調用的。

第二步,設置鏈接回調和請求監聽回調

第二步是調用方法ngx_http_optimize_servers。它對配置文件中的全部listening的端口和IP進行監聽設置。記住,這裏只是進行回調的設置,具體的listeningbinding操做不是在ngx_conf_parse中,而是在ngx_open_listening_sockets 中。

這個XMind中有標註(xxx時候回調)的分支就是隻有在事件回調的時候會進行調用,不是在ngx_conf_parse的時候調用的。

咱們再仔細看看這個腦圖中的流程,在conf_parse的時候,我實際上只對HTTP鏈接的時候設置了一個回調函數(ngx_http_init_connection)。在有HTTP鏈接上來的時候,纔會設置讀請求的回調(ngx_http_wait_request_handler)。在這個回調,纔是真正的解析 HTTP 請求的請求頭,請求體等。nginx 中著名的11階段就是在這個地方進行一個個步驟進行調用的。

這裏說一下回調。nginx 是由各類各樣的回調組合起來的。回調就須要要求有一個事件驅動機制。在nginx中,這個事件驅動機制也是一個模塊,event 模塊。在編譯的時候,編譯程序會判斷你的系統支持哪些事件驅動,好比個人是centos,支持的是epoll,在配置文件配置

event epoll;

以後,就用這個epoll事件驅動監聽IO事件。其餘模塊和事件驅動的交互就是經過ngx_add_event進行事件監聽和回調的。

http請求因爲可能請求體或者返回體比較大,因此不必定會在一個事件中完成,爲了總體的 nginx 高效,http 模塊在處理 http請求的時候,處理完成了一個event回調函數以後,若是沒有處理完成整個HTTP,就會在event中繼續註冊一個回調,而後把處理權和資源都交給事件驅動中心。等待事件驅動下一次觸發回調。

第三步,初始化定義 HTTP 的11個處理階段

HTTP請求在nginx中會通過11個處理階段和他們的checker方法:

  • NGX_HTTP_POST_READ_PHASE階段(ngx_http_core_generic_phase)
  • NGX_HTTP_SERVER_REWRITE_PHASE階段(ngx_http_rewrite_handler)
  • NGX_HTTP_FIND_CONFIG_PHASE階段(ngx_http_core_find_config_phase)
  • NGX_HTTP_REWRITE_PHASE階段(ngx_http_rewrite_handler)
  • NGX_HTTP_POST_REWRITE_PHASE階段(ngx_http_core_post_rewrite_phase)
  • NGX_HTTP_PREACCESS_PHASE階段(ngx_http_core_generic_phase)
  • NGX_HTTP_ACCESS_PHASE階段(ngx_http_core_access_phase)
  • NGX_HTTP_POST_ACCESS_PHASE階段(ngx_http_core_post_access_phase)
  • NGX_HTTP_TRY_FILES_PHASE階段(ngx_http_core_try_files_phase)
  • NGX_HTTP_CONTENT_PHASE階段(ngx_http_core_content_phase)
  • NGX_HTTP_LOG_PHASE階段(ngx_http_log_module中的ngx_http_log_handler)

就像把大象放冰箱須要幾步,處理 HTTP 須要11步,這11個步驟,有的是處理配置文件,有的是處理rewrite,有的是處理權限,有的是處理日誌。因此,若是咱們要本身開發一個http模塊,咱們就須要定義咱們這個http模塊處理http請求的這11個階段(固然並非這11個階段均可以被自定義的,有的階段是不能被自定義模塊設置的)。而後當一個請求進來的時候,就按照順序把請求通過全部模塊的這11個階段。

這裏所謂的通過這些11個階段本質上就是調用他們的 checker 方法。這些checker方法除了最後一個 NGX_HTTP_LOG_NGX_HTTP_LOG_PAHSE 是在 ngx_http_log_module 裏面以外,其餘的都是在 http 的 core 模塊中定義好了。

其餘

這裏其餘的函數調用就沒有特別須要注意的了。

ngx_http_wait_request_handler

咱們繼續跟着ngx_http_optimize_servers,ngx_http_init_listening,ngx_http_add_listening,ngx_http_init_connection 進入處處理http請求內容的函數裏面。

這個函數先調用recv將http請求的數據獲取到,而後調用ngx_http_create_request建立了 HTTP 的請求結構體。

首先nginx處理的是HTTP的第一行,就是HTTP 1.0 GET, 從這一行,HTTP 會獲取到協議,方法等。接着再調用ngx_http_process_request_line一行一行處理請求頭。ngx_http_read_request_headerngx_http_process_request_headers。處理完成 http header 頭以後,ngx_http_process_request 再接着進行後續的處理

ngx_http_process_request 設置了讀和寫的handler,而且把監聽事件從epoll事件驅動隊列中拿出來(ngx_http_block_reading),就表明這個時候是阻塞作事件處理的事情。而後再調用ngx_http_core_run_phases來讓請求通過那11個階段。

其實並非全部請求都須要讀取整個HTTP請求體。好比你只是獲取一個css文件,nginx在11個階段中的配置讀取階段就不會再繼續讀取HTTP的body了。可是若是是一個fastcgi請求,在http_fastcgi的模塊中,就會進入到NGX_HTTP_CONTENT_PHASE階段,在這個階段,它作的一個事情就是循環讀取讀緩存區的數據,直到讀取完畢,而後進行處理,再返回結構。

相關文章
相關標籤/搜索