Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 499, comments - 13, trackbacks - 0, articles - 1

          本文編寫時引用了“聊聊IM系統的即時性和可靠性”一文的部分內容和圖片,感謝原作者。

          1、引言

          上一篇《零基礎IM開發入門(二):什么是IM系統的實時性?》講到了IM系統的“立足”之本——“實時性”這個技術特征,本篇主要講解IM系統中的“可靠性”這個話題,內容盡量做到只講原理不深入展開,避開深層次的技術性探討,確保通俗易懂。


          閱讀對象:本系列文章主要閱讀對象為零IM基礎的開發者或產品經理,目標是告訴你“IM系統是什么?”,盡量不深入探討具體的技術實現,確保通俗易懂,老少皆宜。

          如您想從技術維度系統學習IM技術并著手自已的IM開發(即解決“IM系統要怎么做?”這個疑問),請從此文開始:《新手入門一篇就夠:從零開發移動端IM》。

          學習交流:

          - 即時通訊/推送技術開發交流5群:215477170[推薦]

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

          - 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK

          (本文同步發布于:http://www.52im.net/thread-3182-1-1.html

          2、系列文章

          零基礎IM開發入門(一):什么是IM系統?

          零基礎IM開發入門(二):什么是IM系統的實時性?

          零基礎IM開發入門(三):什么是IM系統的可靠性?》(* 本文

          零基礎IM開發入門(四):什么是IM系統的消息時序一致性?

          《零基礎IM開發入門(五):什么是IM系統的安全性? (稍后發布)》

          《零基礎IM開發入門(六):什么是IM系統的的心跳機制? (稍后發布)》

          《零基礎IM開發入門(七):如何理解并實現IM系統消息未讀數? (稍后發布)》

          《零基礎IM開發入門(八):如何理解并實現IM系統的多端消息漫游? (稍后發布)》

          3、正文概述

          一般來說,IM系統的消息“可靠性”,通常就是指聊天消息投遞的可靠性(準確的說,這個“消息”是廣義的,因為還存用戶看不見的各種指令,為了通俗,統稱“消息”)。

          從用戶行為來講,消息“可靠性”應該分為兩種類型:

          • 1)在線消息的可靠性:即發送消息時,接收方當前處于“在線”狀態;
          • 2)離線消息的可靠性:即發送消息時,接收方當前處于“離線”狀態。

          從具體的技術表現來講,消息“可靠性”包含兩層含義:

          • 1)消息不丟:這很直白,發出去的消息不能像進了黑洞一樣,一臉懵逼可不行;
          • 2)消息不重:這是丟消息的反面,消息重復了也不能容忍。

          對于“消息不丟”這個特征來說,細化下來,它又包含兩重含義:

          • 1)已明確被對方收到;
          • 2)已明確未被對方收到。

          是的,對于第1)重含義好理解,第2)重含義的意思是:當對方沒有成功收到時,你的im系統也必須要感知到,否則,它同樣屬于被“丟”范疇。

          總之,一個成型的im系統,必須包含這兩種消息“可靠性”邏輯,才能堪用,缺一不可。

          消息的可靠性(不丟失、不重復)無疑是IM系統的重要指標,也是IM系統實現中的難點之一。本文以下文字,將從在線消息的可靠性和離線消息的可靠性進行討論。

          4、典型的在線消息收發流程

          先看下面這張典型的im消息收發流程: 

          是的,這是一個典型的服務端中轉型IM架構。

          所謂“服務端中轉型IM架構”是指:一條消息從客戶端A發出后,需要先經過 IM 服務器來進行中轉,然后再由 IM 服務器推送給客戶端B,這種模式也是目前最常見的 IM 系統的消息分發架構。

          你可能會說,IM不可以是P2P模式的嗎?是的,目前來說主流IM基本都是服務器中轉這種方式,P2P模式在IM系統中用的很少。

          原因是以下兩個很明顯的弊端:

          • 1)P2P模式下,IM運營者很容易被用戶架空(無法監管到用戶行為,用戶涉黃了怕不怕?);
          • 2)P2P模式下,群聊這種業務形態,很難實現(我要在千人群中發消息給,不可能我自已來分發1000次吧)。

          話題有點跑偏,我們回到正題:在上面這張圖里,客戶A發送消息到服務端、服務端中轉消息給客戶B,假設這兩條數據鏈接中使用的通信協議是TCP,你認為在TCP所謂可靠傳輸協議加持下,真的能保證IM聊天消息的可靠性嗎?

          答案是否定的。我們繼續看下節。

          5、TCP并不能保證在線消息的“可靠性”

          接上節,在一個典型的服務端中轉型IM架構中,即使使用“可靠的傳輸協議”TCP,也不能保證聊天消息的可靠性。為什么這么說?

          要回答這個問題,網上的很多文章,都會從服務端的角度舉例:比如消息發送時操作系統崩潰、網絡閃斷、存儲故障等等,總之很抽象,不太容易理解。

          這次我們從客戶端角度來理解,為什么使用了可靠傳輸協議TCP的情況下IM聊天消息仍然不可靠的問題。

          具體來說:如何確保 IM 消息的可靠性是個相對復雜的話題,從客戶端發送數據到服務器,再從服務器送達目標客戶端,最終在 UI 成功展示,其間涉及的環節很多,這里只取其中一環「接收端如何確保消息不丟失」來探討,粗略聊下我接觸過的兩種設計思路。

          說到可靠送達:第一反應會聯想到 TCP 的可靠性。數據的可靠送達是個通用性的問題,無論是網絡二進制流數據,還是上層的業務數據,都有可靠性保障問題,TCP 作為網絡基礎設施協議,其可靠性設計的可靠性是毋庸置疑的,我們就從 TCP 的可靠性說起。

          在 TCP 這一層:所有 Sender 發送的數據,每一個 byte 都有標號(Sequence Number),每個 byte 在抵達接收端之后都會被接收端返回一個確認信息(Ack Number), 二者關系為 Ack = Seq + 1。簡單來說,如果 Sender 發送一個 Seq = 1,長度為 100 bytes 的包,那么 receiver 會返回一個 Ack = 101 的包,如果 Sender 收到了這個Ack 包,說明數據確實被 Receiver 收到了,否則 Sender 會采取某種策略重發上面的包。

          第一個問題是:既然 TCP 本身是具備可靠性的,為什么還會出現消息接收端(Receiver)丟失消息的情況?

          看下圖一目了然:

          ▲ 上圖引用自《從客戶端的角度來談談移動端IM的消息可靠性和送達機制

          一句話總結上圖的含義:網絡層的可靠性不等同于業務層的可靠性。

          數據可靠抵達網絡層之后,還需要一層層往上移交處理,可能的處理有:

          • 1)安全性校驗;
          • 2)binary 解析;
          • 3)model 創建;
          • 4)寫 db;
          • 5)存入 cache;
          • 6)UI 展示;
          • 7)以及一些邊界問題:比如斷網、用戶突然退出登陸、磁盤已滿、內存溢出、app奔潰、突然關機等等。

          項目的功能特性越多,網絡層往上的處理出錯的可能性就越大。

          舉個最簡單的場景為例子:消息可靠抵達網絡層之后,寫 db 之前 IM APP 崩潰(不稀奇,是 App 都有崩潰的可能),雖然數據在網絡層可靠抵達了,但沒存進 db,下次用戶打開 App 消息自然就丟失了,如果不在業務層再增加可靠性保障(比如:后面要提到的網絡層面的消息重發保障),那么意味著這條消息對于接收端來說就永遠丟失了,也就自然不存在“可靠性”了。

          從客戶端角度理解IM的可能性以及解決辦法,可以詳細閱讀:從客戶端的角度來談談移動端IM的消息可靠性和送達機制》,本節引用的是該文中“4、TCP協議的可靠性之外還會出現消息丟失?”一節的文字。

          6、為在線消息增加“可靠性”保障

          那么怎樣在應用層增加可靠性保障呢?

          有一個現成的機制可供我們借鑒:TCP協議的超時、重傳、確認機制。

          具體來說就是:

          • 1)在應用層構造一種ACK消息,當接收方正確處理完消息后,向發送方發送ACK;
          • 2)假如發送方在超時時間內沒有收到ACK,則認為消息發送失敗,需要進行重傳或其他處理。

          增加了確認機制的消息收發過程如下: 

          我們可以把整個過程分為兩個階段。

          階段1:clientA -> server

          • 1-1:clientA向server發送消息(msg-Req);
          • 1-2:server收取消息,回復ACK(msg-Ack)給clientA;
          • 1-3:一旦clientA收到ACK即可認為消息已成功投遞,第一階段結束。

          無論msg-A或ack-A丟失,clientA均無法在超時時間內收到ACK,此時可以提示用戶發送失敗,手動進行重發。

          階段2:server -> clientB

          • 2-1:server向clientB發送消息(Notify-Req);
          • 2-2:clientB收取消息,回復ACK(Notify-ACk)給server;
          • 2-3:server收到ACK之后將該消息標記為已發送,第二階段結束。

          無論msg-B或ack-B丟失,server均無法在超時時間內收到ACK,此時需要重發msg-B,直到clientB返回ACK為止。

          關于IM聊天消息的可靠性保障問的深入討論,可以詳讀:IM消息送達保證機制實現(一):保證在線實時消息的可靠投遞》,該文會深入討論這個話題。

          7、典型的離線消息收發流程

          說完在線消息的“可靠性”問題,我們該了解一下離線消息了。

          7.1 離線消息的收發也存在“不可靠性”

          下圖是一張典型的IM離線消息流程圖:

          如上圖所示,和在線消息收發流程類似。

          離線消息收發流程也可劃分為兩個階段:

          階段1:clientA -> server

          • 1-1:clientA向server發送消息(msg-Req) ;
          • 1-2:server發現clientB離線,將消息存入offline-DB。

          階段2:server -> clientB

          • 2-1:clientB上線后向server拉取離線消息(pull-Req) ;
          • 2-2:server從offline-DB檢索相應的離線消息推送給clientB(pull-res),并從offline-DB中刪除。

          顯然:離線消息收發過程同樣存在消息丟失的可能性。

          舉例來說:假設pull-res沒有成功送達clientB,而offline-DB中已刪除,這部分離線消息就徹底丟失了。

          7.2 離線消息的“可靠性”保障

          與在線消息收發流程類似,我們同樣需要在應用層增加可靠性保障機制。

          下圖是增加了可靠性保障后的離線消息收發流程: 

          與初始的離線消息收發流程相比,上圖增加了1-3、2-4、2-5步驟:

          • 1-3:server將消息存入offline-DB后,回復ACK(msg-Ack)給clientA,clientA收到ACK即可認為消息投遞成功;
          • 2-4:clientB收到推送的離線消息,回復ACK(res-Ack)給server;
          • 2-5:server收到res-ACk后確定離線消息已被clientB成功收取,此時才能從offline-DB中刪除。

          當然,上述的保障機制,還存在性能優化空間。

          當離線消息的量較大時:如果對每條消息都回復ACK,無疑會大大增加客戶端與服務器的通信次數。這種情況我們通常使用批量ACK的方式,對多條消息僅回復一個ACK。在某此后IM的實現中是將所有的離線消息按會話進行分組,每組回復一個ACK,假如某個ACK丟失,則只需要重傳該會話的所有離線消息。

          有關離線消息的可靠性保障機制的詳細討論,可以詳讀:IM消息送達保證機制實現(二):保證離線消息的可靠投遞》、《IM開發干貨分享:如何優雅的實現大量離線消息的可靠投遞》,這兩篇文章可以給你更深入具體的答案。

          8、聊天消息重復的問題

          上面章節中,通過在應用層加入重傳、確認機制后,我們確實是杜絕了消息丟失的可能性。

          但由于重試機制的存在,我們會遇到一個新的問題:那就是同一條消息可能被重復發送。

          舉一個最簡單的例子:假設client成功收到了server推送的消息,但其后續發送的ACK丟失了,那么server將會在超時后再次推送該消息,如果業務層不對重復消息進行處理,那么用戶就會看到兩條完全一樣的消息。

          消息去重的方式其實非常簡單,一般是根據消息的唯一標志(id)進行過濾。

          具體過程在服務端和客戶端可能有所不同:

          • 1)客戶端 :我們可以通過構造一個map來維護已接收消息的id,當收到id重復的消息時直接丟棄;
          • 2)服務端 :收到消息時根據id去數據庫查詢,若庫中已存在則不進行處理,但仍然需要向客戶端回復Ack(因為這條消息很可能來自用戶的手動重發)。

          關于消息的去重問題,在一對一聊天的情況下,邏輯并不復雜,但在群聊模式下,會將問題復雜化,有關群聊消息不丟和去重的詳細討論,可以深入閱讀:《IM群聊消息如此復雜,如何保證不丟不重?》。

          9、本文小結

          保證消息的可靠性是IM系統設計中很重要的一環,能不能做到“消息不丟”、“消息不重”,對用戶的體驗影響極大。

          所謂“可靠的傳輸協議”TCP也并不能保障消息在應用層的可靠性。

          我們一般通過在應用層的ACK應答和重傳機制,來實現IM消息的可靠性保障。但由此帶來的消息重復問題,需要我們額外進行處理,最簡單的方法就是通過消息ID進行冪等去重。

          關于IM系統消息可靠性的理論基礎,我們就探討到這里,有疑問的讀者,可以在本文末尾留意,歡迎積極討論。

          10、參考資料

          [1] IM消息送達保證機制實現(一):保證在線實時消息的可靠投遞

          [2] IM消息送達保證機制實現(二):保證離線消息的可靠投遞

          [3] IM開發干貨分享:如何優雅的實現大量離線消息的可靠投遞

          [4] 從客戶端的角度來談談移動端IM的消息可靠性和送達機制

          [5] 聊聊IM系統的即時性和可靠性

          [6] 學習筆記4——IM系統如何保證消息的可靠性

          [7] IM群聊消息如此復雜,如何保證不丟不重?

          附錄:更多IM開發熱門技術點

          移動端IM開發者必讀(一):通俗易懂,理解移動網絡的“弱”和“慢”

          移動端IM開發者必讀(二):史上最全移動弱網絡優化方法總結

          現代移動端網絡短連接的優化手段總結:請求速度、弱網適應、安全保障

          移動端IM中大規模群消息的推送如何保證效率、實時性?

          移動端IM開發需要面對的技術問題

          開發IM是自己設計協議用字節流好還是字符流好?

          請問有人知道語音留言聊天的主流實現方式嗎?

          如何保證IM實時消息的“時序性”與“一致性”?

          一個低成本確保IM消息時序的方法探討

          IM單聊和群聊中的在線狀態同步應該用“推”還是“拉”?

          IM群聊消息如此復雜,如何保證不丟不重?

          談談移動端 IM 開發中登錄請求的優化

          移動端IM登錄時拉取數據如何作到省流量?

          淺談移動端IM的多點登錄和消息漫游原理

          完全自已開發的IM該如何設計“失敗重試”機制?

          微信對網絡影響的技術試驗及分析(論文全文)

          IM開發基礎知識補課(五):通俗易懂,正確理解并用好MQ消息隊列

          微信技術分享:微信的海量IM聊天消息序列號生成實踐(算法原理篇)

          IM開發基礎知識補課(六):數據庫用NoSQL還是SQL?讀這篇就夠了!

          IM里“附近的人”功能實現原理是什么?如何高效率地實現它?

          IM的掃碼登錄功能如何實現?一文搞懂主流應用的掃碼登錄技術原理

          IM消息ID技術專題(一):微信的海量IM聊天消息序列號生成實踐(算法原理篇)

          IM消息ID技術專題(二):微信的海量IM聊天消息序列號生成實踐(容災方案篇)

          IM消息ID技術專題(三):解密融云IM產品的聊天消息ID生成策略

          IM消息ID技術專題(四):深度解密美團的分布式ID生成算法

          IM消息ID技術專題(五):開源分布式ID生成器UidGenerator的技術實現

          IM消息ID技術專題(六):深度解密滴滴的高性能ID生成器(Tinyid)

          IM開發寶典:史上最全,微信各種功能參數和邏輯規則資料匯總

          本文已同步發布于“即時通訊技術圈”公眾號。

          ▲ 本文在公眾號上的鏈接是:點此進入,原文鏈接是:http://www.52im.net/thread-3182-1-1.html



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


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 惠来县| 宣威市| 宣化县| 丽江市| 大竹县| 湟中县| 通许县| 伊通| 长葛市| 肥乡县| 石门县| 揭西县| 台湾省| 客服| 盐边县| 建始县| 宜州市| 德钦县| 南澳县| 青州市| 五原县| 循化| 贵南县| 沙田区| 五指山市| 松阳县| 平陆县| 米易县| 台中市| 含山县| 揭东县| 陕西省| 阿合奇县| 三门峡市| 伽师县| 秦安县| 红桥区| 扬中市| 遂昌县| 阜南县| 北安市|