Nginx 基礎(chǔ)概念

2022-03-23 15:02 更新

connection

在 Nginx 中 connection 就是對 tcp 連接的封裝,其中包括連接的 socket,讀事件,寫事件。利用 Nginx 封裝的 connection,我們可以很方便的使用 Nginx 來處理與連接相關(guān)的事情,比如,建立連接,發(fā)送與接受數(shù)據(jù)等。而 Nginx 中的 http 請求的處理就是建立在 connection之上的,所以 Nginx 不僅可以作為一個web服務(wù)器,也可以作為郵件服務(wù)器。當(dāng)然,利用 Nginx 提供的 connection,我們可以與任何后端服務(wù)打交道。

結(jié)合一個 tcp 連接的生命周期,我們看看 Nginx 是如何處理一個連接的。首先,Nginx 在啟動時,會解析配置文件,得到需要監(jiān)聽的端口與 ip 地址,然后在 Nginx 的 master 進(jìn)程里面,先初始化好這個監(jiān)控的 socket(創(chuàng)建 socket,設(shè)置 addrreuse 等選項,綁定到指定的 ip 地址端口,再 listen),然后再 fork 出多個子進(jìn)程出來,然后子進(jìn)程會競爭 accept 新的連接。此時,客戶端就可以向 Nginx 發(fā)起連接了。當(dāng)客戶端與服務(wù)端通過三次握手建立好一個連接后,Nginx 的某一個子進(jìn)程會 accept 成功,得到這個建立好的連接的 socket,然后創(chuàng)建 Nginx 對連接的封裝,即 ngx_connection_t 結(jié)構(gòu)體。接著,設(shè)置讀寫事件處理函數(shù)并添加讀寫事件來與客戶端進(jìn)行數(shù)據(jù)的交換。最后,Nginx 或客戶端來主動關(guān)掉連接,到此,一個連接就壽終正寢了。

當(dāng)然,Nginx 也是可以作為客戶端來請求其它 server 的數(shù)據(jù)的(如 upstream 模塊),此時,與其它 server 創(chuàng)建的連接,也封裝在 ngx_connection_t 中。作為客戶端,Nginx 先獲取一個 ngx_connection_t 結(jié)構(gòu)體,然后創(chuàng)建 socket,并設(shè)置 socket 的屬性( 比如非阻塞)。然后再通過添加讀寫事件,調(diào)用 connect/read/write 來調(diào)用連接,最后關(guān)掉連接,并釋放 ngx_connection_t。

在 Nginx 中,每個進(jìn)程會有一個連接數(shù)的最大上限,這個上限與系統(tǒng)對 fd 的限制不一樣。在操作系統(tǒng)中,通過 ulimit -n,我們可以得到一個進(jìn)程所能夠打開的 fd 的最大數(shù),即 nofile,因為每個 socket 連接會占用掉一個 fd,所以這也會限制我們進(jìn)程的最大連接數(shù),當(dāng)然也會直接影響到我們程序所能支持的最大并發(fā)數(shù),當(dāng) fd 用完后,再創(chuàng)建 socket 時,就會失敗。Nginx 通過設(shè)置 worker_connectons 來設(shè)置每個進(jìn)程支持的最大連接數(shù)。如果該值大于 nofile,那么實際的最大連接數(shù)是 nofile,Nginx 會有警告。Nginx 在實現(xiàn)時,是通過一個連接池來管理的,每個 worker 進(jìn)程都有一個獨(dú)立的連接池,連接池的大小是 worker_connections。這里的連接池里面保存的其實不是真實的連接,它只是一個 worker_connections 大小的一個 ngx_connection_t 結(jié)構(gòu)的數(shù)組。并且,Nginx 會通過一個鏈表 free_connections 來保存所有的空閑 ngx_connection_t,每次獲取一個連接時,就從空閑連接鏈表中獲取一個,用完后,再放回空閑連接鏈表里面。

