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

本文作者“商文默”,本次有修訂和改動。
1、寫在前面
我整理的大量IM技術(shù)文章中(見本文末“參考資料”一節(jié)),有關(guān)消息可靠性和一致性問題的文章占了很大比重,原因是IM這類系統(tǒng)拋開各種眼花繚亂的產(chǎn)品功能和技術(shù)特性,保證消息的可靠性和一致性幾乎是IM產(chǎn)品必需的素質(zhì)。
試想如果一個IM連發(fā)出的消息都不知道對方到底能不能收到、發(fā)出的聊天內(nèi)容對方看到的到底是不是“胡言亂語”(嚴重亂序問題),這樣的APP用戶肯定不會讓他在手機上過夜(肯定第一時間卸載了),因為最基本的聊天邏輯都無法實現(xiàn),它已經(jīng)失去了IM軟件本身的意義。
不過,另一個方面來講,IM系統(tǒng)是不標準的(雖然曾經(jīng)XMPP這種協(xié)議試圖解決這個問題,但事實證明那根本不現(xiàn)實),各家?guī)缀醵际亲砸训乃接袇f(xié)議、不同的實現(xiàn)邏輯,這也決定了即使同一個技術(shù)問題,對于IM來說很難有固定的實現(xiàn)套路和標準的解決方案。
所以,對于本文來說,文中作者雖然提供了有關(guān)IM消息“可靠性”與“一致性”問題的解決方案,但方案到底合不合理、適不適合你,這就是仁者見仁、智者見智的事了。用人話說就是:本文內(nèi)容僅供參考,具體的解決方案請務(wù)結(jié)合自已的系統(tǒng)構(gòu)架和實現(xiàn)情況,多閱讀幾篇有關(guān)這個技術(shù)話題的文章,取其精華,找到適合自已的技術(shù)方案和思路才是最明智的。

學(xué)習(xí)交流:
- 即時通訊/推送技術(shù)開發(fā)交流5群:215477170?[推薦]
- 移動端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK
(本文同步發(fā)布于:http://www.52im.net/thread-3574-1-1.html)
2、本文引言
叢所周之,即時通訊聊天(IM)系統(tǒng)必需要解決消息可靠性及消息一致性問題(PS:如果具體IM系統(tǒng)是什么你都還沒弄明白,先讀這篇《零基礎(chǔ)IM開發(fā)入門(一):什么是IM系統(tǒng)?》)。
這兩個問題,通俗來說就是:
1)消息可靠性:簡單來說就是不丟消息,會話一方發(fā)送消息,消息成功到達對方并正確顯示;
2)消息一致性:包括發(fā)送一方消息一致及會話雙方消息一致,要求消息不重復(fù),不亂序。
本文會從典型的IM消息發(fā)送邏輯開始,簡單易懂地闡明消息可靠性、一致性問題的原理及可參考的技術(shù)解決方法,或許技術(shù)方案并不完美,但希望能為你的IM技術(shù)問題解決帶來啟發(fā)。
3、典型IM消息發(fā)送過程
IM的消息發(fā)送一般的實現(xiàn)過程可以分為兩個階段:
1)發(fā)送方發(fā)送消息、服務(wù)端接收、返回消息 ACK 給發(fā)送方;
2)服務(wù)端將消息推送到接收方。
判斷消息發(fā)送是否成功主要依據(jù)第一階段——即服務(wù)器是否接受到消息。
對于消息發(fā)送者來說,消息狀態(tài)可以分為三類:
1)正在發(fā)送;
2)發(fā)送成功;
3)發(fā)送失敗。
具體來說,這三類狀態(tài)的具體意義是:
1)正在發(fā)送:發(fā)送方觸發(fā)發(fā)送事件開始,到收到服務(wù)端返回消息對應(yīng) ACK 之前;
2)發(fā)送成功:發(fā)送方收到消息對應(yīng) ACK 回復(fù);
3)發(fā)送失?。撼^一定重發(fā)次數(shù),未收到消息對應(yīng) ACK 回復(fù)。
對應(yīng)的消息發(fā)送流程如下圖所示:

4、IM消息可靠性
限于篇幅,對于IM消息可靠性的基本概念和詳細原理建議閱讀《零基礎(chǔ)IM開發(fā)入門(三):什么是IM系統(tǒng)的可靠性?》,本文著重談?wù)劷鉀Q思路。
4.1 重發(fā)機制
保證消息發(fā)送第一階段(見本文“3、典型IM消息發(fā)送過程”一節(jié))消息成功發(fā)送的方法是設(shè)立重發(fā)機制:
1)依據(jù)一定時長內(nèi)是否收到消息對應(yīng) ACK,判斷消息是否要重發(fā);
2)如果超過預(yù)設(shè)時長,就重新發(fā)送;
3)當重發(fā)次數(shù)超過預(yù)設(shè)次數(shù),就不再重發(fā),判定該消息發(fā)送失敗,修改消息發(fā)送狀態(tài)。
PS:具體的完整方案級代碼實現(xiàn),可以參考MobileIMSDK??中有關(guān)QoS機制的代碼實現(xiàn)。
4.2 會話記錄檢查
消息發(fā)送第二階段(見本文“3、典型IM消息發(fā)送過程”一節(jié))服務(wù)端推送消息到接收方,如果連接斷開,會丟失消息。
所以要保證消息完整,就需要在建立連接后,根據(jù)上一條消息(已經(jīng) ACK)時間戳,獲取會話記錄,一次返回一段時間內(nèi)所有消息(PS:中大型應(yīng)用中,消息的拉取也不是個簡單事情,詳情可以閱讀《IM開發(fā)干貨分享:如何優(yōu)雅的實現(xiàn)大量離線消息的可靠投遞》)。
另一種保證方法是加入定時輪詢,檢查消息完整性,具體的思路如下圖所示。
建立連接流程圖:

