Nginx 處理 HTTP 頭部的過程 Nginx 在處理 HTTP 請求之前,首先需要 Nginx 的框架先和客戶端建立好連接,然後接收用戶發來的 HTTP 的請求行,比如方法、URL 等,然後接收所有的 Header,根據這些 Header 信息,才能決定由哪些 HTTP 模塊處理請求。下麵這張 ...
Nginx 處理 HTTP 頭部的過程
Nginx 在處理 HTTP 請求之前,首先需要 Nginx 的框架先和客戶端建立好連接,然後接收用戶發來的 HTTP 的請求行,比如方法、URL 等,然後接收所有的 Header,根據這些 Header 信息,才能決定由哪些 HTTP 模塊處理請求。下麵這張圖,解釋了 Nginx 在處理 HTTP 請求之前,所經歷的一系列流程,強烈建議收藏保存。下麵針對每個部分單獨講解一下。
接收請求事件模塊
首先是三次握手,當客戶端發來 ACK 之後,由操作系統內核回一個 SYN+ACK,緊接著客戶端 ACK 之後,連接建立成功。同時可能有很多 worker 進程都在監聽 80 或 443 埠,由操作系統的負載均衡演算法,選取一個 worker 進程來處理,這個 worker 進程會通過 epoll_wait
方法,返回一個建立連接的句柄。拿到了監聽的句柄之後,這實際上是一個讀事件(因為是從操作系統中讀取到了一個請求),調用 accept
方法,分配連接記憶體池。
記憶體池主要分為連接記憶體池和請求記憶體池。
連接記憶體池大小的配置是 connection_pool_size
,到了這一步之後,Nginx 會為已經建立的連接分配一個 512 位元組大小的連接記憶體池。分配完記憶體池,建立好連接之後,HTTP 模塊會從事件模塊手裡接入請求處理的過程,HTTP 模塊在啟動時,會調用 ngx_http_init_connection
方法來設置回調方法,這個時候會把新建立連接的讀事件通過 epoll_ctl
函數添加到 epoll 中,然後加一個超時定時器 client_header_timeout: 60s
,這個定時器的作用是,如果超過 60s 還沒有接收到客戶端發來的請求,那麼就會斷開連接。這一部分走完之後,Nginx 的事件模塊可能就會切換到其他的句柄去處理了。
當用戶真的把請求發來之後,操作系統會回覆一個 ACK,同時事件模塊的 epoll_wait
也拿到了這個請求,這個時候會調用設置的回調方法 ngx_http_wait_request_handler
,將接收到的用戶請求讀到用戶態中,而讀取到用戶態中需要操作系統分配記憶體,那麼這段記憶體分配多大?從哪裡分配呢?
這段記憶體是從連接記憶體池分配的,初始雖然分配了 512 位元組,但是記憶體池可以擴展,由 client_header_buffer_size: 1k
分配 1k 記憶體,記憶體池並不是越大越好,因為用戶即使發送了 1 個位元組,也會分配出 1k 的記憶體出來。當 URL 超過 1k 後,應該怎麼辦呢?
接收請求 HTTP 模塊
處理請求和處理連接是不一樣的,處理請求只需要放到 Nginx 記憶體中就行了,但是處理請求還需要做大量的上下文分析,所以要分配一個請求記憶體池 request_pool_size: 4k
。分配完以後,狀態機開始解析請求行,如果這時候發現 URL 大於 4k,那麼就會再分配一個大記憶體,也就是 large_client_header_buffers: 4 8k
,這個配置的意思是說,最多分配 4 個 8k,它並不是一次性分配 32k,而是先分配 8k 然後再去解析請求行,如果依然大於 8k,那麼就會再分配 8k 的記憶體。
Nginx 有很多變數,這些變數都是指針,其中可以用來標識 URI,標識完成之後,就開始處理 header。狀態機解析 header 的時候,如果發現記憶體不夠,也就是假如 URL 已經用掉了 large_client_header_buffers: 4 8k
中的 2 個 8k,這時候最多也只能分配 8k,請求行和 header 是公用 4 個 8k的。
分配完大記憶體之後,就開始標識 header,確定哪一個 server 塊去處理請求,然後移除超時定時器,接下來,就開始核心的 11 個階段 HTTP 請求處理請求。
這裡需要註意以下幾個地方:
- 連接記憶體池:初始大小 512 位元組
client_header_buffer_size: 1k
從連接記憶體池中分配large_client_header_buffers: 4 8k
也是從連接記憶體池中分配
- 請求記憶體池:
request_pool_size: 4k
公眾號「原少子楊」回覆 Nginx 領取知識圖譜