IM消息送達保證機制實現(二):保證離線消息的可靠投遞 頂 原 薦

1、前言

本文的上篇《IM消息送達保證機制實現(一):保證在線實時消息的可靠投遞》中,我們討論了在線實時消息的投遞可以通過應用層的確認、發送方的超時重傳、接收方的去重等手段來保證業務層面消息的不丟不重。

但實時在線投遞針對的是消息收發雙方都在線的情況(如當發送方用戶A發送消息給接收方用戶B時,用戶B是在線的),那如果消息的接收方用戶B不在線,系統是如何保證消息的可達性的呢?這就是本文要討論的問題。(本文同步發佈於:http://www.52im.net/thread-594-1-1.html

2、學習交流 

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

- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM

3、IM消息送達保證系列文章

本文是討論IM消息送達保證系列文章中的第2篇,總目錄如下:

另外,如果您正在查閱移動端IM開發資料,推薦閱讀《新手入門一篇就夠:從零開發移動端IM》。

4、消息接收方不在線時的典型消息發送流程

 

如上圖所述,通常此類情況下消息的發送流程如下:

  • Step 1:用戶A發送一條消息給用戶B;
  • Step 2:服務器查看用戶B的狀態,發現B的狀態爲“offline”(即B當前不在線);
  • Step 3:服務器將此條消息以離線消息的形式持久化存儲到DB中(當然,具體的持久化方案可由您IM的具體技術實現爲準);
  • Step 4:服務器返回用戶A“發送成功”ACK確認包(注:對於消息發送方而言,消息一旦落地存儲至DB就認爲是發送成功了)。

關於 “Step 4” 的補充說明:

請一定要理解“Step 4”,因爲現在無論是傳統的PC端IM(類似QQ這樣的——可以在UI上看到好友的在線、離線狀態)還是目前主流的移動端IM(強調的是用戶全時在線——即你看不到好友到底在線還是離線,反正給你的假像就是這個好友“應該”是在線的),消息發送出去後,無論是對方實時在線收到還是對方不在線而被服務端離線存儲了,對於發送方而言只要消息沒有因爲網絡等原因莫名消失,就應該認爲是“被收到了”。

從技術的角度講,消息接收方收到的消息應答ACK包的真正發起者,實際上有兩種可能性:一種是由接收方發出、而另一種是由服務端代爲發送(這在MobileIMSDK開源工程裏被稱作“僞應答”)。

5、典型離線消息表的設計以及拉取離線消息的過程

① 存儲離線消看書的表主要字段大致如下:

-- 消息接收者ID
receiver_uid varchar(50),

-- 消息的唯一指紋碼(即消息ID),用於去重等場景,單機情況下此id可能是個自增值、分佈式場景下可能是類似於UUID這樣的東西
msg_id varchar(70),

-- 消息發出時的時間戳(如果是個跨國IM,則此時間戳可能是GMT-0標準時間)      
send_time time,

-- 消息發送者ID
sender_uid varchar(50),

-- 消息類型(標識此條消息是:文本、圖片還是語音留言等)
msg_type int,

-- 消息內容(如果是圖片或語音留言等類型,由此字段存放的可能是對應文件的存儲地址或CDN的訪問URL)
msg_content varchar(1024),

…


② 離線消息拉取模式:
接收方B要拉取發送方A給ta發送的離線消息,只需在receiver_uid(即接收方B的用戶ID), sender_uid(即發送方A的用戶ID)上查詢,然後把離線消息刪除,再把消息返回B即可。

③ 離線消息的拉取,如果用SQL語句來描述的話,它可以是:

SELECT msg_id, send_time, msg_type, msg_content 
FROM offline_msgs
WHERE receiver_uid = ? and sender_uid = ?

 

④ 離線拉取的整體流程如下圖所示:

  • Stelp 1:用戶B開始拉取用戶A發送給ta的離線消息;
  • Stelp 2:服務器從DB(或對應的持久化容器)中拉取離線消息;
  • Stelp 3:服務器從DB(或對應的持久化容器)中把離線消息刪除;
  • Stelp 4:服務器返回給用戶B想要的離線消息。


 

6、上述流程存在的問題以及優化方案

如果用戶B有很多好友,登陸時客戶端需要對所有好友進行離線消息拉取,客戶端與服務器交互次數就會比較多。

① 拉取好友離線消息的客戶端僞代碼:

// 登陸時所有好友都要拉取
for(all uid in B’s friend-list){ 
     // 與服務器交互
     get_offline_msg(B,uid);  
}


② 優化方案1:
先拉取各個好友的離線消息數量,真正用戶B進去看離線消息時,才往服務器發送拉取請求(手機端爲了節省流量,經常會使用這個按需拉取的優化)。

③ 優化方案2:
如下圖所示,一次性拉取所有好友發送給用戶B的離線消息,到客戶端本地再根據sender_uid進行計算,這樣的話,離校消息表的訪問模式就變爲->只需要按照receiver_uid來查詢了。登錄時與服務器的交互次數降低爲了1次。

 

④ 方案小結:
通常情況下,主流的的移動端IM(比如微信、手Q等)通常都是以“優化方案2”爲主,因爲移動網絡的不可靠性加上電量、流量等資源的昂貴性,能儘量一次性幹完的事,就儘可能一次搞定,從而提供整個APP的用戶體驗(對於移動端應用而言,省電、省流量同樣是用戶體驗的一部分)。這方面的文章,可以進一步參閱《談談移動端 IM 開發中登錄請求的優化》、《移動端IM實踐:iOS版微信界面卡頓監測方案》、《移動端IM實踐:Android版微信如何大幅提升交互性能(二)》。

7、消息接收方一次拉取大量離線消息導致速度慢、卡頓的解決方法

用戶B一次性拉取所有好友發給ta的離線消息,消息量很大時,一個請求包很大、速度慢,容易卡頓怎麼辦?

 

正如上圖所示,我們可以分頁拉取:根據業務需求,先拉取最新(或者最舊)的一頁消息,再按需一頁頁拉取,這樣便能很好地解決用戶體驗問題。

8、優化離線消息的拉取過程,保證離線消息不會丟失

如何保證可達性,上述步驟第三步執行完畢之後,第四個步驟離線消息返回給客戶端過程中,服務器掛點,路由器丟消息,或者客戶端crash了,那離線消息豈不是丟了麼(數據庫已刪除,用戶還沒收到)?

確實,如果按照上述的1、2、3、4步流程,的確是的,那如何保證離線消息的絕對可靠性、可達性?

 

如同在線消息的應用層ACK機制一樣,離線消息拉時,不能夠直接刪除數據庫中的離線消息,而必須等應用層的離線消息ACK(說明用戶B真的收到離線消息了),才能刪除數據庫中的離線消息。這個應用層的ACK可以通過實時消息通道告之服務端,也可以通過服務端提供的REST接口,以更通用、簡單的方式通知服務端。

9、進一步優化,解決重複拉取離線消息的問題

如果用戶B拉取了一頁離線消息,卻在ACK之前crash了,下次登錄時會拉取到重複的離線消息麼?

確實,拉取了離線消息卻沒有ACK,服務器不會刪除之前的離線消息,故下次登錄時系統層面還會拉取到。但在業務層面,可以根據msg_id去重。SMC理論:系統層面無法做到消息不丟不重,業務層面可以做到,對用戶無感知。

優化後的拉取過程,如下圖所示:
 

10、進一步優化,降低離線拉取ACK帶來的額外與服務器的交互次數

假設有N頁離線消息,現在每個離線消息需要一個ACK,那麼豈不是客戶端與服務器的交互次數又加倍了?有沒有優化空間?

 

如上圖所示,不用每一頁消息都ACK,在拉取第二頁消息時相當於第一頁消息的ACK,此時服務器再刪除第一頁的離線消息即可,最後一頁消息再ACK一次(實際上:最後一頁拉取的肯定是空返回,這樣可以極大地簡化這個分頁過程,否則客戶端得知道當前離線消息的總頁數,而由於消息讀取延遲的存在,這個總頁數理論上並非絕對不變,從而加大了數據讀取不一致的可能性)。這樣的效果是,不管拉取多少頁離線消息,只會多一個ACK請求,與服務器多一次交互。

11、本文小結

正如本文中所列舉的問題所描述的那樣,保證“離線消息”的可達性比大家想象的要複雜一些,常見優化總結如下:

  • 1)對於同一個用戶B,一次性拉取所有用戶發給ta的離線消息,再在客戶端本地進行發送方分析,相比按照發送方一個個進行消息拉取,能大大減少服務器交互次數;
  • 2)分頁拉取,先拉取計數再按需拉取,是無線端的常見優化;
  • 3)應用層的ACK,應用層的去重,才能保證離線消息的不丟不重;
  • 4)下一頁的拉取,同時作爲上一頁的ACK,能夠極大減少與服務器的交互次數。

