本文來自融雲技術團隊原創分享,原文發佈於「 融雲全球互聯網通訊雲」公衆號,原題《IM 即時通信之鏈路保活》,即時通信網收錄時有部分改動。php
衆所周知,IM 即時通信是一項對即時性要求很是高的技術,而保障消息即時到達的首要條件就是鏈路存活。那麼在複雜的網絡環境和國內安卓手機被深度定製化的條件下,如何保障鏈路存活呢?本文詳解了融雲安卓端IM產品在基於 TCP 協議實現鏈路保活方面的實踐總結。html
學習交流:java
- 即時通信/推送技術開發交流5羣:215477170 [推薦]android
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》編程
(本文同步發佈於:http://www.52im.net/thread-2744-1-1.html)api
- 《爲什麼基於TCP協議的移動端IM仍然須要心跳保活機制?》
- 《微信團隊原創分享:Android版微信後臺保活實戰分享(進程保活篇)》
- 《微信團隊原創分享:Android版微信後臺保活實戰分享(網絡保活篇)》
- 《移動端IM實踐:實現Android版微信的智能心跳機制》
- 《移動端IM實踐:WhatsApp、Line、微信的心跳策略分析》
- 《Android P正式版即將到來:後臺應用保活、消息推送的真正噩夢》
- 《全面盤點當前Android後臺保活方案的真實運行效果(截止2019年前)》
- 《一文讀懂即時通信應用中的網絡心跳包機制:做用、原理、實現思路等》
- 《融雲技術分享:融雲安卓端IM產品的網絡鏈路保活技術實踐》
如上圖所示,爲了保障鏈路存活,一套成熟的 IM 系統通常會包含消息鏈路和推送鏈路兩條長鏈接通道。安全
當有新消息到達時,消息服務首先會判斷消息鏈路是否存活,若是消息鏈路處於存活狀態,消息優先從消息鏈路下發到客戶端,不然會被路由到推送服務器,由推送鏈路下發。服務器
綜上所述:鏈路保活涉及到消息鏈路和推送鏈路兩條鏈路的保活策略。基於這兩條鏈路使用場景的不一樣,保活策略上除了心跳機制是相同的,其它保活策略各有不一樣。下面將逐一解讀。微信
基於 TCP 的 Socket 鏈接創建以後,若是不作任何處理,這個鏈接會長時間存在而且可用嗎?答案是否認的。網絡
緣由有兩點:
1)默認Socket 鏈接沒法及時探測到鏈路的異常狀況,即便將 Socket 的屬性參數 KeepAlive 設置爲 True 仍然沒法及時獲取到鏈路存活狀態。這是由於 Socket 的鏈接狀態是由一個狀態機進行維護的,鏈接完畢後,雙方都會處於創建狀態。假如某臺服務器由於某些緣由致使負載超高,沒法及時響應業務請求,這時 TCP 探測到的仍然是鏈接狀態,而實際上此鏈路已經不可用了。
2)國內運營商的 NAT 超時機制會把必定時間內沒有數據交互的鏈接斷開,這個時間可能只有幾分鐘,遠沒法知足咱們的長鏈接需求。
這方面更詳細的技術文章,請見:《爲什麼基於TCP協議的移動端IM仍然須要心跳保活機制?》、《微信團隊原創分享:Android版微信後臺保活實戰分享(網絡保活篇)》
基於以上緣由,要維持 Socket 鏈接長時間存活,就須要實現本身的保活機制。
最通用的一種保活機制就是心跳機制。即客戶端每隔一段時間給服務器發送一個很小的數據包,根據可否收到服務器的響應來判斷鏈路的可用性。爲了節省流量,這個包通常很是小(一般是越小越好,好比網易雲信的IM雲產品中1字節心跳包是做爲產品賣點進行宣傳的),甚至沒有內容。
那麼客戶端如何實現定時發送心跳包呢?通常有兩種方式。
一種是經過 Java 裏的 Timer 來實現。
最基本的步驟以下:
1)創建一個要執行的任務 TimerTask ;
2)建立一個 Timer 實例,經過 Timer 提供的 schedule() 方法,將 TimerTask 加入到定時器 Timer 中,設置每隔一段時間執行 TimerTask , 在 TimerTask 裏發送心跳包。這種方式實現起來較簡單,並且省電,不須要持有 WakeLock 。缺點也很明顯,長時間在後臺,進程被回收或者系統休眠後, Timer 機制隨之失效。
另一種方式是利用安卓系統的定時任務管理器 AlarmManager 循環執行發送心跳包的任務。
這種方式不會由於系統休眠而失效,系統休眠後仍然能夠經過 WakeLock 喚醒,執行心跳任務。
所以相對 Timer 機制,這種方式比較費電,使用的時候必定要注意以下幾點:
1)首先根據需求合理使用 AlarmManager 的鬧鐘參數。鬧鐘各參數的區別參考下表:
2)其次 AlarmManager 提供了 cancel() 方法,在設置新的定時任務前,經過 cancel() 方法取消系統裏設置的同類型任務,避免設置冗餘任務。
最後,安卓從 6.0 版本引入了 Doze 模式,並提供了新的鬧鐘設置方法 setExactAndAllowWhileIdle() ,經過該方法設置的鬧鐘時間,系統會智能調度,將各個應用設置的事務統一在一次喚醒中處理,以達到省電的目的。推薦在安卓 6.0 以上系統中,優先使用該方法。
這方面更詳細的技術文章,請見:
《應用保活終極總結(一):Android6.0如下的雙進程守護保活實踐》
《應用保活終極總結(二):Android6.0及以上的保活實踐(進程防殺篇)》
《應用保活終極總結(三):Android6.0及以上的保活實踐(被殺復活篇)》
消息鏈路做爲收發消息的主要通道,須要最大程度保障鏈路的可用性。在鏈路不可用或者異常斷開時,能及時探測並啓動重連等保障機制。
基於以上特性,消息鏈路除了前面所說的心跳機制外,還另外維護了兩套鏈路優化機制:複合鏈接機制和重連機制。
複合鏈接機制的基本步驟以下:
1)客戶端鏈接導航服務器,導航服務器會下發應用對應的配置信息,其中包括鏈接服務器的地址列表;
2)客戶端從第一個服務器地址嘗試鏈接,並啓動超時機制,若是鏈接失敗或沒有及時收到服務響應, 則繼續嘗試鏈接下一個直到成功鏈接,將成功鏈接的地址保存到本地,做爲最優地址,後面鏈接時優先使用此地址。經過這種機制,能保障客戶端優先選用最優鏈路,縮短鏈接時間。
▲ 複合鏈接機制原理
重連機制:則是指業務層在檢測到與服務器的鏈接斷開後,嘗試 N 次從新鏈接服務器,首次斷開 1 秒後會從新鏈接,若是仍然鏈接不成功,會在 2 秒後(重連間隔時間爲上次重連間隔時間乘 2 )嘗試從新鏈接服務器,以此類推當嘗試重連 N 次後,仍然連不上服務器將再也不嘗試從新鏈接,只有在網絡狀況發生變化或從新打開應用時纔會再次嘗試重連。
▲ 重連機制原理
推送鏈路做爲消息到達的補充手段,要求儘量延長在後臺的存活時間。即便被殺後,仍然能被再次喚醒。 iOS 手機有 APNS 來達到以上效果(詳見《瞭解iOS消息推送一文就夠:史上最全iOS Push技術詳解》),但安卓的官方推送系統 FCM 在國內基本不可用。那在國內安卓系統上如何保障推送到達呢?
首先我們須要先了解下安卓系統上進程管理的兩大機制:
1)一種是 LMK 機制,英文是 Low Memory Killer , 基於 Linux 的內存管理機制衍生而來。主要是經過進程的 oom_adj 值來斷定進程的重要程度,從而決定是否回收這些進程。 oom_adj 的值越低,表明重要度越高,好比 native 進程, framework 層啓動的系統進程,優先級通常都爲負數。其次是前臺可見進程,系統也不會回收。然而可見進程退到後臺後, oom_adj 的值會當即升高,在系統定時清理時被殺;
2)另一種機制是安卓原生的權限管理機制( AppOps ),各大廠家在此基礎上又進行了深度定製化,好比小米的安全中心,華爲的手機管家等,都用來進行權限管理。該權限管理機制運行在安卓系統的框架層,上層各應用的進程若是想嘗試從新啓動,系統首先會去權限管理中心檢查該進程有沒有自啓動權限,若是有,才准予啓動。不然,從框架層直接限制系統的啓動。
基於以上兩種機制,推送鏈路的保活也可分爲兩大類。
第一類:進程保活:
它的思路是根據 LMK 機制提升進程優先級,下降被殺的概率。
主要有如下幾種方法:
1.1)監聽黑屏事件,啓動 1 像素透明 Activity :使應用進程轉爲可視進程,下降被殺機率。在屏幕亮時,關閉該 Activity 。
1.2)雙服務守護: A 服務以 startForeground() 形式啓動,發送一個通知, B 服務一樣以 startForeground() 形式啓動,且發送和 A 相同 ID 的通知,而後在 B 服務裏調用 stopForeground() 方法,取消通知。這樣 A 服務就會之前臺進程的形式存活,且不影響用戶感知。
1.3)根據文件鎖互斥原理,監視 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 會去掃描其它應用,把「兄弟姐妹」 拉活。這種方式對用戶體驗傷害很是大,會形成系統莫名其妙的耗電。
如下保活「黑科技」的詳細介紹文章,請詳讀:
《應用保活終極總結(一):Android6.0如下的雙進程守護保活實踐》
隨着安卓系統版本的迭代,對後臺進程的啓動管控愈來愈嚴。爲了解決推送的問題,各手機廠家推出了本身的系統級推送服務。由廠家在 Framework 層統一維護一條推送通道,上層全部應用共同使用該推送鏈路,不須要再維護單獨進程。當前支持系統級推送的廠家有:小米、華爲、魅族、 vivo 、OPPO 。
鑑於Android系統對後臺進程管控愈來愈嚴,保活「黑科技」已經不怎麼靈了:
集成第三方系統級推送以後,整個消息的收發流程能夠參考下圖:
這種系統級別的推送省電,省內存,到達率高。應用能夠根據手機型號的不一樣,優先使用廠家系統級別的推送,再配合自身的保活機制,最大程度保障推送的到達率。
[1] IM開發相關的熱門技術問題綜合文章:
《新手入門一篇就夠:從零開發移動端IM》
《移動端IM開發者必讀(一):通俗易懂,理解移動網絡的「弱」和「慢」》
《移動端IM開發者必讀(二):史上最全移動弱網絡優化方法總結》
《從客戶端的角度來談談移動端IM的消息可靠性和送達機制》
《現代移動端網絡短鏈接的優化手段總結:請求速度、弱網適應、安全保障》
《騰訊技術分享:社交網絡圖片的帶寬壓縮技術演進之路》
《小白必讀:閒話HTTP短鏈接中的Session和Token》
《IM開發基礎知識補課:正確理解前置HTTP SSO單點登錄接口的原理》
《移動端IM中大規模羣消息的推送如何保證效率、實時性?》
《移動端IM開發須要面對的技術問題》
《開發IM是本身設計協議用字節流好仍是字符流好?》
《請問有人知道語音留言聊天的主流實現方式嗎?》
《IM消息送達保證機制實現(一):保證在線實時消息的可靠投遞》
《IM消息送達保證機制實現(二):保證離線消息的可靠投遞》
《如何保證IM實時消息的「時序性」與「一致性」?》
《一個低成本確保IM消息時序的方法探討》
《IM單聊和羣聊中的在線狀態同步應該用「推」仍是「拉」?》
《IM羣聊消息如此複雜,如何保證不丟不重?》
《談談移動端 IM 開發中登陸請求的優化》
《移動端IM登陸時拉取數據如何做到省流量?》
《淺談移動端IM的多點登錄和消息漫遊原理》
《徹底自已開發的IM該如何設計「失敗重試」機制?》
《通俗易懂:基於集羣的移動端IM接入層負載均衡方案分享》
《微信對網絡影響的技術試驗及分析(論文全文)》
《即時通信系統的原理、技術和應用(技術論文)》
[2] 一些網絡編程基礎資料:
《TCP/IP詳解 - 第11章·UDP:用戶數據報協議》
《TCP/IP詳解 - 第17章·TCP:傳輸控制協議》
《TCP/IP詳解 - 第18章·TCP鏈接的創建與終止》
《TCP/IP詳解 - 第21章·TCP的超時與重傳》
《技術往事:改變世界的TCP/IP協議(珍貴多圖、手機慎點)》
《通俗易懂-深刻理解TCP協議(上):理論基礎》
《通俗易懂-深刻理解TCP協議(下):RTT、滑動窗口、擁塞處理》
《高性能網絡編程(一):單臺服務器併發TCP鏈接數到底能夠有多少》
《高性能網絡編程(二):上一個10年,著名的C10K併發鏈接問題》
《高性能網絡編程(三):下一個10年,是時候考慮C10M併發問題了》
《高性能網絡編程(四):從C10K到C10M高性能網絡應用的理論探索》
《高性能網絡編程(五):一文讀懂高性能網絡編程中的I/O模型》
《高性能網絡編程(六):一文讀懂高性能網絡編程中的線程模型》
《鮮爲人知的網絡編程(一):淺析TCP協議中的疑難雜症(上篇)》
《鮮爲人知的網絡編程(二):淺析TCP協議中的疑難雜症(下篇)》
《鮮爲人知的網絡編程(三):關閉TCP鏈接時爲何會TIME_WAIT、CLOSE_WAIT》
《鮮爲人知的網絡編程(四):深刻研究分析TCP的異常關閉》
《鮮爲人知的網絡編程(五):UDP的鏈接性和負載均衡》
《鮮爲人知的網絡編程(六):深刻地理解UDP協議並用好它》
《鮮爲人知的網絡編程(七):如何讓不可靠的UDP變的可靠?》
《鮮爲人知的網絡編程(八):從數據傳輸層深度解密HTTP》
《鮮爲人知的網絡編程(九):理論聯繫實際,全方位深刻理解DNS》
《網絡編程懶人入門(一):快速理解網絡通訊協議(上篇)》
《網絡編程懶人入門(二):快速理解網絡通訊協議(下篇)》
《網絡編程懶人入門(三):快速理解TCP協議一篇就夠》
《網絡編程懶人入門(四):快速理解TCP和UDP的差別》
《網絡編程懶人入門(五):快速理解爲何說UDP有時比TCP更有優點》
《網絡編程懶人入門(六):史上最通俗的集線器、交換機、路由器功能原理入門》
《網絡編程懶人入門(七):深刻淺出,全面理解HTTP協議》
《網絡編程懶人入門(八):手把手教你寫基於TCP的Socket長鏈接》
《網絡編程懶人入門(九):通俗講解,有了IP地址,爲什麼還要用MAC地址?》
《現代移動端網絡短鏈接的優化手段總結:請求速度、弱網適應、安全保障》
《移動端IM開發者必讀(一):通俗易懂,理解移動網絡的「弱」和「慢」》
《移動端IM開發者必讀(二):史上最全移動弱網絡優化方法總結》
《從HTTP/0.9到HTTP/2:一文讀懂HTTP協議的歷史演變和設計思路》
《腦殘式網絡編程入門(一):跟着動畫來學TCP三次握手和四次揮手》
《腦殘式網絡編程入門(二):咱們在讀寫Socket時,究竟在讀寫什麼?》
《腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識》
《腦殘式網絡編程入門(四):快速理解HTTP/2的服務器推送(Server Push)》
《腦殘式網絡編程入門(五):天天都在用的Ping命令,它究竟是什麼?》
《腦殘式網絡編程入門(六):什麼是公網IP和內網IP?NAT轉換又是什麼鬼?》
>> 更多同類文章 ……
[3] 有關推送技術方面的文章:
《iOS的推送服務APNs詳解:設計思路、技術原理及缺陷等》
《信鴿團隊原創:一塊兒走過 iOS10 上消息推送(APNS)的坑》
《Android端消息推送總結:實現原理、心跳保活、遇到的問題等》
《掃盲貼:認識MQTT通訊協議》
《一個基於MQTT通訊協議的完整Android推送Demo》
《IBM技術經理訪談:MQTT協議的制定歷程、發展示狀等》
《求教android消息推送:GCM、XMPP、MQTT三種方案的優劣》
《移動端實時消息推送技術淺析》
《掃盲貼:淺談iOS和Android後臺實時消息推送的原理和區別》
《絕對乾貨:基於Netty實現海量接入的推送服務技術要點》
《移動端IM實踐:谷歌消息推送服務(GCM)研究(來自微信)》
《爲什麼微信、QQ這樣的IM工具不使用GCM服務推送消息?》
《極光推送系統大規模高併發架構的技術實踐分享》
《從HTTP到MQTT:一個基於位置服務的APP數據通訊實踐概述》
《魅族2500萬長鏈接的實時消息推送架構的技術實踐分享》
《專訪魅族架構師:海量長鏈接的實時消息推送系統的心得體會》
《深刻的聊聊Android消息推送這件小事》
《基於WebSocket實現Hybrid移動應用的消息推送實踐(含代碼示例)》
《一個基於長鏈接的安全可擴展的訂閱/推送服務實現思路》
《實踐分享:如何構建一套高可用的移動端消息推送系統?》
《Go語言構建千萬級在線的高併發消息推送系統實踐(來自360公司)》
《騰訊信鴿技術分享:百億級實時消息推送的實戰經驗》
《百萬在線的美拍直播彈幕系統的實時推送技術實踐之路》
《京東京麥商家開放平臺的消息推送架構演進之路》
《瞭解iOS消息推送一文就夠:史上最全iOS Push技術詳解》
《基於APNs最新HTTP/2接口實現iOS的高性能消息推送(服務端篇)》
《解密「達達-京東到家」的訂單即時派發技術原理和實踐》
《技術乾貨:從零開始,教你設計一個百萬級的消息推送系統》
>> 更多同類文章 ……片的帶寬壓縮技術演進之路》《小白必讀:閒話HTTP短鏈接中的Session和Token》
《IM開發基礎知識補課:正確理解前置HTTP SSO單點登錄接口的原理》
《IM消息送達保證機制實現(一):保證在線實時消息的可靠投遞》
[2] 一些網絡編程基礎資料:
《技術往事:改變世界的TCP/IP協議(珍貴多圖、手機慎點)》
《通俗易懂-深刻理解TCP協議(下):RTT、滑動窗口、擁塞處理》
《高性能網絡編程(一):單臺服務器併發TCP鏈接數到底能夠有多少》
《高性能網絡編程(二):上一個10年,著名的C10K併發鏈接問題》
《高性能網絡編程(三):下一個10年,是時候考慮C10M併發問題了》
《高性能網絡編程(四):從C10K到C10M高性能網絡應用的理論探索》
《高性能網絡編程(五):一文讀懂高性能網絡編程中的I/O模型》
《高性能網絡編程(六):一文讀懂高性能網絡編程中的線程模型》
《鮮爲人知的網絡編程(一):淺析TCP協議中的疑難雜症(上篇)》
《鮮爲人知的網絡編程(二):淺析TCP協議中的疑難雜症(下篇)》
《鮮爲人知的網絡編程(三):關閉TCP鏈接時爲何會TIME_WAIT、CLOSE_WAIT》
《鮮爲人知的網絡編程(七):如何讓不可靠的UDP變的可靠?》
《鮮爲人知的網絡編程(九):理論聯繫實際,全方位深刻理解DNS》
《網絡編程懶人入門(五):快速理解爲何說UDP有時比TCP更有優點》
《網絡編程懶人入門(六):史上最通俗的集線器、交換機、路由器功能原理入門》
《網絡編程懶人入門(八):手把手教你寫基於TCP的Socket長鏈接》
《網絡編程懶人入門(九):通俗講解,有了IP地址,爲什麼還要用MAC地址?》
《現代移動端網絡短鏈接的優化手段總結:請求速度、弱網適應、安全保障》
《移動端IM開發者必讀(一):通俗易懂,理解移動網絡的「弱」和「慢」》
《移動端IM開發者必讀(二):史上最全移動弱網絡優化方法總結》
《從HTTP/0.9到HTTP/2:一文讀懂HTTP協議的歷史演變和設計思路》
《腦殘式網絡編程入門(一):跟着動畫來學TCP三次握手和四次揮手》
《腦殘式網絡編程入門(二):咱們在讀寫Socket時,究竟在讀寫什麼?》
《腦殘式網絡編程入門(三):HTTP協議必知必會的一些知識》
《腦殘式網絡編程入門(四):快速理解HTTP/2的服務器推送(Server Push)》
《腦殘式網絡編程入門(五):天天都在用的Ping命令,它究竟是什麼?》
《腦殘式網絡編程入門(六):什麼是公網IP和內網IP?NAT轉換又是什麼鬼?》
>> 更多同類文章 ……
[3] 有關推送技術方面的文章:
《iOS的推送服務APNs詳解:設計思路、技術原理及缺陷等》
《信鴿團隊原創:一塊兒走過 iOS10 上消息推送(APNS)的坑》
《Android端消息推送總結:實現原理、心跳保活、遇到的問題等》
《一個基於MQTT通訊協議的完整Android推送Demo》
《求教android消息推送:GCM、XMPP、MQTT三種方案的優劣》
《掃盲貼:淺談iOS和Android後臺實時消息推送的原理和區別》
《移動端IM實踐:谷歌消息推送服務(GCM)研究(來自微信)》
《爲什麼微信、QQ這樣的IM工具不使用GCM服務推送消息?》
《從HTTP到MQTT:一個基於位置服務的APP數據通訊實踐概述》
《基於WebSocket實現Hybrid移動應用的消息推送實踐(含代碼示例)》
《Go語言構建千萬級在線的高併發消息推送系統實踐(來自360公司)》
《瞭解iOS消息推送一文就夠:史上最全iOS Push技術詳解》
《基於APNs最新HTTP/2接口實現iOS的高性能消息推送(服務端篇)》
>> 更多同類文章 ……
(本文同步發佈於:http://www.52im.net/thread-2744-1-1.html)