本文引用了沈劍《如何保證IM實時消息的“時序性”與“一致性”?》一文的圖片和內容(由于太懶,圖沒重新畫),原文鏈接在文末。
1、引言
本文接上篇《零基礎IM開發(fā)入門(三):什么是IM系統(tǒng)的可靠性?》,講解IM系統(tǒng)中消息時序的一致性問題。
所謂的一致性,在IM中通常指的是消息時序的一致性,那就是:
- 1)聊天消息的上下文連續(xù)性;
- 2)聊天消息的絕對時間序。
再具體一點,IM消息的一致性體現(xiàn)在:
- 1)單聊時:要保證發(fā)送方發(fā)出聊天消息的順序與接收方看到的順序一致;
- 2)群聊時:要保證所有群員看到的聊天消息,與發(fā)送者發(fā)出消息時的絕對時間序是一致的。
IM系統(tǒng)中消息時序的一致性問題是個看似簡單,實則非常有難度的技術熱點話題之一,本文盡量以通俗簡顯的文字為你講解IM消息時序一致性問題的產品意義、發(fā)生原因、解決思路等。
- 即時通訊/推送技術開發(fā)交流5群:215477170 [推薦]
- 移動端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK
(本文同步發(fā)布于:http://www.52im.net/thread-3189-1-1.html)
2、系列文章
《零基礎IM開發(fā)入門(一):什么是IM系統(tǒng)?》
《零基礎IM開發(fā)入門(二):什么是IM系統(tǒng)的實時性?》
《零基礎IM開發(fā)入門(三):什么是IM系統(tǒng)的可靠性?》
《零基礎IM開發(fā)入門(四):什么是IM系統(tǒng)的消息時序一致性?》(* 本文)
《零基礎IM開發(fā)入門(五):什么是IM系統(tǒng)的安全性? (稍后發(fā)布)》
《零基礎IM開發(fā)入門(六):什么是IM系統(tǒng)的的心跳機制? (稍后發(fā)布)》
《零基礎IM開發(fā)入門(七):如何理解并實現(xiàn)IM系統(tǒng)消息未讀數(shù)? (稍后發(fā)布)》
《零基礎IM開發(fā)入門(八):如何理解并實現(xiàn)IM系統(tǒng)的多端消息漫游? (稍后發(fā)布)》
3、消息時序的一致性,對于IM的意義
現(xiàn)如今,由于移動互聯(lián)網的普及,現(xiàn)代人的實際社交關系,幾乎完全是靠IM這種即時通訊社交工具所組織起來的,IM這種工具的重要性不言而喻。
IM在現(xiàn)代人的生活中,越來越重要,但也越來平常。現(xiàn)在想聯(lián)系一個人,第一時間想到的不是打個電話,而是發(fā)個“微信”或“QQ”。是的,IM這玩意承載的意義越來越多。
消息時序的一致性問題,對于IM的意義,毫無疑問帶來的不只是簡簡單單的所謂用戶體驗問題。我們來看看例子。
假設:你跟女神的表白正進入到關鍵階段,聊著聊著就因為這爛IM,導致聊天消息前言不搭后語,此刻1000公里外你的女神真一臉蒙逼的盯著手機看你的“醉話”,后果是多么嚴重——這半年來的“舔狗”生活、忍辱負重,算是白白被程序員這群“格子衫”、“地中海”們給禍害了。
再往嚴重了說,就因為這爛IM,讓你失去了這難得的借助女神優(yōu)良基因,改造家族后代顏值的絕佳機會,無不讓人痛心疾首。。。
上面這個例子,說的還只是單聊,如果是群聊,則問題可能還會被無限放大:試想一個技術交流群,正在激烈的爭吵著“php是世界最好語言”這種話題的時候,忽然就想砸手機了,不是因為吵的太兇,而因為消息順序全亂完全沒法看,已經嚴重影響鍵盤俠們積極發(fā)表個人意見了。
4、憑什么說保證消息時序的一致性很困難?
4.1 基本認知
在普通IM用戶的眼里,消息無非是從一臺手機傳遞到另一臺手機而已,保證時序有何困難?
是的,普通用戶這么認為,從技術上講,他只是單純的將IM消息的收發(fā)過程理解為單線程的工作模式而已。
實際上,在IM這種高性能場景下,服務端為了追求高吞吐、高并發(fā),用到了多線程、異步IO等等技術。
在這種情況下,“高并發(fā)”與“順序”對于IM服務端來說,本來就是矛盾的,這就有點魚與熊掌的味道了(兩者很難兼得)。
所以,要實現(xiàn)IM場景下的消息時序一致性,需要做出權衡,而且要考慮的技術維度相當多。這就導致具體技術實施起來沒有固定的套路,而由于開發(fā)者技術能力的參差不齊,也就使得很多IM系統(tǒng)在實際的效果上會有較大問題,對于用戶而言也將直接在產品體驗上反應出來。
下面將具體說明技術難點所在。
4.2 沒有全局時鐘
如上圖所示,一個真正堪用的生產系統(tǒng),顯示不可能所有服務都跑在一臺服務器上,分布式環(huán)境是肯定的。
那么:在分布式環(huán)境下,客戶端+服務端后臺的各種后臺服務,都各自分布在不同的機器上,機器之間都是使用的本地時鐘,沒有一個所謂的“全局時鐘”(也沒辦法做到真正的全局時鐘),那么所謂的消息時序也就沒有真正意義上的時序基準點。所以消息時序問題顯然不是“本地時間”可以完全決定的。
4.3 多發(fā)送方問題
服務端分布式的情況下,不能用“本地時間”來保證時序性,那么能否用接收方本地時間表示時序呢?
遺憾的是,由于多個客戶端的存在(比如群聊時),即使是一臺服務器的本地時間,也無法表示“絕對時序”。
如上圖所示:絕對時序上,APP1先發(fā)出msg1,APP2后發(fā)出msg2,都發(fā)往服務器web1,網絡傳輸是不能保證msg1一定先于msg2到達的,所以即使以一臺服務器web1的時間為準,也不能精準描述msg1與msg2的絕對時序。
4.4 多接收方問題
多發(fā)送方不能保證時序,假設只有一個發(fā)送方,能否用發(fā)送方的本地時間表示時序呢?遺憾的是,由于多個接收方的存在,無法用發(fā)送方的本地時間,表示“絕對時序”。
如上圖,絕對時序上,web1先發(fā)出msg1,后發(fā)出msg2,由于網絡傳輸及多接收方的存在,無法保證msg1先被接收到先被處理,故也無法保證msg1與msg2的處理時序。
4.5 網絡傳輸與多線程問題
既然多發(fā)送方與多接收方都難以保證絕對時序,那么假設只有單一的發(fā)送方與單一的接收方,能否保證消息的絕對時序一致性呢?
結論是悲觀的,由于網絡傳輸與多線程的存在,這仍然不行。
如上圖所示,web1先發(fā)出msg1、后發(fā)出msg2,即使msg1先到達(網絡傳輸其實還不能保證msg1先到達),由于多線程的存在,也不能保證msg1先被處理完。
5、如何保證絕對的消息時序一致性?
通過上一章內容的總結,我們已經對IM中消息時序一致性問題所產生的緣由,有了較為深刻的認識。
從純技術的角度來說,假設:
- 1)只有一個發(fā)送方;
- 2)一個接收方;
- 3)上下游連接只有一條socket連接;
- 4)通過阻塞的方式通訊。
這樣的情況下,難道不能保證先發(fā)出的消息被先處理,進而被先展示給消息的接收者嗎?
是的,可以!
但實際生產情況下不太可能出現(xiàn)這種IM系統(tǒng),必竟單發(fā)送方、單接收方、單socket連接、阻塞方式,這樣的IM一旦做出來,產品經理會立馬死給你看。。。
6、實用的優(yōu)化思路
6.1 一對一單聊的消息一致性保證思路
假設兩人一對一聊天,發(fā)送方A依次發(fā)出了msg1、msg2、msg3三條消息給接收方B,這三條消息該怎么保證顯示時序的一致性(發(fā)送與顯示的順序一致)?
我們知道,發(fā)送方A依次發(fā)出的msg1、msg2、msg3三條消息,到底服務端后,再由服務端中轉發(fā)出時,這個順序由于多線程的網絡的問題,是有可能亂序的。
那么結果就可能是這樣:
如上圖所示,會出現(xiàn)與發(fā)出時的消息時序不一致問題(收到的消息順序是:msg3、msg1、msg2)。
不過,實際上一對一聊天的兩個人,并不需要全局消息時序的一致(因為聊天只在兩人的同一會話在發(fā)生),只需要對于同一個發(fā)送方A,發(fā)給B的消息時序一致就行了。
常見優(yōu)化方案,在A往B發(fā)出的消息中,加上發(fā)送方A本地的一個絕對時序(比如本機時間戳),來表示接收方B的展現(xiàn)時序。
那么當接收方B收到消息后,即使極端情況下消息可能存在亂序到達,但因為這個亂序的時間差對于普通用戶來說體感是很短的,在UI展現(xiàn)層按照消息中自帶的絕對時序排個序后再顯示,用戶其實是沒有太多感知的。
6.2 多對多群聊的消息一致性保證思路
假設N個群友在一個IM群里聊天,應該怎樣保證所有群員收到消息的顯示時序一致性呢?
首先:不能像一對聊天那樣利用發(fā)送方的絕對時序來保證消息順序,因為群聊發(fā)送方不單點,時間也不一致。
或許:我們可以利用服務器的單點做序列化。
如上圖所示,此時IM群聊的發(fā)送流程為:
- 1)sender1發(fā)出msg1,sender2發(fā)出msg2;
- 2)msg1和msg2經過接入集群,服務集群;
- 3)service層到底層拿一個唯一seq,來確定接收方展示時序;
- 4)service拿到msg2的seq是20,msg1的seq是30;
- 5)通過投遞服務講消息給多個群友,群友即使接收到msg1和msg2的時間不同,但可以統(tǒng)一按照seq來展現(xiàn)。
這個方法:
- 1)優(yōu)點是:能實現(xiàn)所有群友的消息展示時序相同;
- 2)缺點是:這個生成全局遞增序列號的服務很容易成為系統(tǒng)瓶頸。
還有沒有進一步的優(yōu)化方法呢?
從技術角度看:群消息其實也不用保證全局消息序列有序,而只要保證一個群內的消息有序即可,這樣的話,“消息id序列化”就成了一個很好的思路。
上圖這個方案中,service層不再需要去一個統(tǒng)一的后端拿全局seq(序列號),而是在service連接池層面做細小的改造,保證一個群的消息落在同一個service上,這個service就可以用本地seq來序列化同一個群的所有消息,保證所有群友看到消息的時序是相同的。
關于IM的系統(tǒng)架構下使用怎么樣實現(xiàn)消息序列化,或者說全局消息ID的生成方案,這又是另一個很熱門的技術話題。
有興趣,可以深入閱讀下面這個系列:
《IM消息ID技術專題(一):微信的海量IM聊天消息序列號生成實踐(算法原理篇)》
《IM消息ID技術專題(二):微信的海量IM聊天消息序列號生成實踐(容災方案篇)》
《IM消息ID技術專題(三):解密融云IM產品的聊天消息ID生成策略》
《IM消息ID技術專題(四):深度解密美團的分布式ID生成算法》
《IM消息ID技術專題(五):開源分布式ID生成器UidGenerator的技術實現(xiàn)》
《IM消息ID技術專題(六):深度解密滴滴的高性能ID生成器(Tinyid)》
這個系列中,尤其微信的趨勢遞增ID生成思路(注意:趨勢遞增不是嚴格遞增,趨勢遞增意味著中問有ID被跳過也沒事),對于分布式IM的消息ID來說是非常切實可行的。
是的,對于IM系統(tǒng)來說,絕對意義上的時序很難保證,但通過服務端生成的單調遞增消息ID的方式,利用遞增ID來保證時序性,也是一個很可性的方案。
7、小結一下
IM系統(tǒng)架構下,消息的絕對時序是很困難的,原因多種多樣,比如:沒有全局時鐘、多發(fā)送方、多接收方、多線程、網絡傳輸不確定性等。
一對一單聊時,其實只需要保證發(fā)出的時序與接收的時序一致,就基本能讓用戶感覺不到亂序了。
多對多的群聊情況下,保證同一群內的所有接收方消息時序一致,也就能讓用戶感覺不到亂序了,方法有兩種,一種單點絕對時序,另一種實現(xiàn)消息id的序列化(也就是實現(xiàn)一種全局遞增消息ID)。
8、參考資料
[1] 如何保證IM實時消息的“時序性”與“一致性”?,作者:沈劍
[2] 一個低成本確保IM消息時序的方法探討,作者:封宇
附錄:更多IM開發(fā)熱門技術點
《移動端IM開發(fā)者必讀(一):通俗易懂,理解移動網絡的“弱”和“慢”》
《移動端IM開發(fā)者必讀(二):史上最全移動弱網絡優(yōu)化方法總結》
《現(xiàn)代移動端網絡短連接的優(yōu)化手段總結:請求速度、弱網適應、安全保障》
《移動端IM中大規(guī)模群消息的推送如何保證效率、實時性?》
《移動端IM開發(fā)需要面對的技術問題》
《開發(fā)IM是自己設計協(xié)議用字節(jié)流好還是字符流好?》
《請問有人知道語音留言聊天的主流實現(xiàn)方式嗎?》
《如何保證IM實時消息的“時序性”與“一致性”?》
《一個低成本確保IM消息時序的方法探討》
《IM單聊和群聊中的在線狀態(tài)同步應該用“推”還是“拉”?》
《IM群聊消息如此復雜,如何保證不丟不重?》
《談談移動端 IM 開發(fā)中登錄請求的優(yōu)化》
《移動端IM登錄時拉取數(shù)據如何作到省流量?》
《淺談移動端IM的多點登錄和消息漫游原理》
《完全自已開發(fā)的IM該如何設計“失敗重試”機制?》
《微信對網絡影響的技術試驗及分析(論文全文)》
《IM開發(fā)基礎知識補課(五):通俗易懂,正確理解并用好MQ消息隊列》
《微信技術分享:微信的海量IM聊天消息序列號生成實踐(算法原理篇)》
《IM開發(fā)基礎知識補課(六):數(shù)據庫用NoSQL還是SQL?讀這篇就夠了!》
《IM里“附近的人”功能實現(xiàn)原理是什么?如何高效率地實現(xiàn)它?》
《IM的掃碼登錄功能如何實現(xiàn)?一文搞懂主流應用的掃碼登錄技術原理》
《IM開發(fā)寶典:史上最全,微信各種功能參數(shù)和邏輯規(guī)則資料匯總》
本文已同步發(fā)布于“即時通訊技術圈”公眾號。

▲ 本文在公眾號上的鏈接是:點此進入,原文鏈接是:http://www.52im.net/thread-3189-1-1.html