(本文同步發佈於:http://www.52im.net/thread-594-1-1.html,本文內容參考了:微信爲啥不丟“離線消息”

12、IM技術資料分類

[1] 網絡編程基礎資料:
TCP/IP詳解 - 第11章·UDP:用戶數據報協議
TCP/IP詳解 - 第17章·TCP:傳輸控制協議
TCP/IP詳解 - 第18章·TCP連接的建立與終止
TCP/IP詳解 - 第21章·TCP的超時與重傳
技術往事:改變世界的TCP/IP協議(珍貴多圖、手機慎點)
通俗易懂-深入理解TCP協議(上):理論基礎
通俗易懂-深入理解TCP協議(下):RTT、滑動窗口、擁塞處理
理論經典:TCP協議的3次握手與4次揮手過程詳解
理論聯繫實際:Wireshark抓包分析TCP 3次握手、4次揮手過程
計算機網絡通訊協議關係圖(中文珍藏版)
UDP中一個包的大小最大能多大?
Java新一代網絡編程模型AIO原理及Linux系統AIO介紹
NIO框架入門(一):服務端基於Netty4的UDP雙向通信Demo演示
NIO框架入門(二):服務端基於MINA2的UDP雙向通信Demo演示
NIO框架入門(三):iOS與MINA2、Netty4的跨平臺UDP雙向通信實戰
NIO框架入門(四):Android與MINA2、Netty4的跨平臺UDP雙向通信實戰
P2P技術詳解(一):NAT詳解——詳細原理、P2P簡介
P2P技術詳解(二):P2P中的NAT穿越(打洞)方案詳解
P2P技術詳解(三):P2P技術之STUN、TURN、ICE詳解
高性能網絡編程(一):單臺服務器併發TCP連接數到底可以有多少
高性能網絡編程(二):上一個10年,著名的C10K併發連接問題
高性能網絡編程(三):下一個10年,是時候考慮C10M併發問題了
高性能網絡編程(四):從C10K到C10M高性能網絡應用的理論探索
>> 更多同類文章 ……

[2] 有關IM/推送的通信格式、協議的選擇:
爲什麼QQ用的是UDP協議而不是TCP協議?
移動端即時通訊協議選擇:UDP還是TCP?
如何選擇即時通訊應用的數據傳輸格式
強列建議將Protobuf作爲你的即時通訊應用數據傳輸格式
移動端IM開發需要面對的技術問題(含通信協議選擇)
簡述移動端IM開發的那些坑:架構設計、通信協議和客戶端
理論聯繫實際:一套典型的IM通信協議設計詳解
58到家實時消息系統的協議設計等技術實踐分享
>> 更多同類文章 ……

[3] 有關IM/推送的心跳保活處理:
Android進程保活詳解:一篇文章解決你的所有疑問
Android端消息推送總結:實現原理、心跳保活、遇到的問題等
爲何基於TCP協議的移動端IM仍然需要心跳保活機制?
微信團隊原創分享:Android版微信後臺保活實戰分享(進程保活篇)
微信團隊原創分享:Android版微信後臺保活實戰分享(網絡保活篇)
移動端IM實踐:實現Android版微信的智能心跳機制
移動端IM實踐:WhatsApp、Line、微信的心跳策略分析
>> 更多同類文章 ……

[4] 有關WEB端即時通訊開發:
新手入門貼:史上最全Web端即時通訊技術原理詳解
Web端即時通訊技術盤點:短輪詢、Comet、Websocket、SSE
SSE技術詳解:一種全新的HTML5服務器推送事件技術
Comet技術詳解:基於HTTP長連接的Web端實時通信技術
WebSocket詳解(一):初步認識WebSocket技術
socket.io實現消息推送的一點實踐及思路
>> 更多同類文章 ……

[5] 有關IM架構設計:
淺談IM系統的架構設計
簡述移動端IM開發的那些坑:架構設計、通信協議和客戶端
一套原創分佈式即時通訊(IM)系統理論架構方案
從零到卓越:京東客服即時通訊系統的技術架構演進歷程
蘑菇街即時通訊/IM服務器開發之架構選擇
騰訊QQ1.4億在線用戶的技術挑戰和架構演進之路PPT
微信技術總監談架構:微信之道——大道至簡(演講全文)
如何解讀《微信技術總監談架構:微信之道——大道至簡》
快速裂變:見證微信強大後臺架構從0到1的演進歷程(一)
17年的實踐:騰訊海量產品的技術方法論
>> 更多同類文章 ……

[6] 有關IM安全的文章:
即時通訊安全篇(一):正確地理解和使用Android端加密算法
即時通訊安全篇(二):探討組合加密算法在IM中的應用
即時通訊安全篇(三):常用加解密算法與通訊安全講解
即時通訊安全篇(四):實例分析Android中密鑰硬編碼的風險
傳輸層安全協議SSL/TLS的Java平臺實現簡介和Demo演示
理論聯繫實際:一套典型的IM通信協議設計詳解(含安全層設計)
微信新一代通信安全解決方案:基於TLS1.3的MMTLS詳解
來自阿里OpenIM:打造安全可靠即時通訊服務的技術實踐分享
>> 更多同類文章 ……

[7] 有關實時音視頻開發:
即時通訊音視頻開發(一):視頻編解碼之理論概述
即時通訊音視頻開發(二):視頻編解碼之數字視頻介紹
即時通訊音視頻開發(三):視頻編解碼之編碼基礎
即時通訊音視頻開發(四):視頻編解碼之預測技術介紹
即時通訊音視頻開發(五):認識主流視頻編碼技術H.264
即時通訊音視頻開發(六):如何開始音頻編解碼技術的學習
即時通訊音視頻開發(七):音頻基礎及編碼原理入門
即時通訊音視頻開發(八):常見的實時語音通訊編碼標準
即時通訊音視頻開發(九):實時語音通訊的迴音及迴音消除概述
即時通訊音視頻開發(十):實時語音通訊的迴音消除技術詳解
即時通訊音視頻開發(十一):實時語音通訊丟包補償技術詳解
即時通訊音視頻開發(十二):多人實時音視頻聊天架構探討
即時通訊音視頻開發(十三):實時視頻編碼H.264的特點與優勢
即時通訊音視頻開發(十四):實時音視頻數據傳輸協議介紹
即時通訊音視頻開發(十五):聊聊P2P與實時音視頻的應用情況
即時通訊音視頻開發(十六):移動端實時音視頻開發的幾個建議
即時通訊音視頻開發(十七):視頻編碼H.264、V8的前世今生
學習RFC3550:RTP/RTCP實時傳輸協議基礎知識
簡述開源實時音視頻技術WebRTC的優缺點
良心分享:WebRTC 零基礎開發者教程(中文)
開源實時音視頻技術WebRTC中RTP/RTCP數據傳輸協議的應用
基於RTMP數據傳輸協議的實時流媒體技術研究(論文全文)
聲網架構師談實時音視頻雲的實現難點(視頻採訪)
淺談開發實時視頻直播平臺的技術要點
還在靠“喂喂喂”測試實時語音通話質量?本文教你科學的評測方法!
實現延遲低於500毫秒的1080P實時音視頻直播的實踐分享
移動端實時視頻直播技術實踐:如何做到實時秒開、流暢不卡
如何用最簡單的方法測試你的實時音視頻方案
技術揭祕:支持百萬級粉絲互動的Facebook實時視頻直播
>> 更多同類文章 ……

[8] IM開發綜合文章:
移動端IM開發需要面對的技術問題
開發IM是自己設計協議用字節流好還是字符流好?
請問有人知道語音留言聊天的主流實現方式嗎?
IM消息送達保證機制實現(一):保證在線實時消息的可靠投遞
IM消息送達保證機制實現(二):保證離線消息的可靠投遞
談談移動端 IM 開發中登錄請求的優化
完全自已開發的IM該如何設計“失敗重試”機制?
微信對網絡影響的技術試驗及分析(論文全文)
即時通訊系統的原理、技術和應用(技術論文)
開源IM工程“蘑菇街TeamTalk”的現狀:一場有始無終的開源秀
>> 更多同類文章 …… 

[9] 開源移動端IM技術框架資料:
開源移動端IM技術框架MobileIMSDK:快速入門
開源移動端IM技術框架MobileIMSDK:常見問題解答
開源移動端IM技術框架MobileIMSDK:壓力測試報告
>> 更多同類文章 ……

[10] 有關推送技術的文章:
iOS的推送服務APNs詳解:設計思路、技術原理及缺陷等
Android端消息推送總結:實現原理、心跳保活、遇到的問題等
掃盲貼:認識MQTT通信協議
一個基於MQTT通信協議的完整Android推送Demo
IBM技術經理訪談:MQTT協議的制定歷程、發展現狀等
求教android消息推送:GCM、XMPP、MQTT三種方案的優劣
移動端實時消息推送技術淺析
掃盲貼:淺談iOS和Android後臺實時消息推送的原理和區別
絕對乾貨:基於Netty實現海量接入的推送服務技術要點
移動端IM實踐:谷歌消息推送服務(GCM)研究(來自微信)
爲何微信、QQ這樣的IM工具不使用GCM服務推送消息?
>> 更多同類文章 ……

[11] 更多即時通訊技術好文分類:
http://www.52im.net/forum.php?mod=collection&op=all

作者:Jack Jiang (點擊作者姓名進入Github) 
出處:http://www.52im.net/space-uid-1.html 
交流:歡迎加入即時通訊開發交流羣 215891622 
討論:http://www.52im.net/ 
Jack Jiang同時是【原創開源Java Swing外觀工程BeautyEye】【開源輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章