我們當(dāng)前的IM雖然進(jìn)行了微服務(wù)化,但是核心的消息投遞模式仍然采用下圖描繪的方式,參看《一個(gè)海量在線用戶即時(shí)通訊系統(tǒng)(IM)的完整設(shè)計(jì)》。
創(chuàng)新互聯(lián)建站是網(wǎng)站建設(shè)專家,致力于互聯(lián)網(wǎng)品牌建設(shè)與網(wǎng)絡(luò)營銷,專業(yè)領(lǐng)域包括成都網(wǎng)站制作、網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)、電商網(wǎng)站制作開發(fā)、小程序設(shè)計(jì)、微信營銷、系統(tǒng)平臺(tái)開發(fā),與其他網(wǎng)站設(shè)計(jì)及系統(tǒng)開發(fā)公司不同,我們的整合解決方案結(jié)合了恒基網(wǎng)絡(luò)品牌建設(shè)經(jīng)驗(yàn)和互聯(lián)網(wǎng)整合營銷的理念,并將策略和執(zhí)行緊密結(jié)合,且不斷評(píng)估并優(yōu)化我們的方案,為客戶提供全方位的互聯(lián)網(wǎng)品牌整合方案!
在這個(gè)方式下,消息同步的基本思路和步驟如下(序號(hào)不對(duì)應(yīng)圖中序號(hào))
1、把消息存儲(chǔ)到離線收件箱
2、向在線用戶推送消息
3、在線用戶返回收到消息的ack信息
4、服務(wù)端清除用戶此條離線消息
對(duì)于離線用戶,登錄后直接拉取離線消息即可
這個(gè)消息同步方式有它合理的地方
1、流程比較直觀
2、網(wǎng)絡(luò)交互量較少(相對(duì)于后邊的TimeLine模型而言)
但是這個(gè)方案存在更多不足的地方
1、我們有App和Web兩個(gè)端,需要為每個(gè)端都寫一份離線消息。由于離線消息是擴(kuò)散寫的,多寫一份,服務(wù)端就多一份壓力
2、消息ack回來之后,服務(wù)端需要把對(duì)應(yīng)的消息從存儲(chǔ)中刪除,這個(gè)過程性能也是一個(gè)問題
這個(gè)消息模式在比較單一的IM應(yīng)用場(chǎng)景下還是能夠勝任的。但是隨著消息場(chǎng)景越來越復(fù)雜,尤其是SDK推出以后,這個(gè)模式就存在很多弊端。SDK的應(yīng)用可能存在很多個(gè)端,服務(wù)端不可能為每個(gè)端都寫離線消息!
對(duì)于SDK,我們采用TimeLine模型來實(shí)現(xiàn)客戶端和服務(wù)端的消息同步。
以下內(nèi)容是釘釘?shù)淖龇?/strong>,比較了傳統(tǒng)架構(gòu)和現(xiàn)代架構(gòu)。而我們現(xiàn)在的IM消息同步這塊介于兩者之間。
傳統(tǒng)架構(gòu)下,消息是先同步后存儲(chǔ)。對(duì)于在線的用戶,消息會(huì)直接實(shí)時(shí)同步到在線的接收方,消息同步成功后,并不會(huì)進(jìn)行持久化。而對(duì)于離線的用戶或者消息無法實(shí)時(shí)同步成功時(shí),消息會(huì)持久化到離線庫,當(dāng)接收方重新連接后,會(huì)從離線庫拉取所有未讀消息。當(dāng)離線庫中的消息成功同步到接收方后,消息會(huì)從離線庫中刪除。傳統(tǒng)的消息系統(tǒng),服務(wù)端的主要工作是維護(hù)發(fā)送方和接收方的連接狀態(tài),并提供在線消息同步和離線消息緩存的能力,保證消息一定能夠從發(fā)送方傳遞到接收方。服務(wù)端不會(huì)對(duì)消息進(jìn)行持久化,所以也無法支持消息漫游。
現(xiàn)代架構(gòu)下,消息是先存儲(chǔ)后同步。先存儲(chǔ)后同步的好處是,如果接收方確認(rèn)接收到了消息,那這條消息一定是已經(jīng)在云端保存了。并且消息會(huì)有兩個(gè)庫來保存,一個(gè)是消息存儲(chǔ)庫,用于全量保存所有會(huì)話的消息,主要用于支持消息漫游。另一個(gè)是消息同步庫,主要用于接收方的多端同步。消息從發(fā)送方發(fā)出后,經(jīng)過服務(wù)端轉(zhuǎn)發(fā),服務(wù)端會(huì)先將消息保存到消息存儲(chǔ)庫,后保存到消息同步庫。完成消息的持久化保存后,對(duì)于在線的接收方,會(huì)直接選擇在線推送。但在線推送并不是一個(gè)必須路徑,只是一個(gè)更優(yōu)的消息傳遞路徑。對(duì)于在線推送失敗或者離線的接收方,會(huì)有另外一個(gè)統(tǒng)一的消息同步方式。接收方會(huì)主動(dòng)的向服務(wù)端拉取所有未同步消息,但接收方何時(shí)來同步以及會(huì)在哪些端來同步消息對(duì)服務(wù)端來說是未知的,所以要求服務(wù)端必須保存所有需要同步到接收方的消息,這是消息同步庫的主要作用。對(duì)于新的同步設(shè)備,會(huì)有消息漫游的需求,這是消息存儲(chǔ)庫的主要作用,在消息存儲(chǔ)庫中,可以拉取任意會(huì)話的全量歷史消息。
以上是傳統(tǒng)架構(gòu)和現(xiàn)代架構(gòu)的一個(gè)簡(jiǎn)單的對(duì)比,現(xiàn)代架構(gòu)上整個(gè)消息的同步和存儲(chǔ)流程,并沒有變復(fù)雜太多,但是其能實(shí)現(xiàn)多端同步以及消息漫游。現(xiàn)代架構(gòu)中最核心的就是兩個(gè)消息庫『消息同步庫』和『消息存儲(chǔ)庫』,是消息同步和存儲(chǔ)最核心的基礎(chǔ)。
我們看看Timeline模型是怎么樣的?
如圖是Timeline模型的一個(gè)抽象表述,Timeline可以簡(jiǎn)單理解為是一個(gè)消息隊(duì)列,但這個(gè)消息隊(duì)列有如下特性:
每個(gè)消息擁有一個(gè)順序ID(SeqId),在隊(duì)列后面的消息的SeqId一定比前面的消息的SeqId大,也就是保證SeqId一定是增長(zhǎng)的,但是不要求嚴(yán)格遞增。
新的消息永遠(yuǎn)在尾部添加,保證新的消息的SeqId永遠(yuǎn)比已經(jīng)存在隊(duì)列中的消息都大。
可根據(jù)SeqId隨機(jī)定位到具體的某條消息進(jìn)行讀取,也可以任意讀取某個(gè)給定范圍內(nèi)的所有消息。
有了這些特性后,消息的同步可以拿Timeline來很簡(jiǎn)單的實(shí)現(xiàn)。圖中的例子中,消息發(fā)送方是A,消息接收方是B,同時(shí)B存在多個(gè)接收端,分別是B1、B2和B3。A向B發(fā)送消息,消息需要同步到B的多個(gè)端,待同步的消息通過一個(gè)Timeline來進(jìn)行交換。A向B發(fā)送的所有消息,都會(huì)保存在這個(gè)Timeline中,B的每個(gè)接收端都是獨(dú)立的從這個(gè)Timeline中拉取消息。每個(gè)接收端同步完畢后,都會(huì)在本地記錄下最新同步到的消息的SeqId,即最新的一個(gè)位點(diǎn),作為下次消息同步的起始位點(diǎn)。服務(wù)端不會(huì)保存各個(gè)端的同步狀態(tài)(我認(rèn)為服務(wù)端也可以記錄各端的同步點(diǎn)位),各個(gè)端均可以在任意時(shí)間從任意點(diǎn)開始拉取消息。
看完TimeLine模型,我存在過困擾。既有推送又有拉取,客戶端怎么確定同步點(diǎn)位究竟在哪里呢?尤其是用戶打開軟件,拉取同步過程中有新消息到了怎么辦?
這里要感謝彬哥(LinkedIn的大牛)提示,他說他們的消息都是拉取的。既然消息是拉取的,那推送的又是什么呢?
仔細(xì)看現(xiàn)代架構(gòu)的圖,第3步寫的是“推送通知”。推送的是有新消息的提示信息,客戶端收到這個(gè)通知就拉取同步消息,客戶端和服務(wù)端各自維護(hù)這個(gè)端的同步點(diǎn)位(為了節(jié)省網(wǎng)絡(luò)交互,客戶端拉取同步消息后,不需要向服務(wù)端確認(rèn),因此客戶端和服務(wù)端維護(hù)的同步點(diǎn)位不完全一致,但是不影響業(yè)務(wù)邏輯,這個(gè)細(xì)節(jié)后續(xù)單獨(dú)寫文章介紹)。由于只存在拉取消息,同步點(diǎn)位的維護(hù)就變得很簡(jiǎn)單了,客戶端保存拉取到的最新消息的ID(SeqId)即可。
至此,支持多端的消息同步模型已經(jīng)成型。
那么這個(gè)方案還有沒有優(yōu)化空間呢?
這個(gè)方式跟我們現(xiàn)在的方式相比增加了網(wǎng)絡(luò)交互次數(shù),有沒有辦法能夠節(jié)省網(wǎng)絡(luò)開銷,有享受TimeLine模型對(duì)多端友好的支持呢?
看過一篇文章介紹微信為每個(gè)用戶的消息ID進(jìn)行了嚴(yán)格遞增編號(hào),也就是為每個(gè)用戶的TimeLine模型的消息進(jìn)行了嚴(yán)格遞增的編號(hào)。既該用戶第一條消息序號(hào)為1,第二條為2以此類推。
這樣一個(gè)編號(hào)服務(wù),開發(fā)成本還是比較高的,那微信為什么要做呢?我現(xiàn)在認(rèn)為其中一個(gè)原因是為了減少網(wǎng)絡(luò)交互。采用推通知,再拉取同步消息的方式,畢竟要多一次網(wǎng)絡(luò)交互。如果消息嚴(yán)格編號(hào),可以將傳統(tǒng)的推消息和新的推通知的方式結(jié)合起來。推往客戶端的消息帶有嚴(yán)格遞增的消息ID,客戶端可以根據(jù)消息ID計(jì)算出是否需要拉取同步消息(如果推過來的消息ID只比客戶端最大的消息ID大1,則沒有必要拉取同步消息)。
實(shí)施層面同樣存在不少挑戰(zhàn),難點(diǎn)是如何將邏輯模型映射到物理模型或具體中間件,細(xì)節(jié)后續(xù)再介紹。