零基礎IM開發入門(三):什麼是IM系統的可靠性?

一、引言

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

二、系列文章

零基礎IM開發入門(一):什麼是IM系統?數據庫

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

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

《零基礎IM開發入門(四):什麼是IM系統的消息時序一致性? (稍後發佈)》服務器

《零基礎IM開發入門(五):什麼是IM系統的安全性? (稍後發佈)》網絡

《零基礎IM開發入門(六):什麼是IM系統的的心跳機制? (稍後發佈)》架構

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

《零基礎IM開發入門(八):如何理解並實現IM系統的多端消息漫遊? (稍後發佈)》性能

三、正文概述

通常來講,IM系統的消息「可靠性」,一般就是指聊天消息投遞的可靠性(準確的說,這個「消息」是廣義的,由於還存用戶看不見的各類指令,爲了通俗,統稱「消息」)。學習

從用戶行爲來說,消息「可靠性」應該分爲兩種類型:

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

從具體的技術表現來說,消息「可靠性」包含兩層含義:

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

對於「消息不丟」這個特徵來講,細化下來,它又包含兩重含義:

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

是的,對於第1)重含義好理解,第2)重含義的意思是:當對方沒有成功收到時,你的im系統也必需要感知到,不然,它一樣屬於被「丟」範疇。

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

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

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

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

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

所謂「服務端中轉型IM架構」是指:一條消息從客戶端A發出後,須要先通過 IM 服務器來進行中轉,而後再由 IM 服務器推送給客戶端B,這種模式也是目前最多見的 IM 系統的消息分發架構。

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

緣由是如下兩個很明顯的弊端:

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

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

答案是否認的。咱們繼續看下節。

五、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 消息天然就丟失了,若是不在業務層再增長可靠性保障(好比:後面要提到的網絡層面的消息重發保障),那麼意味着這條消息對於接收端來講就永遠丟失了,也就天然不存在「可靠性」了。

六、爲在線消息增長「可靠性」保障

那麼怎樣在應用層增長可靠性保障呢?

有一個現成的機制可供咱們借鑑: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爲止。

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

說完在線消息的「可靠性」問題,咱們該瞭解一下離線消息了。

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-三、2-四、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丟失,則只須要重傳該會話的全部離線消息。

八、聊天消息重複的問題

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

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

舉一個最簡單的例子:假設client成功收到了server推送的消息,但其後續發送的ACK丟失了,那麼server將會在超時後再次推送該消息,若是業務層不對重複消息進行處理,那麼用戶就會看到兩條徹底同樣的消息。

消息去重的方式其實很是簡單,通常是根據消息的惟一標誌(id)進行過濾。

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

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

關於消息的去重問題,在一對一聊天的狀況下,邏輯並不複雜,但在羣聊模式下,會將問題複雜化,有關羣聊消息不丟和去重的詳細討論,能夠深刻閱讀:《IM羣聊消息如此複雜,如何保證不丟不重?》。

九、本文小結

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

所謂「可靠的傳輸協議」TCP也並不能保障消息在應用層的可靠性。

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

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

十、參考資料

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

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

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

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

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

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

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

本文已同步發佈於「即時通信技術圈」公衆號:

連接是:http://www.52im.net/thread-3182-1-1.html

相關文章
相關標籤/搜索