天天看點

【融雲分析】 IM 即時通訊之鍊路保活

衆所周知,IM 即時通訊是一項對即時性要求非常高的技術,而保障消息即時到達的首要條件就是鍊路存活。那麼在複雜的網絡環境和國内安卓手機被深度定制化的條件下,如何保障鍊路存活呢?本文詳解了融雲安卓端 SDK 在基于 TCP 協定實作鍊路保活方面的探索和經驗。

IM 系統整體架構

【融雲分析】 IM 即時通訊之鍊路保活

如上圖所示,為了保障鍊路存活,一套成熟的 IM 系統一般會包含消息鍊路和推送鍊路兩條長連接配接通道。當有新消息到達時,消息服務首先會判斷消息鍊路是否存活,如果消息鍊路處于存活狀态,消息優先從消息鍊路下發到用戶端,否則會被路由到推送伺服器,由推送鍊路下發。

綜上所述,鍊路保活涉及到消息鍊路和推送鍊路兩條鍊路的保活政策。基于這兩條鍊路使用場景的不同,保活政策上除了心跳機制是相同的,其它保活政策各有不同。下面将詳細講解。

鍊路保活的必要性

基于 TCP 的 Socket 連接配接建立之後,如果不做任何處理,這個連接配接會長時間存在并且可用嗎?答案是否定的。原因有兩點:

一、預設 Socket 連接配接無法及時探測到鍊路的異常情況,即使将 Socket 的屬性參數 keepAlive 設定為 true 仍然無法及時擷取到鍊路存活狀态。這是因為 Socket 的連接配接狀态是由一個狀态機進行維護的,連接配接完畢後,雙方都會處于建立狀态。假如某台伺服器因為某些原因導緻負載超高,無法及時響應業務請求,這時 TCP 探測到的仍然是連接配接狀态,而實際上此鍊路已經不可用了。

二、國内營運商的 NAT 逾時機制會把一定時間内沒有資料互動的連接配接斷開,這個時間可能隻有幾分鐘,遠無法滿足我們的長連接配接需求。

通用保活機制 - 心跳機制

基于以上原因,要維持 Socket 連接配接長時間存活,就需要實作自己的保活機制。最通用的一種保活機制就是心跳機制。即用戶端每隔一段時間給伺服器發送一個很小的資料包,根據能否收到伺服器的響應來判斷鍊路的可用性。為了節省流量,這個包一般非常小,甚至沒有内容。

【融雲分析】 IM 即時通訊之鍊路保活

那麼用戶端如何實作定時發送心跳包呢?一般有兩種方式:

一種是通過 Java 裡的 Timer 來實作。最基本的步驟如下:

1、建立一個要執行的任務TimerTask。

2、建立一個Timer執行個體,通過Timer提供的schedule()方法,将 TimerTask 加入到定時器Timer 中,設定每隔一段時間執行 TimerTask , 在 TimerTask 裡發送心跳包。這種方式實作起來較簡單,而且省電,不需要持有 WakeLock 。缺點也很明顯,長時間在背景,程序被回收或者系統休眠後, Timer 機制随之失效。

另外一種方式是利用安卓系統的定時任務管理器 AlarmManager 循環執行發送心跳包的任務。這種方式不會因為系統休眠而失效,系統休眠後仍然可以通過 WakeLock 喚醒,執行心跳任務,是以相對 Timer 機制,這種方式比較費電,使用的時候一定要注意如下幾點:

首先根據需求合理使用 AlarmManager 的鬧鐘參數。鬧鐘各參數的差別參考下表:

【融雲分析】 IM 即時通訊之鍊路保活

其次 AlarmManager 提供了 cancel() 方法,在設定新的定時任務前,通過 cancel() 方法取消系統裡設定的同類型任務,避免設定備援任務。

最後,安卓從 6.0 版本引入了 Doze 模式,并提供了新的鬧鐘設定方法 setExactAndAllowWhileIdle(),通過該方法設定的鬧鐘時間,系統會智能排程,将各個應用設定的事務統一在一次喚醒中處理,以達到省電的目的。推薦在安卓 6.0 以上系統中,優先使用該方法。

消息鍊路保活機制

消息鍊路作為收發消息的主要通道,需要最大程度保障鍊路的可用性。在鍊路不可用或者異常斷開時,能及時探測并啟動重連等保障機制。基于以上特性,消息鍊路除了前面所說的心跳機制外,還另外維護了兩套鍊路優化機制:複合連接配接機制和重連機制。

複合連接配接機制的基本步驟如下:

1. 用戶端連接配接導航伺服器,導航伺服器會下發應用對應的配置資訊,其中包括連接配接伺服器的位址清單。

2. 用戶端從第一個伺服器位址嘗試連接配接,并啟動逾時機制,如果連接配接失敗或沒有及時收到服務響應, 則繼續嘗試連接配接下一個直到成功連接配接,将成功連接配接的位址儲存到本地,作為最優位址,後面連接配接時優先使用此位址。通過這種機制,能保障用戶端優先選用最優鍊路,縮短連接配接時間。