4.3 需要考慮的兩個問題
消息重發(fā)、會話記錄檢查需要考慮兩個問題:
1)消息是否會重復(fù)發(fā)送;
2)消息順序是否會被打亂。
舉兩個例子。
關(guān)于消息重發(fā)問題:
1)如果丟消息的點在消息達到服務(wù)端之前,服務(wù)端并沒有收到消息,發(fā)送方重新發(fā)送丟失消息,服務(wù)端接收成功,不會產(chǎn)生兩條相同消息;
2)而如果服務(wù)端接收到消息,返回 ACK 丟失,這時再發(fā)送一次相同消息,就可能造成消息重復(fù)。
關(guān)于消息順序問題:
1)如果發(fā)送方連發(fā)三條消息,第一、第三條成功被服務(wù)端接收,第二條丟了,那第三條消息是否會被記錄?
2)如果這時第二條消息達到服務(wù)端,其順序是在第三條時間之前還是之后(服務(wù)端一般都會給記錄打一個時間戳)?
5、IM消息一致性
同上節(jié)一樣,對于IM消息一致性的基本概念和詳細原理建議閱讀《零基礎(chǔ)IM開發(fā)入門(四):什么是IM系統(tǒng)的消息時序一致性?》。
5.1 使用 uuid 消息去重
對于消息重發(fā)問題,可以給每條消息增加屬性 uuid 作為消息唯一標識,重發(fā)消息 uuid 不變,前端根據(jù) uuid 去重。大致思路就是這樣。
PS:對于IM來說,消息ID也是個很大的技術(shù)話題,有興趣可以讀下面這個系列:
《IM消息ID技術(shù)專題(一):微信的海量IM聊天消息序列號生成實踐(算法原理篇)》
《IM消息ID技術(shù)專題(二):微信的海量IM聊天消息序列號生成實踐(容災(zāi)方案篇)》
《IM消息ID技術(shù)專題(三):解密融云IM產(chǎn)品的聊天消息ID生成策略》
《IM消息ID技術(shù)專題(四):深度解密美團的分布式ID生成算法》
《IM消息ID技術(shù)專題(五):開源分布式ID生成器UidGenerator的技術(shù)實現(xiàn)》
《IM消息ID技術(shù)專題(六):深度解密滴滴的高性能ID生成器(Tinyid)》
5.2 使用向量時鐘進行消息排序
對于消息排序問題:因為在聊天中,消息的順序?qū)τ诎l(fā)送方的表述有重要的影響,消息不完整或順序顛倒都可能造成語意不連貫,甚至曲解。所以需要保證發(fā)送方發(fā)送消息順序,而會話雙方消息排序需要考慮實際情況。
在一般的認知里:狀態(tài)是正在發(fā)送的消息,應(yīng)該還沒有被對方看到,只有發(fā)送成功的消息,才會被對方看到。但在實現(xiàn)中,消息發(fā)送成功是以服務(wù)器接收消息并返回 ACK 成功為判斷依據(jù),而不是被對方接收到。
那么就會出現(xiàn)這樣一個問題:如果一條消息狀態(tài)是正在發(fā)送,此時收到一條消息,那么收到的消息是在正在發(fā)送的消息之前還是之后?
這是一個上下文關(guān)系,關(guān)鍵問題是:發(fā)送方是以哪條所見消息為依據(jù)發(fā)送消息的。
這里提供一種思路:借鑒分布式系統(tǒng)中的向量時鐘算法(見《分布式系統(tǒng)中的向量時鐘算法》)。
先簡單描述向量時鐘算法:
向量時鐘算法用于在分布式系統(tǒng)中生成事件偏序關(guān)系,并糾正因果關(guān)系。一個系統(tǒng)包含 N 個節(jié)點,每個節(jié)點產(chǎn)生的消息體中包含該節(jié)點的邏輯時鐘,整體系統(tǒng)的向量時鐘由 N 維邏輯時鐘組成,并在每個節(jié)點產(chǎn)生的消息體中傳遞。
簡單來說,向量時鐘算法的實現(xiàn)原理如下:
1)初始狀態(tài),向量值為 0;
2)每次節(jié)點處理完節(jié)點事件,該節(jié)點時鐘+1;
3)每次節(jié)點發(fā)送消息,將包含自身時鐘的系統(tǒng)向量時鐘一起發(fā)送;
4)每次節(jié)點收到消息,更新向量時鐘,該節(jié)點時鐘+1,其他節(jié)點對比每個節(jié)點本地保留的向量時鐘值和消息體中向量時鐘值,取最大值;
5)節(jié)點同時收到多條消息,判斷接收消息的向量時鐘之間是否存在偏序關(guān)系。
針對上述的第5)點:
1)如果存在偏序關(guān)系,則合并向量時鐘,取偏序較大的向量時鐘;
2)如果不存在偏序關(guān)系,則不能合并。
偏序關(guān)系:如果 A 向量中的每一維都大于等于 B 向量,則 A、B 之間存在偏序關(guān)系,否則不存在偏序關(guān)系。
對于IM為聊天消息排序來說,其實就是處理聊天消息的上下文語境,決定消息之間的因果關(guān)系。
參考向量時鐘算法:假設(shè)有 N 個消息會話方,系統(tǒng)的向量時鐘由 N 維時鐘組成,向量時鐘在各方發(fā)送的消息體中傳遞,并依據(jù)向量時鐘排序。
具體實現(xiàn)思路如下:
1)系統(tǒng)向量時鐘設(shè)為 (0, 0, …, N);
2)節(jié)點發(fā)送消息,更新系統(tǒng)向量時鐘,該節(jié)點時鐘加一,其他節(jié)點不變;
3)節(jié)點接收消息,更新系統(tǒng)向量時鐘,該節(jié)點時鐘加一;其他節(jié)點對比每個節(jié)點本地保留的向量時鐘的值和消息中向量時鐘的值,取最大值;
4)依據(jù)消息體內(nèi)系統(tǒng)向量時鐘的偏序關(guān)系決定消息順序。
針對上述第4)點:
1)如果可以確定偏序關(guān)系,則根據(jù)偏序關(guān)系由小到大顯示;
2)如果多條消息不能確定偏序關(guān)系,則按照自然順序(接收到的順序)顯示。
向量時鐘在理論上可以解決大部分消息一致性的問題,但在實現(xiàn)中還需要考慮實際使用時的體驗。
這其中最需要關(guān)注的問題是:是否要強制排序,或者說,如果實際顯示順序和向量時鐘之間的偏序關(guān)系不一致,是否要移動消息之間的順序。
舉個例子:在一個有多人的會話中,如果有一方網(wǎng)速特別慢,收不到消息,也發(fā)不出消息。在他看到的最后的消息之后,其他人已經(jīng)開始新的話題,這時他關(guān)于上一個話題的消息終于發(fā)送成功,并被其他人收到。
此時就存在這樣一個問題:這條關(guān)于上一個話題的消息是顯示在最后,還是移到較早時間?
1)如果顯示在最后,但消息內(nèi)容和目前的話題不相關(guān),其他人可能會感到莫名其妙;
2)如果把消息移到較早時間,那么這條消息可能不會被其他人看到,或者看到前面多了一條消息,會有種突兀的感覺。
IM 的場景很多,也很復(fù)雜,更多的時候需要從產(chǎn)品角度考慮問題。
對于消息是否需要排序的問題,這里只提出一個比較通用的方案:建議會話中不強制排序,會話歷史記錄中按照向量時鐘的偏序關(guān)系進行排序。
6、本文小結(jié)
對于 IM 系統(tǒng)消息可靠性及一致性問題,通過消息重發(fā)機制保證消息成功被服務(wù)端接收,通過會話記錄檢查保證收取消息完整,從而保證整個消息發(fā)送過程的可靠性。使用 uuid 消息去重,參考向量時鐘算法進行消息排序,為保證消息一致性提供一種解決方案。
總之,IM這類系統(tǒng)看似簡單,實則水深似海,如果你是IM開發(fā)新手,可以從《新手入門一篇就夠:從零開發(fā)移動端IM》這篇入手系統(tǒng)學(xué)習(xí)。如果你自認為已是IM老手,這里整理的?IM中大型架構(gòu)設(shè)計?方面的文章或許可以參考一下。
7、參考資料
[1]?零基礎(chǔ)IM開發(fā)入門(三):什么是IM系統(tǒng)的可靠性?
[2]?零基礎(chǔ)IM開發(fā)入門(四):什么是IM系統(tǒng)的消息時序一致性?
[3]?IM消息送達保證機制實現(xiàn)(一):保證在線實時消息的可靠投遞
[4]?IM消息送達保證機制實現(xiàn)(二):保證離線消息的可靠投遞
[5]?如何保證IM實時消息的“時序性”與“一致性”?
[6]?一個低成本確保IM消息時序的方法探討
[7]?IM群聊消息如此復(fù)雜,如何保證不丟不重?
[8]?完全自已開發(fā)的IM該如何設(shè)計“失敗重試”機制?
[9]?IM開發(fā)干貨分享:如何優(yōu)雅的實現(xiàn)大量離線消息的可靠投遞
[10]?從客戶端的角度來談?wù)勔苿佣薎M的消息可靠性和送達機制
[11]?一套億級用戶的IM架構(gòu)技術(shù)干貨(下篇):可靠性、有序性、弱網(wǎng)優(yōu)化等
[12]?從新手到專家:如何設(shè)計一套億級消息量的分布式IM系統(tǒng)
本文已同步發(fā)布于“即時通訊技術(shù)圈”公眾號。

▲ 本文在公眾號上的鏈接是:點此進入。同步發(fā)布鏈接是:http://www.52im.net/thread-3574-1-1.html