Nginx 在處理 HTTP 請求之前,首先需要 Nginx 的框架先和客戶端建立好連接,然后接收用戶發(fā)來的 HTTP 的請求行,比如方法、URL 等,然后接收所有的 Header,根據(jù)這些 Header 信息,才能決定由哪些 HTTP 模塊處理請求。下面這張圖,解釋了 Nginx 在處理 HTTP 請求之前,所經(jīng)歷的一系列流程,強(qiáng)烈建議收藏保存。下面針對每個部分單獨(dú)講解一下。

接收請求事件模塊

首先是三次握手,當(dāng)客戶端發(fā)來 ACK 之后,由操作系統(tǒng)內(nèi)核回一個 SYN+ACK,緊接著客戶端 ACK 之后,連接建立成功。同時可能有很多 worker 進(jìn)程都在監(jiān)聽 80 或 443 端口,由操作系統(tǒng)的負(fù)載均衡算法,選取一個 worker 進(jìn)程來處理,這個 worker 進(jìn)程會通過 epoll_wait 方法,返回一個建立連接的句柄。拿到了監(jiān)聽的句柄之后,這實(shí)際上是一個讀事件(因?yàn)槭菑牟僮飨到y(tǒng)中讀取到了一個請求),調(diào)用 accept 方法,分配連接內(nèi)存池。
內(nèi)存池主要分為連接內(nèi)存池和請求內(nèi)存池。
連接內(nèi)存池大小的配置是 connection_pool_size ,到了這一步之后,Nginx 會為已經(jīng)建立的連接分配一個 512 字節(jié)大小的連接內(nèi)存池。分配完內(nèi)存池,建立好連接之后,HTTP 模塊會從事件模塊手里接入請求處理的過程,HTTP 模塊在啟動時,會調(diào)用 ngx_http_init_connection 方法來設(shè)置回調(diào)方法,這個時候會把新建立連接的讀事件通過 epoll_ctl 函數(shù)添加到 epoll 中,然后加一個超時定時器 client_header_timeout: 60s ,這個定時器的作用是,如果超過 60s 還沒有接收到客戶端發(fā)來的請求,那么就會斷開連接。這一部分走完之后,Nginx 的事件模塊可能就會切換到其他的句柄去處理了。

當(dāng)用戶真的把請求發(fā)來之后,操作系統(tǒng)會回復(fù)一個 ACK,同時事件模塊的 epoll_wait 也拿到了這個請求,這個時候會調(diào)用設(shè)置的回調(diào)方法 ngx_http_wait_request_handler ,將接收到的用戶請求讀到用戶態(tài)中,而讀取到用戶態(tài)中需要操作系統(tǒng)分配內(nèi)存,那么這段內(nèi)存分配多大?從哪里分配呢?
這段內(nèi)存是從連接內(nèi)存池分配的,初始雖然分配了 512 字節(jié),但是內(nèi)存池可以擴(kuò)展,由 client_header_buffer_size: 1k 分配 1k 內(nèi)存,內(nèi)存池并不是越大越好,因?yàn)橛脩艏词拱l(fā)送了 1 個字節(jié),也會分配出 1k 的內(nèi)存出來。當(dāng) URL 超過 1k 后,應(yīng)該怎么辦呢?
接收請求 HTTP 模塊

處理請求和處理連接是不一樣的,處理請求只需要放到 Nginx 內(nèi)存中就行了,但是處理請求還需要做大量的上下文分析,所以要分配一個請求內(nèi)存池 request_pool_size: 4k 。分配完以后,狀態(tài)機(jī)開始解析請求行,如果這時候發(fā)現(xiàn) URL 大于 4k,那么就會再分配一個大內(nèi)存,也就是 large_client_header_buffers: 4 8k ,這個配置的意思是說,最多分配 4 個 8k,它并不是一次性分配 32k,而是先分配 8k 然后再去解析請求行,如果依然大于 8k,那么就會再分配 8k 的內(nèi)存。
Nginx 有很多變量,這些變量都是指針,其中可以用來標(biāo)識 URI,標(biāo)識完成之后,就開始處理 header。狀態(tài)機(jī)解析 header 的時候,如果發(fā)現(xiàn)內(nèi)存不夠,也就是假如 URL 已經(jīng)用掉了 large_client_header_buffers: 4 8k 中的 2 個 8k,這時候最多也只能分配 8k,請求行和 header 是公用 4 個 8k的。
分配完大內(nèi)存之后,就開始標(biāo)識 header,確定哪一個 server 塊去處理請求,然后移除超時定時器,接下來,就開始核心的 11 個階段 HTTP 請求處理請求。
這里需要注意以下幾個地方:
- 連接內(nèi)存池:初始大小 512 字節(jié)
client_header_buffer_size: 1k 從連接內(nèi)存池中分配
large_client_header_buffers: 4 8k 也是從連接內(nèi)存池中分配
- 請求內(nèi)存池:
request_pool_size: 4k
|