在這里,很多人會誤解 worker_connections 這個參數(shù)的意思,認(rèn)為這個值就是 Nginx 所能建立連接的最大值。其實不然,這個值是表示每個 worker 進(jìn)程所能建立連接的最大值,所以,一個 Nginx 能建立的最大連接數(shù),應(yīng)該是worker_connections * worker_processes。當(dāng)然,這里說的是最大連接數(shù),對于 HTTP 請求本地資源來說,能夠支持的最大并發(fā)數(shù)量是worker_connections * worker_processes,而如果是 HTTP 作為反向代理來說,最大并發(fā)數(shù)量應(yīng)該是worker_connections * worker_processes/2。因為作為反向代理服務(wù)器,每個并發(fā)會建立與客戶端的連接和與后端服務(wù)的連接,會占用兩個連接。

那么,我們前面有說過一個客戶端連接過來后,多個空閑的進(jìn)程,會競爭這個連接,很容易看到,這種競爭會導(dǎo)致不公平,如果某個進(jìn)程得到 accept 的機(jī)會比較多,它的空閑連接很快就用完了,如果不提前做一些控制,當(dāng) accept 到一個新的 tcp 連接后,因為無法得到空閑連接,而且無法將此連接轉(zhuǎn)交給其它進(jìn)程,最終會導(dǎo)致此 tcp 連接得不到處理,就中止掉了。很顯然,這是不公平的,有的進(jìn)程有空余連接,卻沒有處理機(jī)會,有的進(jìn)程因為沒有空余連接,卻人為地丟棄連接。那么,如何解決這個問題呢?首先,Nginx 的處理得先打開 accept_mutex 選項,此時,只有獲得了 accept_mutex 的進(jìn)程才會去添加accept事件,也就是說,Nginx會控制進(jìn)程是否添加 accept 事件。Nginx 使用一個叫 ngx_accept_disabled 的變量來控制是否去競爭 accept_mutex 鎖。在第一段代碼中,計算 ngx_accept_disabled 的值,這個值是 Nginx 單進(jìn)程的所有連接總數(shù)的八分之一,減去剩下的空閑連接數(shù)量,得到的這個 ngx_accept_disabled 有一個規(guī)律,當(dāng)剩余連接數(shù)小于總連接數(shù)的八分之一時,其值才大于 0,而且剩余的連接數(shù)越小,這個值越大。再看第二段代碼,當(dāng) ngx_accept_disabled 大于 0 時,不會去嘗試獲取 accept_mutex 鎖,并且將 ngx_accept_disabled 減 1,于是,每次執(zhí)行到此處時,都會去減 1,直到小于 0。不去獲取 accept_mutex 鎖,就是等于讓出獲取連接的機(jī)會,很顯然可以看出,當(dāng)空余連接越少時,ngx_accept_disable 越大,于是讓出的機(jī)會就越多,這樣其它進(jìn)程獲取鎖的機(jī)會也就越大。不去 accept,自己的連接就控制下來了,其它進(jìn)程的連接池就會得到利用,這樣,Nginx 就控制了多進(jìn)程間連接的平衡了。

    ngx_accept_disabled = ngx_cycle->connection_n / 8
        - ngx_cycle->free_connection_n;

    if (ngx_accept_disabled > 0) {
        ngx_accept_disabled--;

    } else {
        if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
            return;
        }

        if (ngx_accept_mutex_held) {
            flags |= NGX_POST_EVENTS;

        } else {
            if (timer == NGX_TIMER_INFINITE
                    || timer > ngx_accept_mutex_delay)
            {
                timer = ngx_accept_mutex_delay;
            }
        }
    }

好了,連接就先介紹到這,本章的目的是介紹基本概念,知道在 Nginx 中連接是個什么東西就行了,而且連接是屬于比較高級的用法,在后面的模塊開發(fā)高級篇會有專門的章節(jié)來講解連接與事件的實現(xiàn)及使用。

request

這節(jié)我們講 request,在 Nginx 中我們指的是 http 請求,具體到 Nginx 中的數(shù)據(jù)結(jié)構(gòu)是ngx_http_request_t。ngx_http_request_t 是對一個 http 請求的封裝。 我們知道,一個 http 請求,包含請求行、請求頭、請求體、響應(yīng)行、響應(yīng)頭、響應(yīng)體。

