「每日分享」長連接和心跳的那些事兒

點擊上方"java全棧技術"關注,每天學習一個java知識點

長連接

首先這裡所說的連接是指網絡傳輸層的使用TCP協議經過三次握手建立的連接;長連接是指建立的連接長期保持,不管此時有無數據包的發送;有長連接自然也有短連接,短連接是指雙方有數據發送時,就建立連接,發送幾次請求後,就主動或者被動斷開連接。

心跳

心跳這個名字比較形象,就像人體心跳一樣,是用來檢測一個系統是否存活或者網絡鏈路是否通暢的一種方式,其一般做法是定時向被檢測系統發送心跳包,被檢測系統收到心跳包進行回覆,收到回覆說明對方存活。

心跳和長連接在一起介紹的原因是,心跳能夠給長連接提供保活功能,能夠檢測長連接是否正常(這裡所說的保活不能簡單的理解為保證活著,具體來說應該是一旦鏈路死了,不可用了,能夠儘快知道,然後做些其他的高可用措施,來保證系統的正常運行)。

長連接的優勢

減少連接建立過程的耗時。大家都知道TCP連接建立需要三次握手,三次握手也就說需要三次交互才能建立一個連接通道,同城的機器之間的大概是ms級別的延時,影響還不大,如果是北京和上海兩地機房,走專線一來一回大概需要30ms,如果使用長連接,這個優化還是十分可觀的。

方便實現push數據。數據交互-推模式實現的前提是網絡長連接,有了長連接,連接兩端很方便的互相push數據,來進行交互。

疑問

TCP連接到底是什麼?

所謂的TCP連接不是物理的連接,是為了實現數據的可靠傳輸由通信雙方進行三次握手交互而建立的邏輯上的連接,通信雙方都需要維護這樣的連接狀態信息。比如netstat經常看到連接的狀態為ESTABLISHED,表示當前處於連接狀態。(這裡需要注意的是這個ESTABLISHED的連接狀態只是操作系統認為當前還處在連接狀態)

是不是建立了長連接,就可以高枕無憂了呢?

建立好長連接,兩端的操作系統都維護了連接已經建立的狀態,是不是這時向對端發送數據一定能到達呢?

答案是否定的。

可能此時鏈路已經不通,只是TCP層還沒有感知到這一信息,操作系統層面顯示的狀態依然是連接狀態,而且因為TCP層還認為連接是ESTABLISHED,所以作為應用層自然也就無法感知當前的鏈路不通。

這種情況會導致什麼問題?

如果此時有數據想要傳輸,顯然,數據是無法傳送到對端,但是TCP協議為了保證可靠性,會重傳請求,如果問題只是網線接頭鬆了,導致網絡不通,此時如果及時將網線接頭接好,數據還是能正常到達對端,且TCP的連接依然是ESTABLISHED,不會有任何變化。但不是任何時候都這麼巧,有時就是某段鏈路癱瘓了,或者主機掛了,系統異常關閉了等。這時候如果應用系統不能感知到,是件很危險的事情。

長連接怎麼保活?

TCP協議實現中,是有保活機制的,也就是TCP的KeepAlive機制(此機制並不是TCP協議規範中的內容,由操作系統去實現),KeepAlive機制開啟後,在一定時間內(一般時間為7200s,參數tcp_keepalive_time)在鏈路上沒有數據傳送的情況下,TCP層將發送相應的KeepAlive探針以確定連接可用性,探測失敗後重試10(參數tcp_keepalive_probes)次,每次間隔時間75s(參數tcp_keepalive_intvl),所有探測失敗後,才認為當前連接已經不可用。這些參數是機器級別,可以調整。

應用層需要做點什麼嗎?

按照TCP的KeepAlive機制,默認的參數,顯然不能滿足要求。那是不是調小點就可以了呢?

調整參數,當然是有用的,但是首先參數的機器級別的,調整起來不太方便,更換機器還得記得調整參數,對系統的使用方來說,未免增加了維護成本,而且很可能忘記;其次由於KeepAlive的保活機制只在鏈路空閒的情況下才會起到作用,假如此時有數據發送,且物理鏈路已經不通,操作系統這邊的鏈路狀態還是ESTABLISHED,這時會發生什麼?自然會走TCP重傳機制,要知道默認的TCP超時重傳,指數退避算法也是一個相當長的過程。因此,一個可靠的系統,長連接的保活肯定是要依賴應用層的心跳來保證的。

這裡應用層的心跳舉個例子,比如客戶端每隔3s通過長連接通道發送一個心跳請求到服務端,連續失敗5次就斷開連接。這樣算下來最長15s就能發現連接已經不可用,一旦連接不可用,可以重連,也可以做其他的failover處理,比如請求其他服務器。

應用層心跳還有個好處,比如某臺服務器因為某些原因導致負載超高,CPU飆高,或者線程池打滿等等,無法響應任何業務請求,如果使用TCP自身的機制無法發現任何問題,然而對客戶端而言,這時的最好選擇就是斷連後重新連接其他服務器,而不是一直認為當前服務器是可用狀態,向當前服務器發送一些必然會失敗的請求。

設計誤區

無心跳

無心跳的設計,也是很常見的,為了省事,長連接斷開,TCP傳輸層有通知,應用程序只要處理這種通知,一旦發現連接異常,就重連。但是此類通知可能來的特別晚,比如在機器奔潰,應用程序異常退出,鏈路不通等情況下。

被連接方檢測心跳

心跳的實現分為心跳的發送和心跳的檢測,心跳由誰來發都可以,也可以雙方都發送,但是檢測心跳,必須由發起連接的這端進行,才安全。因為只有發起連接的一端檢測心跳,知道鏈路有問題,這時才會去斷開連接,進行重連,或者重連到另一臺服務器。

例如,client去連接server,client定時發送心跳到server,server檢測心跳,發現一段時間client沒有傳心跳過來,認為與client的鏈路已經出了問題或者client自身就已經出了問題。粗看上去貌似沒什麼問題,但是如果只是client與當前這個server之間的鏈路出了問題,作為一個高可用的系統,是不是應該還有另一個server作為備選,問題出在短時間內client根本不知道自己和第一個server出了問題,所以也不會主動去連接第二個server。

第三方心跳

還有一類心跳,使用第三方保活,也就是除了客戶端和服務端之外,還有另一臺機器,定時發送心跳去探測服務端的存活。這類探活方法使用在檢測系統的存活與否的問題上是沒有問題的,但是這類設計是無法用來檢測客戶端和服務端之間鏈路的好壞。

參考方案

方案一

最簡單的策略當然是客戶端定時n秒發送心跳包,服務端收到心跳包後,回覆客戶端的心跳,如果客戶端連續m秒沒有收到心跳包,則主動斷開連接,然後重連,將正常的業務請求暫時不發送的該臺服務器上。

方案二

可能有人覺得,這樣是不是傳送一些無效的數據包有點多,是不是可以優化下,說實話,個人認為其實一點也不多。當然是可以做些優化的,因為心跳就是一種探測請求,業務上的正常請求除了做業務處理外,還可以用作探測的功能,比如此時有請求需要發送到服務端,這個請求就可以當作是一次心跳,服務端收到請求,處理後回覆,只要服務端有回覆,就表明鏈路還是通的,如果客戶端請求比較空閒的時候,服務端一直沒有數據回覆,就使用心跳進行探測,這樣就有效利用了正常的請求來作為心跳的功能,減少無效的數據傳輸。


分享到:


相關文章: