理解IM訊息“可靠性”和“一致性”問題,以及解決方案探討

語言: CN / TW / HK

本文作者“商文默”,有修訂和改動。

1、寫在前面

我整理的大量IM技術文章中(見本文末“參考資料”一節),有關訊息可靠性和一致性問題的文章佔了很大比重,原因是IM這類系統拋開各種眼花繚亂的產品功能和技術特性,保證訊息的可靠性和一致性幾乎是IM產品必需的素質。

試想如果一個IM連發出的訊息都不知道對方到底能不能收到、發出的聊天內容對方看到的到底是不是“胡言亂語”(嚴重亂序問題),這樣的APP使用者肯定不會讓他在手機上過夜(肯定第一時間解除安裝了),因為最基本的聊天邏輯都無法實現,它已經失去了IM軟體本身的意義。

不過,另一個方面來講,IM系統是不標準的(雖然曾經XMPP這種協議試圖解決這個問題,但事實證明那根本不現實),各家幾乎都是自已的私有協議、不同的實現邏輯,這也決定了即使同一個技術問題,對於IM來說很難有固定的實現套路和標準的解決方案。

所以,對於本文來說,文中作者雖然提供了有關IM訊息“可靠性”與“一致性”問題的解決方案,但方案到底合不合理、適不適合你,這就是仁者見仁、智者見智的事了。用人話說就是:本文內容僅供參考,具體的解決方案請務結合自已的系統構架和實現情況,多閱讀幾篇有關這個技術話題的文章,取其精華,找到適合自已的技術方案和思路才是最明智的。

2、本文引言

叢所周之,即時通訊聊天(IM)系統必需要解決訊息可靠性及訊息一致性問題(PS:如果具體IM系統是什麼你都還沒弄明白,先讀這篇《零基礎IM開發入門(一):什麼是IM系統?)。

這兩個問題,通俗來說就是:

  • 1)訊息可靠性:簡單來說就是不丟訊息,會話一方傳送訊息,訊息成功到達對方並正確顯示;
  • 2)訊息一致性:包括髮送一方訊息一致及會話雙方訊息一致,要求訊息不重複,不亂序。

本文會從典型的IM訊息傳送邏輯開始,簡單易懂地闡明訊息可靠性、一致性問題的原理及可參考的技術解決方法,或許技術方案並不完美,但希望能為你的IM技術問題解決帶來啟發。

3、典型IM訊息傳送過程

IM的訊息傳送一般的實現過程可以分為兩個階段:

  • 1)傳送方傳送訊息、服務端接收、返回訊息 ACK 給傳送方;
  • 2)服務端將訊息推送到接收方。

判斷訊息傳送是否成功主要依據第一階段——即伺服器是否接受到訊息。

對於訊息傳送者來說,訊息狀態可以分為三類:

  • 1)正在傳送;
  • 2)傳送成功;
  • 3)傳送失敗。

具體來說,這三類狀態的具體意義是:

  • 1)正在傳送:傳送方觸發傳送事件開始,到收到服務端返回訊息對應 ACK 之前;
  • 2)傳送成功:傳送方收到訊息對應 ACK 回覆;
  • 3)傳送失敗:超過一定重發次數,未收到訊息對應 ACK 回覆。

對應的訊息傳送流程如下圖所示:

4、IM訊息可靠性

限於篇幅,對於IM訊息可靠性的基本概念和詳細原理建議閱讀《零基礎IM開發入門(三):什麼是IM系統的可靠性?》,本文著重談談解決思路。

4.1 重發機制

保證訊息傳送第一階段(見本文“3、典型IM訊息傳送過程”一節)訊息成功傳送的方法是設立重發機制:

  • 1)依據一定時長內是否收到訊息對應 ACK,判斷訊息是否要重發;
  • 2)如果超過預設時長,就重新發送;
  • 3)當重發次數超過預設次數,就不再重發,判定該訊息傳送失敗,修改訊息傳送狀態。

PS:具體的完整方案級程式碼實現,可以參考MobileIMSDK  中有關QoS機制的程式碼實現。