http 請求是典型的請求-響應(yīng)類型的的網(wǎng)絡(luò)協(xié)議,而 http 是文本協(xié)議,所以我們在分析請求行與請求頭,以及輸出響應(yīng)行與響應(yīng)頭,往往是一行一行的進(jìn)行處理。如果我們自己來寫一個 http 服務(wù)器,通常在一個連接建立好后,客戶端會發(fā)送請求過來。然后我們讀取一行數(shù)據(jù),分析出請求行中包含的 method、uri、http_version 信息。然后再一行一行處理請求頭,并根據(jù)請求 method 與請求頭的信息來決定是否有請求體以及請求體的長度,然后再去讀取請求體。得到請求后,我們處理請求產(chǎn)生需要輸出的數(shù)據(jù),然后再生成響應(yīng)行,響應(yīng)頭以及響應(yīng)體。在將響應(yīng)發(fā)送給客戶端之后,一個完整的請求就處理完了。當(dāng)然這是最簡單的 webserver 的處理方式,其實 Nginx 也是這樣做的,只是有一些小小的區(qū)別,比如,當(dāng)請求頭讀取完成后,就開始進(jìn)行請求的處理了。Nginx 通過 ngx_http_request_t 來保存解析請求與輸出響應(yīng)相關(guān)的數(shù)據(jù)。

那接下來,簡要講講 Nginx 是如何處理一個完整的請求的。對于 Nginx 來說,一個請求是從ngx_http_init_request 開始的,在這個函數(shù)中,會設(shè)置讀事件為 ngx_http_process_request_line,也就是說,接下來的網(wǎng)絡(luò)事件,會由 ngx_http_process_request_line 來執(zhí)行。從ngx_http_process_request_line 的函數(shù)名,我們可以看到,這就是來處理請求行的,正好與之前講的,處理請求的第一件事就是處理請求行是一致的。通過 ngx_http_read_request_header 來讀取請求數(shù)據(jù)。然后調(diào)用 ngx_http_parse_request_line 函數(shù)來解析請求行。Nginx 為提高效率,采用狀態(tài)機(jī)來解析請求行,而且在進(jìn)行 method 的比較時,沒有直接使用字符串比較,而是將四個字符轉(zhuǎn)換成一個整型,然后一次比較以減少 cpu 的指令數(shù),這個前面有說過。很多人可能很清楚一個請求行包含請求的方法,uri,版本,卻不知道其實在請求行中,也是可以包含有 host 的。比如一個請求 GET http://www.taobao.com/uri HTTP/1.0 這樣一個請求行也是合法的,而且 host 是 www.taobao.com,這個時候,Nginx 會忽略請求頭中的 host 域,而以請求行中的這個為準(zhǔn)來查找虛擬主機(jī)。另外,對于對于 http0.9 版來說,是不支持請求頭的,所以這里也是要特別的處理。所以,在后面解析請求頭時,協(xié)議版本都是 1.0 或 1.1。整個請求行解析到的參數(shù),會保存到 ngx_http_request_t 結(jié)構(gòu)當(dāng)中。

在解析完請求行后,Nginx 會設(shè)置讀事件的 handler 為 ngx_http_process_request_headers,然后后續(xù)的請求就在 ngx_http_process_request_headers 中進(jìn)行讀取與解析。ngx_http_process_request_headers 函數(shù)用來讀取請求頭,跟請求行一樣,還是調(diào)用 ngx_http_read_request_header 來讀取請求頭,調(diào)用 ngx_http_parse_header_line 來解析一行請求頭,解析到的請求頭會保存到 ngx_http_request_t 的域 headers_in 中,headers_in 是一個鏈表結(jié)構(gòu),保存所有的請求頭。而 HTTP 中有些請求是需要特別處理的,這些請求頭與請求處理函數(shù)存放在一個映射表里面,即 ngx_http_headers_in,在初始化時,會生成一個 hash 表,當(dāng)每解析到一個請求頭后,就會先在這個 hash 表中查找,如果有找到,則調(diào)用相應(yīng)的處理函數(shù)來處理這個請求頭。比如:Host 頭的處理函數(shù)是 ngx_http_process_host。

