原文地址: http://www.51testing.com/html/71/410671-867088.html
---------------
當然,在nginx中,對於http1.0與http1.1也是支援長串連的。什麼是長串連呢。我們知道,http請求是基於TCP協議之上的,那麼,當用戶端在發起請求前,需要先與服務端建立TCP串連,而每一次的TCP串連是需要三向交握來確定的,如果用戶端與服務端之間網路差一點,這三次互動消費的時間會比較多,而且三次互動也會帶來網路流量。當然,當串連斷開後,也會有四次的互動,當然對使用者體驗來說就不重要了。而http請求是請求應答式的,如果我們能知道每個要求標頭與響應體的長度,那麼我們是可以在一個串連上面執行多個請求的,這就是所謂的長串連,但前提條件是我們先得確定要求標頭與響應體的長度。對於請求來說,如果當前請求需要有body,如POST請求,那麼nginx就需要用戶端在要求標頭中指定content-length來表明body的大小,否則返回400錯誤。也就是說,請求體的長度是確定的,那麼響應體的長度呢。先來看看http協議中關於響應body長度的確定: 對於http1.0協議來說,如果回應標頭中有content-length頭,則以content-length的長度就可以知道body的長度了,用戶端在接收body時,就可以依照這個長度來接收資料,接收完後,就表示這個請求完成了。而如果沒有content-length頭,則用戶端會一直接收資料,直到服務端主動中斷連線,才表示body接收完了。 而對於http1.1協議來說,如果回應標頭中的Transfer-encoding為chunked傳輸,則表示body是流式輸出,body會被分成多個塊,每塊的開始會標識出當前塊的長度,此時,body不需要通過長度來指定。如果是非chunked傳輸,而且有content-length,則按照content-length來接收資料。否則,如果是非chunked,並且沒有content-length,則用戶端接收資料,直到服務端主動中斷連線。
從上面,我們可以看到,除了http1.0不帶content-length以及http1.1非chunked不帶content-length外,body的長度是可知的。此時,當服務端在輸出完body之後,會可以考慮使用長串連。能否使用長串連,也是有條件限制的。如果用戶端的要求標頭中的connection為close,則表示用戶端需要關掉長串連,如果為keep-alive,則用戶端需要開啟長串連,如果用戶端的請求中沒有connection這個頭,那麼根據協議,如果是http1.0,則預設為close,如果是http1.1,則預設為keep-alive。如果結果為keepalive,那麼,nginx在輸出完響應體後,會設定當前串連的keepalive屬性,然後等待用戶端下一次請求。當然,nginx不可能一直等待下去,如果用戶端一直不發資料過來,豈不是一直佔用這個串連。所以當nginx設定了keepalive等待下一次的請求時,同時也會設定一個最大等待時間,這個時間是通過選項keepalive_timeout來配置的,如果配置為0,則表示關掉keepalive,此時,http版本無論是1.1還是1.0,用戶端的connection不管是close還是keepalive,都會強製為close。
如果服務端最後的決定是keepalive開啟,那麼在響應的http頭裡面,也會包含有connection頭域,其值是”Keep-Alive”,否則就是”Close”。如果connection值為close,那麼在nginx響應完資料後,會主動關掉串連。所以,對於請求量比較大的nginx來說,關掉keepalive最後會產生比較多的time-wait狀態的socket。一般來說,當用戶端的一次訪問,需要多次訪問同一個server時,開啟keepalive的優勢非常大,比如圖片伺服器,通常一個網頁會包含很多個圖片。開啟keepalive也會大量減少time-wait的數量。 pipe¶
在http1.1中,引入了一種新的特性,即pipeline。那麼什麼是pipeline呢。pipeline其實就是流水線作業,它可以看作為keepalive的一種升華,因為pipeline也是基於長串連的,目的就是利用一個串連做多次請求。如果用戶端要提交多個請求,對於keepalive來說,那麼第二個請求,必須要等到第一個請求的響應接收完全後,才能發起,這和TCP的停止等待協議是一樣的,得到兩個響應的時間至少為2*RTT。而對pipeline來說,用戶端不必等到第一個請求處理完後,就可以馬上發起第二個請求。得到兩個響應的時間可能能夠達到1*RTT。nginx是直接支援pipeline的,但是,nginx對pipeline中的多個請求的處理卻不是並行的,依然是一個請求接一個請求的處理,只是在處理第一個請求的時候,用戶端就可以發起第二個請求。這樣,nginx利用pipeline減少了處理完一個請求後,等待第二個請求的要求標頭資料的時間。其實nginx的做法很簡單,前面說到,nginx在讀取資料時,會將讀取的資料放到一個buffer裡面,所以,如果nginx在處理完前一個請求後,如果發現buffer裡面還有資料,就認為剩下的資料是下一個請求的開始,然後就接下來處理下一個請求,否則就設定keepalive。 lingering_close¶
lingering_close,字面意思就是延遲關閉,也就是說,當nginx要關閉串連時,並非立即關閉串連,而是先關閉tcp串連的寫,再等待一段時間後再關掉串連的讀。為什麼要這樣呢。我們先來看看這樣一個情境。nginx在接收用戶端的請求時,可能由於用戶端或服務端出錯了,要立即響應錯誤資訊給用戶端,而nginx在響應錯誤資訊後,大分部情況下是需要關閉當前串連。nginx執行完write()系統調用把錯誤資訊發送給用戶端,write()系統調用返回成功並不表示資料已經發送到用戶端,有可能還在tcp串連的write buffer裡。接著如果直接執行close()系統調用關閉tcp串連,核心會首先檢查tcp的read buffer裡有沒有用戶端發送過來的資料留在核心態沒有被使用者態進程讀取,如果有則發送給用戶端RST報文來關閉tcp串連丟棄write buffer裡的資料,如果沒有則等待write buffer裡的資料發送完畢,然後再經過正常的4次分手報文中斷連線。所以,當在某些情境下出現tcp write buffer裡的資料在write()系統調用之後到close()系統調用執行之前沒有發送完畢,且tcp read buffer裡面還有資料沒有讀,close()系統調用會導致用戶端收到RST報文且不會拿到服務端發送過來的錯誤資訊資料。那用戶端肯定會想,這伺服器好霸道,動不動就reset我的串連,連個錯誤資訊都沒有。
在上面這個情境中,我們可以看到,關鍵點是服務端給用戶端發送了RST包,導致自己發送的資料在用戶端忽略掉了。所以,解決問題的重點是,讓服務端別發RST包。再想想,我們發送RST是因為我們關掉了串連,關掉串連是因為我們不想再處理此串連了,也不會有任何資料產生了。對於全雙工系統的TCP串連來說,我們只需要關掉寫就行了,讀可以繼續進行,我們只需要丟掉讀到的任何資料就行了,這樣的話,當我們關掉串連後,用戶端再發過來的資料,就不會再收到RST了。當然最終我們還是需要關掉這個讀端的,所以我們會設定一個逾時時間,在這個時間過後,就關掉讀,用戶端再發送資料來就不管了,作為服務端我會認為,都這麼長時間了,發給你的錯誤資訊也應該讀到了,再慢就不關我事了,要怪就怪你RP不好了。當然,正常的用戶端,在讀取到資料後,會關掉串連,此時服務端就會在逾時時間內關掉讀端。這些正是lingering_close所做的事情。協議棧提供 SO_LINGER 這個選項,它的一種配置情況就是來處理lingering_close的情況的,不過nginx是自己實現的lingering_close。lingering_close存在的意義就是來讀取剩下的用戶端發來的資料,所以nginx會有一個讀逾時時間,通過lingering_timeout選項來設定,如果在lingering_timeout時間內還沒有收到資料,則直接關掉串連。nginx還支援設定一個總的讀取時間,通過lingering_time來設定,這個時間也就是nginx在關閉寫之後,保留socket的時間,用戶端需要在這個時間內發送完所有的資料,否則nginx在這個時間過後,會直接關掉串連。當然,nginx是支援配置是否開啟lingering_close選項的,通過lingering_close選項來配置。 那麼,我們在實際應用中,是否應該開啟lingering_close呢。這個就沒有固定的推薦值了,如Maxim Dounin所說,lingering_close的主要作用是保持更好的用戶端相容性,但是卻需要消耗更多的額外資源(比如串連會一直佔著)。
這節,我們介紹了nginx中,串連與請求的基本概念,下節,我們講基本的資料結構。