【融雲分析】 IM 即時通信之鏈路保活

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

IM 系統總體框架服務器

圖片描述

如上圖所示,爲了保障鏈路存活,一套成熟的 IM 系統通常會包含消息鏈路和推送鏈路兩條長鏈接通道。當有新消息到達時,消息服務首先會判斷消息鏈路是否存活,若是消息鏈路處於存活狀態,消息優先從消息鏈路下發到客戶端,不然會被路由到推送服務器,由推送鏈路下發。網絡

綜上所述,鏈路保活涉及到消息鏈路和推送鏈路兩條鏈路的保活策略。基於這兩條鏈路使用場景的不一樣,保活策略上除了心跳機制是相同的,其它保活策略各有不一樣。下面將詳細講解。框架

鏈路保活的必要性優化

基於 TCP 的 Socket 鏈接創建以後,若是不作任何處理,這個鏈接會長時間存在而且可用嗎?答案是否認的。緣由有兩點:spa

1、默認 Socket 鏈接沒法及時探測到鏈路的異常狀況,即便將 Socket 的屬性參數 keepAlive 設置爲 true 仍然沒法及時獲取到鏈路存活狀態。這是由於 Socket 的鏈接狀態是由一個狀態機進行維護的,鏈接完畢後,雙方都會處於創建狀態。假如某臺服務器由於某些緣由致使負載超高,沒法及時響應業務請求,這時 TCP 探測到的仍然是鏈接狀態,而實際上此鏈路已經不可用了。進程

2、國內運營商的 NAT 超時機制會把必定時間內沒有數據交互的鏈接斷開,這個時間可能只有幾分鐘,遠沒法知足咱們的長鏈接需求。事件

通用保活機制 - 心跳機制圖片

基於以上緣由,要維持 Socket 鏈接長時間存活,就須要實現本身的保活機制。最通用的一種保活機制就是心跳機制。即客戶端每隔一段時間給服務器發送一個很小的數據包,根據可否收到服務器的響應來判斷鏈路的可用性。爲了節省流量,這個包通常很是小,甚至沒有內容。
圖片描述事務

那麼客戶端如何實現定時發送心跳包呢?通常有兩種方式:

一種是經過 Java 裏的 Timer 來實現。最基本的步驟以下:

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

二、建立一個Timer實例,經過Timer提供的schedule()方法,將 TimerTask 加入到定時器Timer 中,設置每隔一段時間執行 TimerTask , 在 TimerTask 裏發送心跳包。這種方式實現起來較簡單,並且省電,不須要持有 WakeLock 。缺點也很明顯,長時間在後臺,進程被回收或者系統休眠後, Timer 機制隨之失效。

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

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

圖片描述

其次 AlarmManager 提供了 cancel() 方法,在設置新的定時任務前,經過 cancel() 方法取消系統裏設置的同類型任務,避免設置冗餘任務。

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

消息鏈路保活機制

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

複合鏈接機制的基本步驟以下:

  1. 客戶端鏈接導航服務器,導航服務器會下發應用對應的配置信息,其中包括鏈接服務器的地址列表。
  2. 客戶端從第一個服務器地址嘗試鏈接,並啓動超時機制,若是鏈接失敗或沒有及時收到服務響應, 則繼續嘗試鏈接下一個直到成功鏈接,將成功鏈接的地址保存到本地,做爲最優地址,後面鏈接時優先使用此地址。經過這種機制,能保障客戶端優先選用最優鏈路,縮短鏈接時間。

圖片描述

重連機制,則是指業務層在檢測到與服務器的鏈接斷開後,嘗試 N 次從新鏈接服務器,首次斷開 1 秒後會從新鏈接,若是仍然鏈接不成功,會在 2 秒後(重連間隔時間爲上次重連間隔時間乘 2 )嘗試從新鏈接服務器,以此類推當嘗試重連 N 次後,仍然連不上服務器將再也不嘗試從新鏈接,只有在網絡狀況發生變化或從新打開應用時纔會再次嘗試重連。
圖片描述

推送鏈路保活機制

推送鏈路做爲消息到達的補充手段,要求儘量延長在後臺的存活時間。即便被殺後,仍然能被再次喚醒。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,這種系統級別的推送省電,省內存,到達率高。應用能夠根據手機型號的不一樣,優先使用廠家系統級別的推送,再配合自身的保活機制,最大程度保障推送的到達率。

集成第三方系統級推送以後,整個消息的收發流程能夠參考下圖:
圖片描述

相關文章
相關標籤/搜索