當(dāng) Nginx 解析到兩個回車換行符時,就表示請求頭的結(jié)束,此時就會調(diào)用 ngx_http_process_request 來處理請求了。ngx_http_process_request 會設(shè)置當(dāng)前的連接的讀寫事件處理函數(shù)為 ngx_http_request_handler,然后再調(diào)用 ngx_http_handler 來真正開始處理一個完整的http請求。這里可能比較奇怪,讀寫事件處理函數(shù)都是ngx_http_request_handler,其實在這個函數(shù)中,會根據(jù)當(dāng)前事件是讀事件還是寫事件,分別調(diào)用 ngx_http_request_t 中的 read_event_handler 或者是 write_event_handler。由于此時,我們的請求頭已經(jīng)讀取完成了,之前有說過,Nginx 的做法是先不讀取請求 body,所以這里面我們設(shè)置 read_event_handler 為 ngx_http_block_reading,即不讀取數(shù)據(jù)了。剛才說到,真正開始處理數(shù)據(jù),是在 ngx_http_handler 這個函數(shù)里面,這個函數(shù)會設(shè)置 write_event_handler 為 ngx_http_core_run_phases,并執(zhí)行 ngx_http_core_run_phases 函數(shù)。ngx_http_core_run_phases 這個函數(shù)將執(zhí)行多階段請求處理,Nginx 將一個 http 請求的處理分為多個階段,那么這個函數(shù)就是執(zhí)行這些階段來產(chǎn)生數(shù)據(jù)。因為 ngx_http_core_run_phases 最后會產(chǎn)生數(shù)據(jù),所以我們就很容易理解,為什么設(shè)置寫事件的處理函數(shù)為 ngx_http_core_run_phases 了。在這里,我簡要說明了一下函數(shù)的調(diào)用邏輯,我們需要明白最終是調(diào)用 ngx_http_core_run_phases 來處理請求,產(chǎn)生的響應(yīng)頭會放在 ngx_http_request_t 的 headers_out 中,這一部分內(nèi)容,我會放在請求處理流程里面去講。Nginx 的各種階段會對請求進(jìn)行處理,最后會調(diào)用 filter 來過濾數(shù)據(jù),對數(shù)據(jù)進(jìn)行加工,如 truncked 傳輸、gzip 壓縮等。這里的 filter 包括 header filter 與 body filter,即對響應(yīng)頭或響應(yīng)體進(jìn)行處理。filter 是一個鏈表結(jié)構(gòu),分別有 header filter 與 body filter,先執(zhí)行 header filter 中的所有 filter,然后再執(zhí)行 body filter 中的所有 filter。在 header filter 中的最后一個 filter,即 ngx_http_header_filter,這個 filter 將會遍歷所有的響應(yīng)頭,最后需要輸出的響應(yīng)頭在一個連續(xù)的內(nèi)存,然后調(diào)用 ngx_http_write_filter 進(jìn)行輸出。ngx_http_write_filter 是 body filter 中的最后一個,所以 Nginx 首先的 body 信息,在經(jīng)過一系列的 body filter 之后,最后也會調(diào)用 ngx_http_write_filter 來進(jìn)行輸出(有圖來說明)。

這里要注意的是,Nginx 會將整個請求頭都放在一個 buffer 里面,這個 buffer 的大小通過配置項 client_header_buffer_size 來設(shè)置,如果用戶的請求頭太大,這個 buffer 裝不下,那 Nginx 就會重新分配一個新的更大的 buffer 來裝請求頭,這個大 buffer 可以通過 large_client_header_buffers 來設(shè)置,這個 large_buffer 這一組 buffer,比如配置 48k,就是表示有四個 8k 大小的 buffer 可以用。注意,為了保存請求行或請求頭的完整性,一個完整的請求行或請求頭,需要放在一個連續(xù)的內(nèi)存里面,所以,一個完整的請求行或請求頭,只會保存在一個 buffer 里面。這樣,如果請求行大于一個 buffer 的大小,就會返回 414 錯誤,如果一個請求頭大小大于一個 buffer 大小,就會返回 400 錯誤。在了解了這些參數(shù)的值,以及 Nginx 實際的做法之后,在應(yīng)用場景,我們就需要根據(jù)實際的需求來調(diào)整這些參數(shù),來優(yōu)化我們的程序了。

