背景:
理想狀態下,一個TCP連接可以被長期保持,但是實際情況下,一個看似正常的TCP連接,可能已經斷連。兩個主機之間通訊,往往需要通過多個中間節點,如:路由器、防火牆等。因此兩個主機TCP連接保持同樣受中間環節影響。斷連的TCP連接已經沒有意義了,但是維護這樣的連接,可能會浪費服務器的系統資源(尤其是內存和socket資源,客戶端一般還好),因此需要服務器采取相應的探測措施。
方案:
TCP探測的原理很簡單:定期向連接的遠程節點發送一定格式的信息,並等待遠程節點的反饋。如果規定時間內返回,那么連接正常,否則已經斷連。
常見的三種探測方式是:應用程序自己探測;第三方應用程序探測;TCP協議層的保活探測;
應用程序自己探測:可以根據應用程序的特點選擇相應的探測機制和功能實現,比較靈活,但是在實際中,大部分程序並未附帶;
第三方應用程序探測:在服務器上安裝相應的第三方程序,對所有的TCP連接探測,確保連接是否正常。最大的不足是:所有探測的客戶端都需要能夠識別來自探測應用的數據報文,因此實際應用中比較少見;
TCP協議層的保活探測:最常用的方案。采用了TCP協議層的保活探測功能,即TCP連接保活定時器,盡管該功能不是RFC規范的一部分,但是幾乎所有的Unix系統都支持。
TCP保活機制剖析:
常見系統命令:
保活定時器
AIX (單位是0.5秒)
# no -a | grep keep
tcp_keepcnt = 8
tcp_keepidle = 14400
tcp_keepintvl = 150
Linux (單位是秒)
# sysctl -A | grep keep
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 7200
AIX中的參數解析:
tcp_keepcnt:關閉一個非活躍的連接之前,探測的最大次數;
tcp_keepidle:對一個連接進行有效探測之前運行的最大非活躍時間間隔,默認是2小時;
tcp_keepintvl:2個探測的時間間隔,默認是75秒;
防火牆導致斷連分析:
所有服務器主機均划為一個局域網,並處於防火牆 B 之后。由於工作需要,來自工作區局域網的主機 testClient 需和服務器局域網內的 testServer 上的數據庫使用 TCP/IP 建立一個連接,testClient 上的上層應用將通過該連接對 testServer 上的數據庫進行相應操作。
在實際測試中,我們發現,在 testClient 和 testServer 均工作正常的情況下,testClient 上的客戶端在事先沒有收到任何異常信息的情況下,所持有的連接會出現非預期的斷連現象(在試圖通過連接進行數據庫操作時,會被告知 connection is reset by foreign host 的錯誤)。
由於該現象不斷出現,並且網絡內的中間節點(路由器和交換機等)均工作正常,因此可以排除物理因素(如掉電、宕機等)的可能。為了便於分析斷連原因,我們首先查看了 testServer 機器上的默認保活設置:
# no -a | grep keep
tcp_keepcnt = 8
tcp_keepidle = 14400
tcp_keepintvl = 150
testServer 上的 tcp_keepidle 為 14400,即 2 個小時。既然中間節點工作正常,為什么保活機制沒有其作用呢?為了進行分析,我們采用 tcpdump 工具捕獲 testClient 和 testServer 上的報文信息,見清單 5 和清單 6 所示。
清單 5. 服務器端的 tcpdump 數據輸出
1 10:18:58.881950 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: S 1182666808:1182666808(0) ...
2 10:18:58.882001 IP testServer.cn.ibm.com.telnet >
testClient.cn.ibm.com.59098: S 3333341833:3333341833(0) ack 1182666809 ...
3 10:18:58.882845 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: . ack 1 ...
4 ...
5 10:19:03.165568 IP testServer.cn.ibm.com.telnet >
testClient.cn.ibm.com.59098: P 1010:1032(22) ack 87 ...
6 10:19:03.166457 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: . ack 1032 ...
7 12:19:05.445336 IP testServer.cn.ibm.com.telnet >
testClient.cn.ibm.com.59098: . 1031:1032(1) ack 86 ...
8 12:19:05.445464 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: R 86:87(1) ack 1031 ...
清單 6. 客戶端的 tcpdump 數據輸出
1 # tcpdump -e -i eth0 host testServer.cn.ibm.com
2 10:18:55.800553 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: S 1182666808:1182666808(0) ...
3 10:18:55.801778 IP testServer.cn.ibm.com.telnet >
testClient.cn.ibm.com.59098: S 3333341833:3333341833(0) ack 1182666809 ...
4 10:18:55.801799 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: . ack 1 ...
5 ...
6 10:19:00.084662 IP testServer.cn.ibm.com.telnet >
testClient.cn.ibm.com.59098: P 1010:1032(22) ack 87 ...
7 10:19:00.084678 IP testClient.cn.ibm.com.59098 >
testServer.cn.ibm.com.telnet: . ack 1032 ...
從清單 5 中可以看出,在該連接處於非活躍狀態的時間達到 tcp_keepidle 設定的 2 小時時,服務器主機發出了第一個連接保活的探測報文(清單 5 中的第 7 行)。緊接着,服務器主機就收到了來自 testClient 的連接復位報文(清單 5 中的第 8 行)。之后,服務器便關閉了該連接(可以通過 netstat –ni 來查看)。然而,從清單 6 的 tcpdump 數據可以看出, testClient 端並未發送任何報文。那么,是誰向 testServer 發送了復位報文呢?
為了查看上述復位報文的發送者,同樣采用上述 tcpdump 命令再次捕獲服務器端和防火牆 B 的報文信息(注意:通常需要捕獲防火牆主機上網絡數據的出口網卡和入口網卡數據),結果顯示,防火牆 B 在收到來自 testServer 的第一個探測報文之后就立刻向 testServer 發送了一個復位報文。
上述分析說明,在連接傳遞完最后一個交互數據之后到服務器端發送第一個保活探測之間,該連接已經被防火牆 B 終止;在此之后,基於該連接的任何報文傳遞在試圖穿過防火牆的時候均會被防火牆丟棄並發送復位報文。
解決方案:
思路:使防火牆和服務器的TCP連接最大非活躍時間一致;
方案1、演長防火牆的TCP連接最大非活躍,使其時間大於服務器的2小時。缺點是:可能會降低防火牆的性能,尤其是維持大量的長時間處於非活躍的連接情況下。
方案2、縮短服務器的TCP連接報活時間。缺點:網絡中的報文會增加(TCP報活報文增加)。