理解IM消息「可靠性」和「一致性」問題,以及解決方案探討

本文做者「商文默」,有修訂和改動。php

一、寫在前面

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

試想若是一個IM連發出的消息都不知道對方到底能不能收到、發出的聊天內容對方看到的究竟是不是「胡言亂語」(嚴重亂序問題),這樣的APP用戶確定不會讓他在手機上過夜(確定第一時間卸載了),由於最基本的聊天邏輯都沒法實現,它已經失去了IM軟件自己的意義。前端

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

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

二、本文引言

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

這兩個問題,通俗來講就是:服務器

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

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

三、典型IM消息發送過程

IM的消息發送通常的實現過程能夠分爲兩個階段:markdown

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

判斷消息發送是否成功主要依據第一階段——即服務器是否接受到消息。架構

對於消息發送者來講,消息狀態能夠分爲三類:

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

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

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

對應的消息發送流程以下圖所示:

四、IM消息可靠性

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

4.1 重發機制

保證消息發送第一階段(見本文「三、典型IM消息發送過程」一節)消息成功發送的方法是設立重發機制:

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

PS: 具體的完整方案級代碼實現,能夠參考MobileIMSDK 中有關QoS機制的代碼實現。

4.2 會話記錄檢查

消息發送第二階段(見本文「三、典型IM消息發送過程」一節)服務端推送消息到接收方,若是鏈接斷開,會丟失消息。

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

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

創建鏈接流程圖:

4.3 須要考慮的兩個問題

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

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

舉兩個例子。

關於消息重發問題:

  • 1)若是丟消息的點在消息達到服務端以前,服務端並無收到消息,發送方從新發送丟失消息,服務端接收成功,不會產生兩條相同消息;
  • 2)而若是服務端接收到消息,返回 ACK 丟失,這時再發送一次相同消息,就可能形成消息重複。

關於消息順序問題:

  • 1)若是發送方連發三條消息,第1、第三條成功被服務端接收,第二條丟了,那第三條消息是否會被記錄?
  • 2)若是這時第二條消息達到服務端,其順序是在第三條時間以前仍是以後(服務端通常都會給記錄打一個時間戳)?

五、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 的場景不少,也很複雜,更多的時候須要從產品角度考慮問題。

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

六、本文小結

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

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

七、參考資料

[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系統

本文已同步發佈於**:** www.52im.net/thread-3574…

相關文章
相關標籤/搜索