本文編寫時引用了“聊聊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)重含義的意思是:當對方沒有成功收到時,你的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