【融雲分析】 IM 即時通訊之鍊路保活

 重連機制,則是指業務層在檢測到與伺服器的連接配接斷開後,嘗試 N 次重新連接配接伺服器,首次斷開 1 秒後會重新連接配接,如果仍然連接配接不成功,會在 2 秒後(重連間隔時間為上次重連間隔時間乘 2 )嘗試重新連接配接伺服器,以此類推當嘗試重連 N 次後,仍然連不上伺服器将不再嘗試重新連接配接,隻有在網絡情況發生變化或重新打開應用時才會再次嘗試重連。

【融雲分析】 IM 即時通訊之鍊路保活

推送鍊路保活機制

推送鍊路作為消息到達的補充手段,要求盡可能延長在背景的存活時間。即使被殺後,仍然能被再次喚醒。iOS 手機有 APNS 來達到以上效果,但安卓的官方推送系統 FCM 在國内基本不可用。那在國内安卓系統上如何保障推送到達呢?首先咱們需要先了解下安卓系統上程序管理的兩大機制:

一種是 LMK 機制,英文是 Low Memory Killer, 基于 Linux 的記憶體管理機制衍生而來。主要是通過程序的 oom_adj 值來判定程序的重要程度,進而決定是否回收這些程序。oom_adj 的值越低,代表重要度越高,比如 native 程序,framework 層啟動的系統程序,優先級一般都為負數。其次是前台可見程序,系統也不會回收。然而可見程序退到背景後, oom_adj 的值會立即升高,在系統定時清理時被殺。

另外一種機制是安卓原生的權限管理機制(AppOps),各大廠家在此基礎上又進行了深度定制化,比如小米的安全中心,華為的手機管家等,都用來進行權限管理。該權限管理機制運作在安卓系統的架構層,上層各應用的程序如果想嘗試重新啟動,系統首先會去權限管理中心檢查該程序有沒有自啟動權限,如果有,才準予啟動。否則,從架構層直接限制系統的啟動。

 基于以上兩種機制,推送鍊路的保活也可分為兩大類,

一 程序保活。它的思路是根據 LMK 機制提高程序優先級,降低被殺的幾率。主要有以下幾種方法:

  • 監聽黑屏事件,啟動 1 像素透明 Activity ,使應用程序轉為可視程序,降低被殺機率。在螢幕亮時,關閉該 Activity。
  • 雙服務守護。A 服務以 startForeground() 形式啟動,發送一個通知,B 服務同樣以 startForeground() 形式啟動,且發送和 A 相同 ID 的通知,然後在 B 服務裡調用 stopForeground() 方法,取消通知。這樣 A 服務就會以前台程序的形式存活,且不影響使用者感覺。
  • 根據檔案鎖互斥原理,監視 Java 程序存活狀态,若被殺,Linux 層成功持有檔案,則通過 exec() 指令,打開一個純 Linux 的可執行檔案,開啟一個 Daemon 程序, 該程序因為從 Linux 層啟動,在安卓 5.0 之前,優先級會比較高,不會被殺。在安卓 5.0 之後,該方式不再有效。

二 程序拉活的政策和安卓系統的 AppOps 機制有關,一般有如下幾種:

        1、利用 Service 本身的 Sticky 屬性,在 Service 的 onStartCommand() 中傳回START_STICKY,這樣當 Service 被殺掉後,系統會自動嘗試重新開機。不過在國内定制化的系統上,這種方式能成功重新開機的幾率很低,需要使用者在權限管理中心打開自啟動等權限,才能成功拉活。

       2、也就是前面講過的心跳機制,不過這裡要求使用 AlarmManager 設定 ELAPSED_REALTIME_WAKEUP 屬性的鬧鐘,在系統休眠後,才會正常接受到心跳事件,進而将程序拉活。

       3、通過監聽網絡切換,使用者行為等事件,拉起程序。

       4、應用間互相拉活。比如系統裡有好幾個應用內建了同一個 SDK , 那麼在使用者啟動其中某一個 App 的時候,SDK 會去掃描其它應用,把“兄弟姐妹” 拉活。這種方式對使用者體驗傷害非常大,會造成系統莫名其妙的耗電。

随着安卓系統版本的疊代,對背景程序的啟動管控越來越嚴。為了解決推送的問題,各手機廠家推出了自己的系統級推送服務。由廠家在 Framework 層統一維護一條推送通道,上層所有應用共同使用該推送鍊路,不需要再維護單獨程序。目前支援系統級推送的廠家有:小米、華為、魅族、vivo、OPPO,這種系統級别的推送省電,省記憶體,到達率高。應用可以根據手機型号的不同,優先使用廠家系統級别的推送,再配合自身的保活機制,最大程度保障推送的到達率。

內建第三方系統級推送之後,整個消息的收發流程可以參考下圖:

【融雲分析】 IM 即時通訊之鍊路保活

 更多技術幹貨請點選http://rong.io/X3KE2