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 領取知識圖譜