摘自Apache官方文檔:http://www.souzz.net/online/ApacheManual/misc/fin_wait_2.html
警告:
此文檔沒有考慮到Apache HTTP服務器 2.0 版本中的變化併爲之作完全更新。 其中某些信息可能仍然有效,但使用時請小心。
從Apache 1.2 beta測試版開始,人們就一直在報告說是處於
FIN_WAIT_2狀態的連接(就是netstat
所報告的)比使用舊版本的要多得多。
當服務器關閉一個TCP連接時,它發送一個設置了FIN位標記的包給客戶端,
客戶端以返回一個設置了ACK位標記的包來響應。
然後由客戶端發送一個設置了FIN位標記的包給服務器,
服務器響應以一個設置了ACK位標記的包,這樣連接就關閉了。
連接處於服務器接收到客戶的ACK和FIN信號之間的階段這時候的狀態就叫做FIN_WAIT_2。
參看TCP RFC
瞭解狀態過渡的技術細節。
FIN_WAIT_2狀態有點不尋常,在關於它的標準上沒有超時的定義。 這意味着在許多操作系統上,一個處在FIN_WAIT_2狀態的連接會保留直到系統重起。 如果系統沒有超時限制並且建立了太多的FIN_WAIT_2連接的話,這些連接會填滿分配來存儲信息的空間並使內核崩潰。 FIN_WAIT_2連接不與一個httpd進程相關。
爲什麼會發生?
對此有許多原因,其中一些至今也不很清楚。已知的原因如下:
有缺陷的客戶端與持久連接
有一些客戶端在處理持久連接(aka keepalives)時存在問題。當連接空閒下來服務器關閉連接時(基於KeepAliveTimeout
指令),
客戶端的程序編制使它不發送FIN和ACK回服務器。這樣就意味着這個連接
將停留在FIN_WAIT_2狀態直到以下之一發生:
- 客戶端爲同一個或者不同的站點打開新的連接,這樣會使它在該個套接字上完全關閉以前的連接。
- 用戶退出客戶端程序,這樣在一些(也許是大多數?)客戶端上會使操作系統完全關閉連接。
- FIN_WAIT_2超時,在那些具有FIN_WAIT_2狀態超時設置的服務器上。
如果你夠幸運,這樣意味着那些有缺陷的客戶端會完全關閉連接並釋放你服務器的資源。 然而,有一些情況下套接字永遠不會完全關閉,比如一個撥號客戶端在關閉客戶端程序之前從ISP斷開。 此外,有的客戶端有可能空置好幾天不創建新連接,並且這樣在好幾天裏保持着套接字的有效即使已經不再使用。 這是瀏覽器或者操作系統的TCP實現的Bug。
已經被證實存在這個問題的客戶端有:
- Mozilla/3.01 (X11; I; FreeBSD 2.1.5-RELEASE i386)
- Mozilla/2.02 (X11; I; FreeBSD 2.1.5-RELEASE i386)
- Mozilla/3.01Gold (X11; I; SunOS 5.5 sun4m)
- MSIE 3.01 on the Macintosh
- MSIE 3.01 on Windows 95
沒有出現問題的有:
- Mozilla/3.01 (Win95; I)
預計許多別的客戶端也有同樣的問題。客戶端應該做的工作 是週期性地檢查自己打開的套接字看是否已經被服務器所關閉,並關閉那些已在服務器端被關閉的套接字。 這個檢查只需要每幾秒一次,在某些系統上甚至可以被操作系統信號檢測到。 (例如 ,Win95和NT客戶端有這種能力,但是它們似乎忽視了這一點)。
Apache 不能 避免這些FIN_WAIT_2狀態出現,除非對那些客戶端禁止持久連接, 就像因爲其他問題我們對Navigator 2.x 客戶端坐的建議那樣。然而,非持久連接增加了每個客戶端 需要的連接的數量並使得訪問具有大量圖片的頁面速度降低。由於非持久連接具有自己的資源消耗與 短暫的每次關閉後的等待時期,爲了更好地提供服務,一個繁忙的服務器會需要持續性。
就我們所知,客戶端導致的FIN_WAIT_2問題對所有支持持久連接的服務器都存在, 包括 Apache 1.1.x 和 1.2.
在 1.2 版中引入的一點必需的代碼
儘管上述bug是個問題,但還不是全部的問題。一些用戶在Apache 1.1.x 上沒有觀察到FIN_WAIT_2問題,
但在 1.2b 上建立了足夠多的FIN_WAIT_2狀態連接就會崩潰內核。
這些附加FIN_WAIT_2狀態最有可能的源頭是一個叫做lingering_close()
的函數調用,
它在 1.1 和 1.2版本之間被加入。這個函數對於正確處理持久連接和任何含有內容的請求(例如
,PUTs和POSTs)是必需的。它的工作是在服務器關閉連接後的特定時刻讀取任何客戶端發送的數據。
這樣做的確切理由有點複雜,但是涉及到在客戶端於服務器發送響應並關閉連接的同一時刻提出請求的情況。
沒有延遲的情況下,客戶端在有機會讀取服務器響應之前可能被強制重置其TCP輸入緩衝區,
如此就可以理解連接關閉的原因了。參看附錄
獲得更詳細的瞭解。
lingering_close()
的代碼看起來似乎是引起一系列問題,包括它引起的
traffic patterns(譯註:字典說是“起落航線”,我感覺像流量模式之類的東西,請分析過源碼的朋友解釋一下)的改變.
這些代碼已被徹底地複查過了,我們沒有在其中發現任何bug。
除了缺乏FIN_WAIT_2狀態的超時機制,有可能在BSD TCP棧中有點問題,被引起我們觀察的這些問題的
lingering_close
代碼暴露出來了。
我能對它做什麼?
對於這個問題有幾種可能的變通辦法,其中一些工作得更好。
爲 FIN_WAIT_2 增加 超時機制
明顯的變通辦法是簡單地爲FIN_WAIT_2狀態加上超時機制。 這不是由RFC明確規定的,還有可能與RFC有所衝突,但是這個辦法被廣泛公認是必需的。 以下系統已知具有超時機制:
- FreeBSD 從 2.0 或者更早版本開始。
- NetBSD 版本 1.2(?)
- OpenBSD 所有版本(?)
- BSD/OS 2.1, 安裝了 the K210-027 補丁。
- Solaris
從大約 2.2 版本開始。
可以用
ndd
調節超時來改變tcp_fin_wait_2_flush_interval
, 但是缺省值應該適合大多數的服務器,並且不正確的調節會產生反面作用。 - Linux 2.0.x 與更早版本(?)
- HP-UX
10.x 缺省值是在超過普通持活超時限制以後
終止the FIN_WAIT_2狀態的連接。這不涉及到持久連接或HTTP保持活動的超時,但是跟套結字選項
SO_LINGER
有關——Apache把它激活了。這個參數可以用nettune
來調校修改 像tcp_keepstart
和tcp_keepstop
這樣的參數。 在較晚的修訂版中,有一個用於FIN_WAIT_2態連接的可以被修改的顯式時鐘; 接洽HP支持可以獲得細節。 - SGI IRIX 可以打補丁來支持超時。 對於IRIX 5.3、6.2、和 6.3,分別使用補丁1654、1703 和 1778。如果尋找補丁遇到困難, 請向你的SGI支持渠道尋求幫助。
- NCR's MP RAS Unix 2.xx 和 3.xx 版都有FIN_WAIT_2超時支持。在 2.xx 版中是600秒不可調, 而在 3.xx 版中缺省爲 600 秒,由可調節的"max keep alive probes" (缺省爲8)乘以 "keep alive interval"(缺省爲75秒)得來。
- Sequent's ptx/TCP/IP for DYNIX/ptx 自從1994年中的4.1發行半以來都有FIN_WAIT_2超時支持。
以下系統是已知沒有超時支持的:
- SunOS 4.x 沒有而且幾乎可以肯定以後也不會有這個功能, 因爲對於Sun公司它已經處於其開發生命週期的最後時刻了。如果你有內核的源碼它補丁應該很容易。
有一個 可用的補丁 ,能夠給它加入FIN_WAIT_2狀態的超時支持。最早是爲BSD/OS開發的, 但是應該能夠適合使用BSD網絡代碼的大多數系統。你需要內核源碼才能使用它。
不使用lingering_close()的編譯
編譯不使用lingering_close()
函數的Apache 1.2 是可能的。
這會使得那一部分的代碼更接近 1.1 版的相應部分。如果你這樣做,
要意識到這樣會引起PUTs、POSTs 和持久連接方面的問題,特別是在客戶端使用管道的情況下。
那是說,情況不會比1.1版更壞,而且我們明白保持你的服務器的運行是相當重要的。
要不帶lingering_close()
函數進行編譯,
,在你的Configuration
文件的EXTRA_CFLAGS
行的最後加上-DNO_LINGCLOSE
,重新運行
Configure
並重新編譯服務器。
使用SO_LINGER
作爲lingering_close()
之外另一個選擇
在許多系統上,有一個可以由setsockopt(2)
設置的叫做
SO_LINGER
的選項。它完成與lingering_close()
相似的工作,
除了這一點:它在許多系統上會終止以致引起比lingering_close
多得多的問題。
在某些系統上它也會工作的很好,如果你沒有別的選擇也值得一試。
要試試它,在你的Configuration
文件的EXTRA_CFLAGS
行的最後加上-DUSE_SO_LINGER -DNO_LINGCLOSE
,重新運行
Configure
並重新編譯服務器。
注意
試圖同時使用SO_LINGER
和lingering_close()
非常可能會出現很糟的結果,所以不要這樣做。增加存儲連接狀態的內存數量
- 基於BSD的網絡代碼:
-
BSD 把網絡數據存儲在一個叫做mbuf的區域中,比如連接狀態數據。
當你有太多的連接以至於內核沒有足夠的mbuf來容納它們全部的時候,
你的內核就很可能崩潰。你可以通過增加可用的mbuf數量來減少這個問題的影響;
這樣不能防止問題出現,只是讓服務器在崩潰之前運行得久一點。
正確的增加方法與你的操作系統有關;找一找關於"mbufs"或者"mbuf clusters"數量的參考資料。 在許多系統上,可以這樣做:在你的內核配置文件中添加一行
NMBCLUSTERS="n"
,n
是你想要的mbuf簇的數量,再重新編譯內核。
禁止KeepAlive
如果你無法做上述任何一項修改,那麼作爲最後的手段,你應該禁止KeepAlive。 編輯你的httpd.conf並把"KeepAlive On"改爲"KeepAlive Off"。
附錄
下面是來自Roy Fielding的文章,他是HTTP/1.1的作者之一:
爲什麼延遲關閉功能對HTTP是必需的
服務器延續一個已關閉連接的需要在HTTP規範中被提到好幾次但沒有解釋。 這裏的解釋是基於我以前在W3C的時候我、Henrik Frystyk、Robert S.Thau、Dave Raggett和 John C. Mallery之間在MIT的走廊上的討論。
如果服務器在客戶端發送數據時關閉連接的輸入端(或者準備發送數據),那麼 服務器的TCP棧會給客戶端發一個RST信號(重置)。當接收到RST,客戶端會刷新他自己的TCP輸入緩衝區, 使之回到亦由RST包的參數所指定的未確認的包的狀態。如果恰好在連接關閉之前, 服務器發送了一條消息給客戶——通常是一個錯誤信息,客戶端在其應用程序代碼從TCP輸入緩衝區讀取 此錯誤消息之前接收了RST包並且服務器也已在接受緩衝收到客戶端的ACK信號,那麼, 這個RST信號將會在客戶應用程序有機會讀取這個錯誤信息之前把它刷新掉。 其結果就是客戶端被留在那裏考慮網絡連接無緣無故的失敗。
在兩種情況下很可能會引發這樣的情況:
- 在沒有正確授權的情況下POST或者PUT數據
- 在接收到響應之前發送多個請求(管線)並且其中一個請求導致了錯誤或者其它引起連接中斷的結果。
其解決辦法都是:發送響應,只關閉連接中寫操作的那一半(本來是停止運轉), 並繼續從套接字讀取直到客戶關閉連接(表示讀取響應已經完成)或者出現超時。 這就是如果SO_LINGER被設置的話內核應該做的事。不幸,SO_LINGER在某些系統上不起作用; 在一些其它的系統上,它又沒有自己的超時以致TCP內存段只好一直堆積連接直到下一次重起(計劃中的及之外的)。
請注意簡單地移走延遲代碼並不能解決問題 -- 這樣只是把問題轉移到另一個檢測起來困難的多的地方。.