04-Windows頻繁開啟和關閉連接埠可能引發的問題
鄭昀 20100810 隸屬於《07.雜項》小節
老趙寫了一篇《關於Windows頻繁開啟關閉連接埠時出現的問題》,論述了他從 Windows Web Server 2008 R2 通過 TCP 串連對 Cent OS 下的 MongoDB 資料庫服務做壓力測試,遇到了 Socket 串連資源耗盡,導致程式報告“由於系統緩衝區空間不足或隊列已滿,不能執行通訊端上的操作。”錯誤的情況。
Socket 串連資源耗盡,在 Windows Server 下很常見,如果使用者程式寫得沒問題的話,一般都是微軟(或其他軟體廠商)設定的一些預設參數不合時宜導致的。
我以前寫過一篇《02-Twisted 構建 Web Server 的 Socket 長連結問題 | 07.雜項 | Python》,記述了另外一種 Windows Server 常見問題:在 Python 開發語言下,用 twisted 架構編寫一個 Web Server ,接收 Google PubSubHubbub 的 Hub Server 推送過來的 Google Reader 文章共用資訊時,遇到了檔案描述符到達上限的現象。這個模式的特點是,用 twisted.web.server 對接 PubSubHubbub Hub Server ,雙方都支援重用串連,照理說,既然保持長串連並且重用 socket 串連,不應該出現這種“too many file descriptors in select”錯誤。這裡的知識點是 Windows 下 select module 檔案描述符(file descriptor)最多是512個,而在 Linux 下這個限制為 32767 ,如果超過這個限制值,就會出現類似上面的異常。
老趙的文章指出了一個有意思的知識點:臨時連接埠號碼可分配範圍,
臨時連接埠號碼範圍定義
當一個用戶端程式(比如說一個瀏覽器)初始化一個 connection 串連遠端服務(比如一個網站)時,用戶端會開啟一個“ephemeral(臨時)”連接埠,連接埠號碼一般是隨機分配的。你可以用微軟工具 TCPView 來查看。
那麼這個進程為出站串連(outbound connection)分配連接埠號碼時,連接埠號碼的可分配範圍與作業系統有關。其中一個主要影響因素是 MaxUserPort ,位於註冊表的 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters 下。
下面這張表列出了不同 Windows 作業系統所能分配的連接埠範圍:
作業系統 |
MaxUserPort 數值的 含義 |
連接埠範圍,如果MaxUserPort 沒有定義 |
連接埠範圍,如果MaxUserPort 已定義 |
最小值 |
最大值 |
Windows NT/2000/XP/2003 |
Ending Port |
1025 to 5000 |
1025 to [MaxUserPort] |
5000 |
65535 |
Windows 2000/XP/2003 with KB951748/KB951746 |
Ending Port |
49152 to 65535 |
1025 to [MaxUserPort] |
5000 |
65535 |
Windows Vista/2008 |
Number of Ports |
49152 to 65535 |
49152 to [49152 + MaxUserPort − 1] |
255 |
16384† |
表1 連接埠範圍
當然,你如果在伺服器上安裝了微軟的一些服務,還是會自動修改這個 MaxUserPort 參數的,比如Small Business Server 2000/2003 會修改為 60000, ISA Server 會修改為 65535, Exchange Server 2003 會改為 60000 。
看了這張表後,你會知道:
1、不要使用小於1025的連接埠號碼;
2、盡量使用系統臨時連接埠範圍(ephemeral port range)之外的連接埠,比如你定義自己應用程式要開啟的連接埠號碼為8000,而不是5000。
3、重用串連。
查看了我的筆記本電腦(Win XP)和伺服器(Windows 2003),註冊表裡都沒有設定過這個MaxUserPort參數,所以連接埠範圍就是預設的,在伺服器上,可分配的臨時連接埠是從1025到5000。
回到我的檔案描述符開啟過多的問題上,
重用連接埠失敗+保持長串連生效
如果監聽的 Google Reader User 足夠多,這些使用者又在一個時段集中分享文章,那麼 Google PubSubHubub Hub Server 就會以極快的速度把資料推送過來,此時如果重用 socket 連接埠失敗,而保持長串連策略又起作用,於是很快在 twisted web server 監聽的連接埠上開啟的檔案描述符又爆了。
處理辦法:
1、參考《02-Twisted 構建 Web Server 的 Socket 長連結問題 | 07.雜項 | Python》,
twisted.web.server.Site 類的初始化函數有一個選擇性參數 timeout ,它的預設值是 60*60*12 ,也就是12小時,它的目的是閑置連接埠逾時自動關閉。在我們的情景下,逾時時間太長,所以才會有許多處於 ESTABLISHED 狀態的 Socket Connections 積累。
所以我們縮短為 15 分鐘(也可以更短),讓沒有得到重用的 Connections 儘快自動關閉。只需要在開始執行:
reactor.listenTCP(8080, Site(MyWebResource.setup(),timeout=60*15))
即可。
2、參考《03-PubSubHubbub 和 twisted 的 Persistent connections 能力 | 07.雜項 | Python》,接收到對方 Server 推送過來的資料後,非同步扔給另一個方法解析和處理,render_POST 方法立刻 return NOT_DONE_YET 標誌, 等非同步處理資料成功了,回調裡再 finish 掉當前的 request 。
參考資源:
1、關於Windows頻繁開啟關閉連接埠時出現的問題 ;
2、Choosing a TCP Port for a Network Service ;
3、02-Twisted 構建 Web Server 的 Socket 長連結問題 | 07.雜項 | Python ;
4、03-PubSubHubbub 和 twisted 的 Persistent connections 能力 | 07.雜項 | Python
5、TCP - WAIT狀態及其對繁忙的伺服器的影響 。