處理流程圖:

以上這些,就是 Nginx 中一個 http 請求的生命周期了。我們再看看與請求相關(guān)的一些概念吧。

keepalive

當(dāng)然,在 Nginx 中,對于 http1.0 與 http1.1 也是支持長連接的。什么是長連接呢?我們知道,http 請求是基于 TCP 協(xié)議之上的,那么,當(dāng)客戶端在發(fā)起請求前,需要先與服務(wù)端建立 TCP 連接,而每一次的 TCP 連接是需要三次握手來確定的,如果客戶端與服務(wù)端之間網(wǎng)絡(luò)差一點(diǎn),這三次交互消費(fèi)的時間會比較多,而且三次交互也會帶來網(wǎng)絡(luò)流量。當(dāng)然,當(dāng)連接斷開后,也會有四次的交互,當(dāng)然對用戶體驗來說就不重要了。而 http 請求是請求應(yīng)答式的,如果我們能知道每個請求頭與響應(yīng)體的長度,那么我們是可以在一個連接上面執(zhí)行多個請求的,這就是所謂的長連接,但前提條件是我們先得確定請求頭與響應(yīng)體的長度。對于請求來說,如果當(dāng)前請求需要有body,如 POST 請求,那么 Nginx 就需要客戶端在請求頭中指定 content-length 來表明 body 的大小,否則返回 400 錯誤。也就是說,請求體的長度是確定的,那么響應(yīng)體的長度呢?先來看看 http 協(xié)議中關(guān)于響應(yīng) body 長度的確定:

  1. 對于 http1.0 協(xié)議來說,如果響應(yīng)頭中有 content-length 頭,則以 content-length 的長度就可以知道 body 的長度了,客戶端在接收 body 時,就可以依照這個長度來接收數(shù)據(jù),接收完后,就表示這個請求完成了。而如果沒有 content-length 頭,則客戶端會一直接收數(shù)據(jù),直到服務(wù)端主動斷開連接,才表示 body 接收完了。

  2. 而對于 http1.1 協(xié)議來說,如果響應(yīng)頭中的 Transfer-encoding 為 chunked 傳輸,則表示 body 是流式輸出,body 會被分成多個塊,每塊的開始會標(biāo)識出當(dāng)前塊的長度,此時,body 不需要通過長度來指定。如果是非 chunked 傳輸,而且有 content-length,則按照 content-length 來接收數(shù)據(jù)。否則,如果是非 chunked,并且沒有 content-length,則客戶端接收數(shù)據(jù),直到服務(wù)端主動斷開連接。

從上面,我們可以看到,除了 http1.0 不帶 content-length 以及 http1.1 非 chunked 不帶 content-length 外,body 的長度是可知的。此時,當(dāng)服務(wù)端在輸出完 body 之后,會可以考慮使用長連接。能否使用長連接,也是有條件限制的。如果客戶端的請求頭中的 connection為close,則表示客戶端需要關(guān)掉長連接,如果為 keep-alive,則客戶端需要打開長連接,如果客戶端的請求中沒有 connection 這個頭,那么根據(jù)協(xié)議,如果是 http1.0,則默認(rèn)為 close,如果是 http1.1,則默認(rèn)為 keep-alive。如果結(jié)果為 keepalive,那么,Nginx 在輸出完響應(yīng)體后,會設(shè)置當(dāng)前連接的 keepalive 屬性,然后等待客戶端下一次請求。當(dāng)然,Nginx 不可能一直等待下去,如果客戶端一直不發(fā)數(shù)據(jù)過來,豈不是一直占用這個連接?所以當(dāng) Nginx 設(shè)置了 keepalive 等待下一次的請求時,同時也會設(shè)置一個最大等待時間,這個時間是通過選項 keepalive_timeout 來配置的,如果配置為 0,則表示關(guān)掉 keepalive,此時,http 版本無論是 1.1 還是 1.0,客戶端的 connection 不管是 close 還是 keepalive,都會強(qiáng)制為 close。