4.2 會話記錄檢查

訊息傳送第二階段(見本文“3、典型IM訊息傳送過程”一節)服務端推送訊息到接收方,如果連線斷開,會丟失訊息。

所以要保證訊息完整,就需要在建立連線後,根據上一條訊息(已經 ACK)時間戳,獲取會話記錄,一次返回一段時間內所有訊息(PS:中大型應用中,訊息的拉取也不是個簡單事情,詳情可以閱讀《IM開發乾貨分享:如何優雅的實現大量離線訊息的可靠投遞)。

另一種保證方法是加入定時輪詢,檢查訊息完整性,具體的思路如下圖所示。

建立連線流程圖:

4.3 需要考慮的兩個問題

訊息重發、會話記錄檢查需要考慮兩個問題:

  • 1)訊息是否會重複傳送;
  • 2)訊息順序是否會被打亂。

舉兩個例子。

關於訊息重發問題:

  • 1)如果丟訊息的點在訊息達到服務端之前,服務端並沒有收到訊息,傳送方重新發送丟失訊息,服務端接收成功,不會產生兩條相同訊息;
  • 2)而如果服務端接收到訊息,返回 ACK 丟失,這時再發送一次相同訊息,就可能造成訊息重複。

關於訊息順序問題:

  • 1)如果傳送方連發三條訊息,第一、第三條成功被服務端接收,第二條丟了,那第三條訊息是否會被記錄?
  • 2)如果這時第二條訊息達到服務端,其順序是在第三條時間之前還是之後(服務端一般都會給記錄打一個時間戳)?

5、IM訊息一致性

同上節一樣,對於IM訊息一致性的基本概念和詳細原理建議閱讀《零基礎IM開發入門(四):什麼是IM系統的訊息時序一致性?》。

5.1 使用 uuid 訊息去重

對於訊息重發問題,可以給每條訊息增加屬性 uuid 作為訊息唯一標識,重發訊息 uuid 不變,前端根據 uuid 去重。大致思路就是這樣。

PS:對於IM來說,訊息ID也是個很大的技術話題,有興趣可以讀下面這個系列:

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

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

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

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

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

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

5.2 使用向量時鐘進行訊息排序

對於訊息排序問題:因為在聊天中,訊息的順序對於傳送方的表述有重要的影響,訊息不完整或順序顛倒都可能造成語意不連貫,甚至曲解。所以需要保證傳送方傳送訊息順序,而會話雙方訊息排序需要考慮實際情況。

在一般的認知裡:狀態是正在傳送的訊息,應該還沒有被對方看到,只有傳送成功的訊息,才會被對方看到。但在實現中,訊息傳送成功是以伺服器接收訊息並返回 ACK 成功為判斷依據,而不是被對方接收到。

那麼就會出現這樣一個問題:如果一條訊息狀態是正在傳送,此時收到一條訊息,那麼收到的訊息是在正在傳送的訊息之前還是之後?

這是一個上下文關係,關鍵問題是:傳送方是以哪條所見訊息為依據傳送訊息的。

這裡提供一種思路:借鑑分散式系統中的向量時鐘演算法(見《分散式系統中的向量時鐘演算法)。

先簡單描述向量時鐘演算法:

向量時鐘演算法用於在分散式系統中生成事件偏序關係,並糾正因果關係。一個系統包含 N 個節點,每個節點產生的訊息體中包含該節點的邏輯時鐘,整體系統的向量時鐘由 N 維邏輯時鐘組成,並在每個節點產生的訊息體中傳遞。

簡單來說,向量時鐘演算法的實現原理如下:

  • 1)初始狀態,向量值為 0;
  • 2)每次節點處理完節點事件,該節點時鐘+1;
  • 3)每次節點發送訊息,將包含自身時鐘的系統向量時鐘一起傳送;
  • 4)每次節點收到訊息,更新向量時鐘,該節點時鐘+1,其他節點對比每個節點本地保留的向量時鐘值和訊息體中向量時鐘值,取最大值;
  • 5)節點同時收到多條訊息,判斷接收訊息的向量時鐘之間是否存在偏序關係。

