TCP協議中有長鏈接和短鏈接之分。短鏈接環境下,數據交互完畢後,主動釋放鏈接;html
雙方創建交互的鏈接,可是並非一直存在數據交互,有些鏈接會在數據交互完畢後,主動釋放鏈接,而有些不會,那麼在長時間無數據交互的時間段內,交互雙方都有可能出現掉電、死機、異常重啓,仍是中間路由網絡無端斷開、NAT超時等各類意外。java
當這些意外發生以後,這些TCP鏈接並將來得及正常釋放,那麼,鏈接的另外一方並不知道對端的狀況,它會一直維護這個鏈接,長時間的積累會致使很是多的半打開鏈接,形成端系統資源的消耗和浪費,爲了解決這個問題,在傳輸層能夠利用TCP的保活報文來實現,這就有了TCP的Keepalive(保活探測)機制。linux
在應用交互的過程當中,可能存在如下幾種狀況:數據庫
利用保活探測功能,能夠探知這種對端的意外狀況,從而保證在乎外發生時,能夠釋放半打開的TCP、
鏈接。ubuntu
中間設備如防火牆等,會爲通過它的數據報文創建相關的鏈接信息表,並未其設置一個超時時間的定時器,若是超出預約時間,某鏈接無任何報文交互的話,中間設備會將該鏈接信息從表中刪除,在刪除後,再有應用報文過來時,中間設備將丟棄該報文,從而致使應用出現異常,這個交互的過程大體以下圖所示:服務器
這種狀況在有防火牆的應用環境下很是常見,這會給某些長時間無數據交互可是又要長時間維持鏈接的應用(如數據庫)帶來很大的影響,爲了解決這個問題,應用自己或TCP能夠經過保活報文來維持中間設備中該鏈接的信息,(也能夠在中間設備上開啓長鏈接屬性或調高鏈接表的釋放時間來解決。網絡
常見應用故障場景:socket
某財務應用,在客戶端須要填寫大量的表單數據,在客戶端與服務器端創建TCP鏈接後,客戶端終端使用者將花費幾分鐘甚至幾十分鐘填寫表單相關信息,終端使用者終於填好表單所需信息後,點擊「提交」按鈕,結果,這個時候因爲中間設備早已經將這個TCP鏈接從鏈接表中刪除了,其將直接丟棄這個報文或者給客戶端發送RST報文,應用故障產生,這將致使客戶端終端使用者全部的工做將須要從新來過,給使用者帶來極大的不便和損失。 tcp
TCP保活的交互過程大體以下圖所示:
ide
TCP保活可能帶來的問題:
當鏈接一端在發送保活探測報文時,中間網絡正好因爲各類異常(如鏈路中斷、中間設備重啓等)而沒法將保活探測報文正確轉發至對端時,可能會致使探測的一方釋放原本正常的鏈接,可是這種可能狀況發生的機率較小,
另外,通常也能夠增長保活探測報文發生的次數來減小這種狀況發生的機率和影響。
下面協議解讀,基於 RFC1122#TCP Keep-Alives (注意這是協議的解讀站在協議的角度)
TCP Keepalive不是TCP規範的一部分,有三點須要注意:
如下環境是在Linux服務器上進行,應用程序若想使用須要設置SO_KEEPALIVE套接口選項纔可以生效。
發送頻率tcp_keepalive_intvl乘以發送次數tcp_keepalive_probes,就獲得了從開始探測到放棄探測肯定鏈接斷開的時間;
若設置,服務器在客戶端鏈接空閒的時候,每90秒發送一次保活探測包到客戶端,若沒有及時收到客戶端的TCP Keepalive ACK確認,將繼續等待15秒*2=30秒。總之能夠在90s+30s=120秒(兩分鐘)時間內可檢測到鏈接失效與否。
如下改動,須要寫入到/etc/sysctl.conf文件:
net.ipv4.tcp_keepalive_time=90 net.ipv4.tcp_keepalive_intvl=15 net.ipv4.tcp_keepalive_probes=2
保存退出,而後執行sysctl -p
生效
可經過 sysctl -a | grep keepalive
命令檢測一下是否已經生效。
針對已經設置SO_KEEPALIVE的套接字,應用程序不用重啓,內核直接生效。
只須要在服務器端一方設置便可,客戶端徹底不用設置,好比基於netty 4服務器程序:
ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) // 心跳監測 .childOption(ChannelOption.SO_KEEPALIVE, true) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast( new EchoServerHandler()); } }); // Start the server. ChannelFuture f = b.bind(port).sync(); // Wait until the server socket is closed. f.channel().closeFuture().sync();
Java程序只能作到設置SO_KEEPALIVE選項,至於TCP_KEEPCNT,TCP_KEEPIDLE,TCP_KEEPINTVL等參數配置,只能依賴於sysctl配置,系統進行讀取。
其餘語言怎麼使用:連接
默認狀況下使用keepalive週期爲2個小時,如不選擇更改屬於誤用範疇,形成資源浪費:內核會爲每個鏈接都打開一個保活計時器,N個鏈接會打開N個保活計時器。
優點很明顯:
關閉TCP的keepalive,徹底使用業務層面心跳保活機制 徹底應用掌管心跳,靈活和可控,好比每個鏈接心跳週期的可根據須要減小或延長
業務心跳 + TCP keepalive一塊兒使用,互相做爲補充,但TCP保活探測週期和應用的心跳週期要協調,以互補方可,不可以差距過大,不然將達不到設想的效果。
朋友的公司所作IM平臺業務心跳2-5分鐘智能調整 + tcp keepalive 300秒,組合協做,聽說效果也不錯。
雖說沒有固定的模式可遵循,那麼有如下原則能夠參考:
咱們知道TCP鏈接關閉時,須要鏈接的兩端中的某一方發起關閉動做,若是某一方忽然斷電,另一端是沒法知道的。tcp的keep_alive就是用以檢測異常的一種機制。
有三個參數:
若是是Linux操做系統,這三個值分別爲
huangcheng@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_keepalive_time 7200 huangcheng@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_keepalive_intvl 75 huangcheng@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_keepalive_probes 9
也就意味着每隔7200s(兩個小時)發起一次keepalive的報文,若是沒有迴應,75秒後進行重試,最多重試9次即認爲鏈接關閉。
這三個選項分別對應TCP_KEEPIDLE、TCP_KEEPINTL和TCP_KEEPCNT的選項值,經過setsockopt進行設置。
可是,tcp本身的keepalive有這樣的一個bug:
**
正常狀況下,鏈接的另外一端主動調用colse關閉鏈接,tcp會通知,咱們知道了該鏈接已經關閉。
可是若是tcp鏈接的另外一端忽然掉線,或者重啓斷電,這個時候咱們並不知道網絡已經關閉。
而此時,若是有發送數據失敗,tcp會自動進行重傳。
重傳包的優先級高於keepalive,那就意味着,咱們的keepalive老是不能發送出去。 而此時,咱們也並不知道該鏈接已經出錯而中斷。在較長時間的重傳失敗以後,咱們纔會知道。
爲了不這種狀況發生,咱們要在tcp上層,自行控制。
對於此消息,記錄發送時間和收到迴應的時間。若是長時間沒有迴應,就多是網絡中斷。若是長時間沒有發送,就是說,長時間沒有進行通訊,能夠自行發一個包,用於keepalive,以保持該鏈接的存在。
按照例子的值在一端的socket上開啓keep alive,而後阻塞在一個recv或者不停的send,這個時候拔了網線,測試從拔掉網線到recv/send返回失敗的時間。
在linux kernel裏頭的測試發現,對於阻塞型的socket,當recv的時候,若是沒有設置keep alive,即便網線拔掉或者ifdown,recv很長時間不會返回,最長達17分鐘,雖然這個時間比linux的默認超時時間短了不少。
可是若是設置了keep alive,基本都在keepalive_time +keepalive_probeskeepalive_intvl =33秒內返回錯誤。*
可是對於循環不停send的socket,當拔掉網線後,會持續一段時間send返回成功(0~10秒左右,取決 於發送數據的量),而後send阻塞,由於協議層的buffer滿了,在等待buffer空閒,大概90秒左右後纔會返回錯誤。
由此看來,send的時候,keep alive彷佛沒有起到做用,這個緣由是由於重傳機制。能夠經過給send以前設置timer來解決的。