如果服務(wù)端最后的決定是 keepalive 打開,那么在響應(yīng)的 http 頭里面,也會包含有 connection 頭域,其值是"Keep-Alive",否則就是"Close"。如果 connection 值為 close,那么在 Nginx 響應(yīng)完數(shù)據(jù)后,會主動關(guān)掉連接。所以,對于請求量比較大的 Nginx 來說,關(guān)掉 keepalive 最后會產(chǎn)生比較多的 time-wait 狀態(tài)的 socket。一般來說,當(dāng)客戶端的一次訪問,需要多次訪問同一個 server 時,打開 keepalive 的優(yōu)勢非常大,比如圖片服務(wù)器,通常一個網(wǎng)頁會包含很多個圖片。打開 keepalive 也會大量減少 time-wait 的數(shù)量。

pipe

在 http1.1 中,引入了一種新的特性,即 pipeline。那么什么是 pipeline 呢?pipeline 其實就是流水線作業(yè),它可以看作為 keepalive 的一種升華,因為 pipeline 也是基于長連接的,目的就是利用一個連接做多次請求。如果客戶端要提交多個請求,對于keepalive來說,那么第二個請求,必須要等到第一個請求的響應(yīng)接收完全后,才能發(fā)起,這和 TCP 的停止等待協(xié)議是一樣的,得到兩個響應(yīng)的時間至少為2*RTT。而對 pipeline 來說,客戶端不必等到第一個請求處理完后,就可以馬上發(fā)起第二個請求。得到兩個響應(yīng)的時間可能能夠達(dá)到1*RTT。Nginx 是直接支持 pipeline 的,但是,Nginx 對 pipeline 中的多個請求的處理卻不是并行的,依然是一個請求接一個請求的處理,只是在處理第一個請求的時候,客戶端就可以發(fā)起第二個請求。這樣,Nginx 利用 pipeline 減少了處理完一個請求后,等待第二個請求的請求頭數(shù)據(jù)的時間。其實 Nginx 的做法很簡單,前面說到,Nginx 在讀取數(shù)據(jù)時,會將讀取的數(shù)據(jù)放到一個 buffer 里面,所以,如果 Nginx 在處理完前一個請求后,如果發(fā)現(xiàn) buffer 里面還有數(shù)據(jù),就認(rèn)為剩下的數(shù)據(jù)是下一個請求的開始,然后就接下來處理下一個請求,否則就設(shè)置 keepalive。

lingering_close

lingering_close,字面意思就是延遲關(guān)閉,也就是說,當(dāng) Nginx 要關(guān)閉連接時,并非立即關(guān)閉連接,而是先關(guān)閉 tcp 連接的寫,再等待一段時間后再關(guān)掉連接的讀。為什么要這樣呢?我們先來看看這樣一個場景。Nginx 在接收客戶端的請求時,可能由于客戶端或服務(wù)端出錯了,要立即響應(yīng)錯誤信息給客戶端,而 Nginx 在響應(yīng)錯誤信息后,大分部情況下是需要關(guān)閉當(dāng)前連接。Nginx 執(zhí)行完 write()系統(tǒng)調(diào)用把錯誤信息發(fā)送給客戶端,write()系統(tǒng)調(diào)用返回成功并不表示數(shù)據(jù)已經(jīng)發(fā)送到客戶端,有可能還在 tcp 連接的 write buffer 里。接著如果直接執(zhí)行 close()系統(tǒng)調(diào)用關(guān)閉 tcp 連接,內(nèi)核會首先檢查 tcp 的 read buffer 里有沒有客戶端發(fā)送過來的數(shù)據(jù)留在內(nèi)核態(tài)沒有被用戶態(tài)進(jìn)程讀取,如果有則發(fā)送給客戶端 RST 報文來關(guān)閉 tcp 連接丟棄 write buffer 里的數(shù)據(jù),如果沒有則等待 write buffer 里的數(shù)據(jù)發(fā)送完畢,然后再經(jīng)過正常的 4 次分手報文斷開連接。所以,當(dāng)在某些場景下出現(xiàn) tcp write buffer 里的數(shù)據(jù)在 write()系統(tǒng)調(diào)用之后到 close()系統(tǒng)調(diào)用執(zhí)行之前沒有發(fā)送完畢,且 tcp read buffer 里面還有數(shù)據(jù)沒有讀,close()系統(tǒng)調(diào)用會導(dǎo)致客戶端收到 RST 報文且不會拿到服務(wù)端發(fā)送過來的錯誤信息數(shù)據(jù)。那客戶端肯定會想,這服務(wù)器好霸道,動不動就 reset 我的連接,連個錯誤信息都沒有。