針對上述的第5)點:

  • 1)如果存在偏序關係,則合併向量時鐘,取偏序較大的向量時鐘;
  • 2)如果不存在偏序關係,則不能合併。

偏序關係:如果 A 向量中的每一維都大於等於 B 向量,則 A、B 之間存在偏序關係,否則不存在偏序關係。

對於IM為聊天訊息排序來說,其實就是處理聊天訊息的上下文語境,決定訊息之間的因果關係。

參考向量時鐘演算法:假設有 N 個訊息會話方,系統的向量時鐘由 N 維時鐘組成,向量時鐘在各方傳送的訊息體中傳遞,並依據向量時鐘排序。

具體實現思路如下:

  • 1)系統向量時鐘設為 (0, 0, …, N);
  • 2)節點發送訊息,更新系統向量時鐘,該節點時鐘加一,其他節點不變;
  • 3)節點接收訊息,更新系統向量時鐘,該節點時鐘加一;其他節點對比每個節點本地保留的向量時鐘的值和訊息中向量時鐘的值,取最大值;
  • 4)依據訊息體內系統向量時鐘的偏序關係決定訊息順序。

針對上述第4)點:

  • 1)如果可以確定偏序關係,則根據偏序關係由小到大顯示;
  • 2)如果多條訊息不能確定偏序關係,則按照自然順序(接收到的順序)顯示。

向量時鐘在理論上可以解決大部分訊息一致性的問題,但在實現中還需要考慮實際使用時的體驗。

這其中最需要關注的問題是:是否要強制排序,或者說,如果實際顯示順序和向量時鐘之間的偏序關係不一致,是否要移動訊息之間的順序。

舉個例子:在一個有多人的會話中,如果有一方網速特別慢,收不到訊息,也發不出訊息。在他看到的最後的訊息之後,其他人已經開始新的話題,這時他關於上一個話題的訊息終於傳送成功,並被其他人收到。

此時就存在這樣一個問題:這條關於上一個話題的訊息是顯示在最後,還是移到較早時間?

  • 1)如果顯示在最後,但訊息內容和目前的話題不相關,其他人可能會感到莫名其妙;
  • 2)如果把訊息移到較早時間,那麼這條訊息可能不會被其他人看到,或者看到前面多了一條訊息,會有種突兀的感覺。

IM 的場景很多,也很複雜,更多的時候需要從產品角度考慮問題。

對於訊息是否需要排序的問題,這裡只提出一個比較通用的方案:建議會話中不強制排序,會話歷史記錄中按照向量時鐘的偏序關係進行排序。

6、本文小結

對於 IM 系統訊息可靠性及一致性問題,通過訊息重發機制保證訊息成功被服務端接收,通過會話記錄檢查保證收取訊息完整,從而保證整個訊息傳送過程的可靠性。使用 uuid 訊息去重,參考向量時鐘演算法進行訊息排序,為保證訊息一致性提供一種解決方案。

總之,IM這類系統看似簡單,實則水深似海,如果你是IM開發新手,可以從《新手入門一篇就夠:從零開發移動端IM》這篇入手系統學習。如果你自認為已是IM老手,這裡整理的 IM中大型架構設計 方面的文章或許可以參考一下。

7、參考資料

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

[2] 零基礎IM開發入門(四):什麼是IM系統的訊息時序一致性?

[3] IM訊息送達保證機制實現(一):保證線上實時訊息的可靠投遞

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

[5] 如何保證IM實時訊息的“時序性”與“一致性”?

[6] 一個低成本確保IM訊息時序的方法探討

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

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

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

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

[11] 一套億級使用者的IM架構技術乾貨(下篇):可靠性、有序性、弱網優化等

[12] 從新手到專家:如何設計一套億級訊息量的分散式IM系統

本文已同步釋出於“即時通訊技術圈”公眾號。

▲ 本文在公眾號上的連結是:點此進入。同步釋出連結是:http://www.52im.net/thread-3574-1-1.html

「其他文章」