1. 程式人生 > >為什麼說基於TCP的移動端IM仍然需要心跳保活?

為什麼說基於TCP的移動端IM仍然需要心跳保活?

1、前言

有關TCP協議的權威理論介紹,請參見《TCP/IP詳解》這本書。

說明:本文引用了網易雲信項望烽的技術文章,感謝分享。 

2、學習交流

- 即時通訊開發交流群:215891622 [推薦]

3、參考資料

4、本文源起

做移動端IM多年以來,經常會與相關人員進行討論和交流。也經常會碰到些較真的技術人員詢問技術細節,如主流的移動端IM如何做心跳、如何保證訊息必達、如何加快檔案上傳等。因為平時工作太忙,沒有時間深入整理和總結,往往只能簡略介紹,並不能具體展開,於是決定寫成文字,也有了有關移動 IM 問題處理的系列文章。

5、什麼是心跳保活?

152639a88oc4ohnuwp89ny.jpg

在使用 TCP 長連線的 IM 服務設計中,往往都會涉及到心跳。心跳一般是指某端(絕大多數情況下是客戶端)每隔一定時間向對端傳送自定義指令,以判斷雙方是否存活,因其按照一定間隔傳送,類似於心跳,故被稱為心跳指令。

有興趣瞭解IM/推送的心跳保活技術的文章,請參見:

6、TCP協議不是自帶KeepAlive的嗎?

那麼問題就隨之而來了:為什麼需要在應用層做心跳,難道 TCP 不是個可靠連線嗎?我們不能夠依賴 TCP 做斷線檢測嗎?比如使用 TCP 的 KeepAlive 機制來實現。應用層心跳是目前的最佳實踐嗎?怎麼樣的心跳才是最佳實踐。

很多做移動端IM的同行,以前確實沒有仔細考慮過這些問題,潛意識裡想當然的認為這僅僅只是個簡單的心跳而已啊。好吧,事實並非這麼簡單,請繼續往下看。

7、IM中保持有效長連線的重要性

對於客戶端而言,使用 TCP 長連線來實現業務的最大驅動力在於:在當前連線可用的情況下,每一次請求都只是簡單的資料傳送和接受,免去了 DNS 解析,連線建立等時間,大大加快了請求的速度,同時也有利於接受伺服器的實時訊息。但前提是連線可用。

如果連線無法很好地保持,每次請求就會變成撞大運:運氣好,通過長連線傳送請求並收到反饋。運氣差,當前連線已失效,請求遲遲沒有收到反饋直到超時,又需要一次連線建立的過程,其效率甚至還不如 HTTP。而連線保持的前提必然是檢測連線的可用性,並在連線不可用時主動放棄當前連線並建立新的連線。

基於這個前提,必須要有一種機制用於檢測連線可用性。同時行動網路的特殊性也要求客戶端需要在空餘時間傳送一定的信令,避免連線被回收。詳見微信和運營商的撕B(另一篇針對微信的信令風暴技術研究文章請見:《微信對網路影響的技術試驗及分析》)。

而對於伺服器而言,能夠及時獲悉連線可用性也非常重要:一方面伺服器需要及時清理無效連線以減輕負載,另一方面也是業務的需求,如遊戲副本中伺服器需要及時處理玩家掉線帶來的問題。

8、TCP的KeepAlive無法替代應用層心跳保活機制的原因

上面說了保持連線的重要性,那麼現在回到具體實現上。為什麼我們需要使用應用層心跳來做檢測,而不是直接使用 TCP 的特性呢?

我們知道 TCP 是一個基於連線的協議,其連線狀態是由一個狀態機進行維護,連線完畢後,雙方都會處於 established 狀態,這之後的狀態並不會主動進行變化。這意味著如果上層不進行任何呼叫,一直使 TCP 連線空閒,那麼這個連線雖然沒有任何資料,但仍是保持連線狀態,一天、一星期、甚至一個月,即使在這期間中間路由崩潰重啟無數次。舉個現實中經常遇到的栗子:當我們 ssh 到自己的 VPS 上,然後不小心踢掉網線,此時的網路變化並不會被 TCP 檢測出,當我們重新插回網線,仍舊可以正常使用 ssh,同時此時並沒有發生任何 TCP 的重連。

有人會說 TCP 不是有 KeepAlive 機制麼,通過這個機制來實現不就可以了嗎?但是事實上,TCP KeepAlive 的機制其實並不適用於此。Keep Alive 機制開啟後,TCP 層將在定時時間到後傳送相應的 KeepAlive 探針以確定連線可用性。一般時間為 7200 s(詳情請參見《TCP/IP詳解》中第23章),失敗後重試 10 次,每次超時時間 75 s。顯然預設值無法滿足我們的需求,而修改過設定後就可以滿足了嗎?答案仍舊是否定的。

因為 TCP KeepAlive 是用於檢測連線的死活,而心跳機制則附帶一個額外的功能:檢測通訊雙方的存活狀態。兩者聽起來似乎是一個意思,但實際上卻大相徑庭。

考慮一種情況,某臺伺服器因為某些原因導致負載超高,CPU 100%,無法響應任何業務請求,但是使用 TCP 探針則仍舊能夠確定連線狀態,這就是典型的連線活著但業務提供方已死的狀態,對客戶端而言,這時的最好選擇就是斷線後重新連線其他伺服器,而不是一直認為當前伺服器是可用狀態,一直向當前伺服器傳送些必然會失敗的請求。

從上面我們可以知道,KeepAlive 並不適用於檢測雙方存活的場景,這種場景還得依賴於應用層的心跳。應用層心跳有著更大的靈活性,可以控制檢測時機,間隔和處理流程,甚至可以在心跳包上附帶額外資訊。從這個角度而言,應用層的心跳的確是最佳實踐。

9、心跳保活機制的實現方案參考

從上面我們可以得出結論,目前而言,應用層心跳的確是檢測連線有效性,雙方是否存活的最佳實踐,那麼剩下的問題就是怎麼實現。

最簡單粗暴做法當然是定時心跳,如每隔 30 秒心跳一次,15 秒內沒有收到心跳回包則認為當前連線已失效,斷開連線並進行重連。這種做法最直接,實現也簡單。唯一的問題是比較耗電和耗流量。以一個協議包 5 個位元組計算,一天收發 2880 個心跳包,一個月就是 5 * 2 * 2880 * 30 = 0.8 M 的流量,如果手機上多裝幾個 IM 軟體,每個月光心跳就好幾兆流量沒了,更不用說頻繁的心跳帶來的電量損耗。

既然頻繁心跳會帶來耗電和耗流量的弊端,改進的方向自然是減少心跳頻率,但也不能過於影響連線檢測的實時性。基於這個需求,一般可以將心跳間隔根據程式狀態進行調整,當程式在後臺時(這裡主要考慮安卓),儘量拉長心跳間隔,5 分鐘、甚至 10 分鐘都可以。

而當 App 在前臺時則按照原來規則操作。連線可靠性的判斷也可以放寬,避免一次心跳超時就認為連線無效的情況,使用錯誤積累,只在心跳超時 n 次後才判定當前連線不可用。當然還有一些小 trick 比如從收到的最後一個指令包進行心跳包週期計時而不是固定時間,這樣也能夠一定程度減少心跳次數。

http://www.cocoachina.com/ios/20161111/18042.html