在上面這個場景中,我們可以看到,關(guān)鍵點(diǎn)是服務(wù)端給客戶端發(fā)送了 RST 包,導(dǎo)致自己發(fā)送的數(shù)據(jù)在客戶端忽略掉了。所以,解決問題的重點(diǎn)是,讓服務(wù)端別發(fā) RST 包。再想想,我們發(fā)送 RST 是因為我們關(guān)掉了連接,關(guān)掉連接是因為我們不想再處理此連接了,也不會有任何數(shù)據(jù)產(chǎn)生了。對于全雙工的 TCP 連接來說,我們只需要關(guān)掉寫就行了,讀可以繼續(xù)進(jìn)行,我們只需要丟掉讀到的任何數(shù)據(jù)就行了,這樣的話,當(dāng)我們關(guān)掉連接后,客戶端再發(fā)過來的數(shù)據(jù),就不會再收到 RST 了。當(dāng)然最終我們還是需要關(guān)掉這個讀端的,所以我們會設(shè)置一個超時時間,在這個時間過后,就關(guān)掉讀,客戶端再發(fā)送數(shù)據(jù)來就不管了,作為服務(wù)端我會認(rèn)為,都這么長時間了,發(fā)給你的錯誤信息也應(yīng)該讀到了,再慢就不關(guān)我事了,要怪就怪你 RP 不好了。當(dāng)然,正常的客戶端,在讀取到數(shù)據(jù)后,會關(guān)掉連接,此時服務(wù)端就會在超時時間內(nèi)關(guān)掉讀端。這些正是 lingering_close 所做的事情。協(xié)議棧提供 SO_LINGER 這個選項,它的一種配置情況就是來處理 lingering_close 的情況的,不過 Nginx 是自己實現(xiàn)的 lingering_close。lingering_close 存在的意義就是來讀取剩下的客戶端發(fā)來的數(shù)據(jù),所以 Nginx 會有一個讀超時時間,通過 lingering_timeout 選項來設(shè)置,如果在 lingering_timeout 時間內(nèi)還沒有收到數(shù)據(jù),則直接關(guān)掉連接。Nginx 還支持設(shè)置一個總的讀取時間,通過 lingering_time 來設(shè)置,這個時間也就是 Nginx 在關(guān)閉寫之后,保留 socket 的時間,客戶端需要在這個時間內(nèi)發(fā)送完所有的數(shù)據(jù),否則 Nginx 在這個時間過后,會直接關(guān)掉連接。當(dāng)然,Nginx 是支持配置是否打開 lingering_close 選項的,通過 lingering_close 選項來配置。

那么,我們在實際應(yīng)用中,是否應(yīng)該打開 lingering_close 呢?這個就沒有固定的推薦值了,如 Maxim Dounin所說,lingering_close 的主要作用是保持更好的客戶端兼容性,但是卻需要消耗更多的額外資源(比如連接會一直占著)。

這節(jié),我們介紹了 Nginx 中,連接與請求的基本概念,下節(jié),我們講基本的數(shù)據(jù)結(jié)構(gòu)。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號