在nginx中,主要包括了連接配接與處理兩部分。
在src/core檔案夾下包含有connection的源檔案,ngx_connection.h/ngx_connection.c中可以找到sock_stream,也就是說nginx是基于tcp連接配接的。
對于應用程式,首先第一步肯定是加載并解析配置檔案,nginx同樣如此,這樣可以獲得需要監聽的端口和ip位址。之後,nginx就要建立master程序,并建立socket,這樣就可以建立多個worker程序來,每個worker程序都可以accept連接配接請求。當通過三次握手成功建立一個連接配接後,nginx的某一個worker程序會accept成功,得到這個建立好的連接配接的socket,然後建立ngx_connection_t結構體,存儲用戶端相關内容。
這樣建立好連接配接後,伺服器和用戶端就可以正常進行讀寫事件了。連接配接完成後就可以釋放掉ngx_connection_t結構體了。
同樣,nginx也可以作為用戶端,這樣就需要先建立一個ngx_connection_t結構體,然後建立socket,并設定socket的屬性( 比如非阻塞)。然後再通過添加讀寫事件,調用connect/read/write來調用連接配接,最後關掉連接配接,并釋放ngx_connection_t。
在linux系統中,每一個程序能夠打開的檔案描述符fd是有限的,而每建立一個socket就會占用一個fd,這樣建立的socket就會有限的。在nginx中,采用連接配接池的方法,可以避免這個問題。
nginx在實作時,是通過一個連接配接池來管理的,每個worker程序都有一個獨立的連接配接池,連接配接池的大小是worker_connections。這裡的連接配接池裡面儲存的其實不是真實的連接配接,它隻是一個worker_connections大小的一個ngx_connection_t結構的數組。并且,nginx會通過一個連結清單free_connections來儲存所有的空閑ngx_connection_t,每次擷取一個連接配接時,就從空閑連接配接連結清單中擷取一個,用完後,再放回空閑連接配接連結清單裡面(這樣就節省了建立與銷毀connection結構的開銷)。
是以對于一個nginx伺服器來說,它所能建立的連接配接數也就是socket連接配接數目可以達到worker_processes(worker數)*worker_connections。
對于多個worker程序同時accpet時産生的競争,有可能導緻某一worker程序accept了大量的連接配接,而其他worker程序卻沒有幾個連接配接,這樣就導緻了負載不均衡,對于負載重的worker程序中的連接配接響應時間必然會增大。很顯然,這是不公平的,有的程序有空餘連接配接,卻沒有處理機會,有的程序因為沒有空餘連接配接,卻人為地丢棄連接配接。
nginx中存在accept_mutex選項,隻有獲得了accept_mutex的程序才會去添加accept事件,也就是說,nginx會控制程序是否添加accept事件。nginx使用一個叫ngx_accept_disabled的變量來控制程序是否去競争accept_mutex鎖。
在nginx中,request是http請求,具體到nginx中的資料結構是ngx_http_request_t。ngx_http_request_t是對一個http請求的封裝。
這裡需要複習下http協定了。
http請求是典型的請求-響應類型的的網絡協定,需要一行一行的分析請求行與請求頭,以及輸出響應行與響應頭。
request 消息分為3部分,第一部分叫請求行requset line, 第二部分叫http header, 第三部分是body. header和body之間有個空行。
response消息的結構, 和request消息的結構基本一樣。 同樣也分為三部分,第一部分叫response line, 第二部分叫response header,第三部分是body. header和body之間也有個空行。
分别為request和response消息結構圖:
worker程序負責業務處理。在worker程序中有一個函數ngx_worker_process_cycle(),執行無限循環,不斷處理收到的來自用戶端的請求,并進行處理,直到整個nginx服務被停止。
一個http request的處理過程:
初始化http request(讀取來自用戶端的資料,生成http requst對象,該對象含有該請求所有的資訊)。
處理請求頭。
處理請求體。
如果有的話,調用與此請求(url或者location)關聯的handler
依次調用各phase handler進行處理。
一個phase handler的執行過程:
擷取location配置。
産生适當的響應。
發送response header.
發送response body.
這裡直接上taobao團隊的給出的nginx流程圖了。
從這個圖中可以清晰的看到解析http消息每個部分的不同子產品。
長連接配接的定義:所謂長連接配接,指在一個連接配接上可以連續發送多個資料包,在連接配接保持期間,如果沒有資料包發送,需要雙方發鍊路檢測包。
在這裡,http請求是基于tcp協定之上的,是以建立需要三次握手,關閉需要四次握手。而http請求是請求應答式的,如果我們能知道每個請求頭與響應體的長度,那麼我們是可以在一個連接配接上面執行多個請求的,這就需要在請求頭中指定content-length來表明body的大小。在http1.0與http1.1中稍有不同,具體情況如下:
當用戶端的一次通路,需要多次通路同一個server時,打開keepalive的優勢非常大,比如圖檔伺服器,通常一個網頁會包含很多個圖檔。打開keepalive也會大量減少time-wait的數量。
管道技術是基于長連接配接的,目的是利用一個連接配接做多次請求。
keepalive采用的是串行方式,而pipeline也不是并行的,但是它可以減少兩個請求間的等待的事件。nginx在讀取資料時,會将讀取的資料放到一個buffer裡面,是以,如果nginx在處理完前一個請求後,如果發現buffer裡面還有資料,就認為剩下的資料是下一個請求的開始,然後就接下來處理下一個請求,否則就設定keepalive。
當nginx要關閉連接配接時,并非立即關閉連接配接,而是再等待一段時間後才真正關掉連接配接。目的在于讀取用戶端發來的剩下的資料。
如果伺服器直接關閉,恰巧用戶端剛發送消息,那麼就不會有ack,導緻出現沒有任何錯誤資訊的提示。
nginx通過設定一個讀取客戶資料的逾時事件lingering_timeout來防止以上問題的發生。