基於TimeLine模型的訊息同步機制

普通程式設計師發表於2018-11-08

我們當前的IM雖然進行了微服務化,但是核心的訊息投遞模式仍然採用下圖描繪的方式,參看《一個海量線上使用者即時通訊系統(IM)的完整設計》。


基於TimeLine模型的訊息同步機制

在這個方式下,訊息同步的基本思路和步驟如下(序號不對應圖中序號)

1、把訊息儲存到離線收件箱

2、向線上使用者推送訊息

3、線上使用者返回收到訊息的ack資訊

4、服務端清除使用者此條離線訊息

對於離線使用者,登入後直接拉取離線訊息即可

這個訊息同步方式有它合理的地方

1、流程比較直觀

2、網路互動量較少(相對於後邊的TimeLine模型而言)

但是這個方案存在更多不足的地方

1、我們有App和Web兩個端,需要為每個端都寫一份離線訊息。由於離線訊息是擴散寫的,多寫一份,服務端就多一份壓力

2、訊息ack回來之後,服務端需要把對應的訊息從儲存中刪除,這個過程效能也是一個問題

這個訊息模式在比較單一的IM應用場景下還是能夠勝任的。但是隨著訊息場景越來越複雜,尤其是SDK推出以後,這個模式就存在很多弊端。SDK的應用可能存在很多個端,服務端不可能為每個端都寫離線訊息!

對於SDK,我們採用TimeLine模型來實現客戶端和服務端的訊息同步。

以下內容是釘釘的做法,比較了傳統架構現代架構。而我們現在的IM訊息同步這塊介於兩者之間。 


基於TimeLine模型的訊息同步機制

傳統架構,訊息是先同步後儲存。對於線上的使用者,訊息會直接實時同步到線上的接收方,訊息同步成功後,並不會進行持久化。而對於離線的使用者或者訊息無法實時同步成功時,訊息會持久化到離線庫,當接收方重新連線後,會從離線庫拉取所有未讀訊息。當離線庫中的訊息成功同步到接收方後,訊息會從離線庫中刪除。傳統的訊息系統,服務端的主要工作是維護髮送方和接收方的連線狀態,並提供線上訊息同步和離線訊息快取的能力,保證訊息一定能夠從傳送方傳遞到接收方。服務端不會對訊息進行持久化,所以也無法支援訊息漫遊。

基於TimeLine模型的訊息同步機制


現代架構下,訊息是先儲存後同步。先儲存後同步的好處是,如果接收方確認接收到了訊息,那這條訊息一定是已經在雲端儲存了。並且訊息會有兩個庫來儲存,一個是訊息儲存庫,用於全量儲存所有會話的訊息,主要用於支援訊息漫遊。另一個是訊息同步庫,主要用於接收方的多端同步。訊息從傳送方發出後,經過服務端轉發,服務端會先將訊息儲存到訊息儲存庫,後儲存到訊息同步庫。完成訊息的持久化儲存後,對於線上的接收方,會直接選擇線上推送。但線上推送並不是一個必須路徑,只是一個更優的訊息傳遞路徑。對於線上推送失敗或者離線的接收方,會有另外一個統一的訊息同步方式。接收方會主動的向服務端拉取所有未同步訊息,但接收方何時來同步以及會在哪些端來同步訊息對服務端來說是未知的,所以要求服務端必須儲存所有需要同步到接收方的訊息,這是訊息同步庫的主要作用。對於新的同步裝置,會有訊息漫遊的需求,這是訊息儲存庫的主要作用,在訊息儲存庫中,可以拉取任意會話的全量歷史訊息。

以上是傳統架構和現代架構的一個簡單的對比,現代架構上整個訊息的同步和儲存流程,並沒有變複雜太多,但是其能實現多端同步以及訊息漫遊。現代架構中最核心的就是兩個訊息庫『訊息同步庫』和『訊息儲存庫』,是訊息同步和儲存最核心的基礎。

我們看看Timeline模型是怎麼樣的?

基於TimeLine模型的訊息同步機制

如圖是Timeline模型的一個抽象表述,Timeline可以簡單理解為是一個訊息佇列,但這個訊息佇列有如下特性:

  • 每個訊息擁有一個順序ID(SeqId),在佇列後面的訊息的SeqId一定比前面的訊息的SeqId大,也就是保證SeqId一定是增長的,但是不要求嚴格遞增

  • 新的訊息永遠在尾部新增,保證新的訊息的SeqId永遠比已經存在佇列中的訊息都大

  • 可根據SeqId隨機定位到具體的某條訊息進行讀取,也可以任意讀取某個給定範圍內的所有訊息。

有了這些特性後,訊息的同步可以拿Timeline來很簡單的實現。圖中的例子中,訊息傳送方是A,訊息接收方是B,同時B存在多個接收端,分別是B1、B2和B3。A向B傳送訊息,訊息需要同步到B的多個端,待同步的訊息透過一個Timeline來進行交換。A向B傳送的所有訊息,都會儲存在這個Timeline中,B的每個接收端都是獨立的從這個Timeline中拉取訊息。每個接收端同步完畢後,都會在本地記錄下最新同步到的訊息的SeqId,即最新的一個位點,作為下次訊息同步的起始位點。服務端不會儲存各個端的同步狀態(我認為服務端也可以記錄各端的同步點位),各個端均可以在任意時間從任意點開始拉取訊息。

看完TimeLine模型,我存在過困擾既有推送又有拉取,客戶端怎麼確定同步點位究竟在哪裡呢?尤其是使用者開啟軟體,拉取同步過程中有新訊息到了怎麼辦?

這裡要感謝彬哥(LinkedIn的大牛)提示,他說他們的訊息都是拉取的。既然訊息是拉取的,那推送的又是什麼呢?

仔細看現代架構的圖,第3步寫的是“推送通知”。推送的是有新訊息的提示資訊,客戶端收到這個通知就拉取同步訊息,客戶端和服務端各自維護這個端的同步點位(為了節省網路互動,客戶端拉取同步訊息後,不需要向服務端確認,因此客戶端和服務端維護的同步點位不完全一致,但是不影響業務邏輯,這個細節後續單獨寫文章介紹)。由於只存在拉取訊息,同步點位的維護就變得很簡單了,客戶端儲存拉取到的最新訊息的ID(SeqId)即可。

至此,支援多端的訊息同步模型已經成型。

那麼這個方案還有沒有最佳化空間呢?

這個方式跟我們現在的方式相比增加了網路互動次數,有沒有辦法能夠節省網路開銷,有享受TimeLine模型對多端友好的支援呢?

看過一篇文章介紹微信為每個使用者的訊息ID進行了嚴格遞增編號,也就是為每個使用者的TimeLine模型的訊息進行了嚴格遞增的編號。既該使用者第一條訊息序號為1,第二條為2以此類推。

這樣一個編號服務,開發成本還是比較高的,那微信為什麼要做呢?我現在認為其中一個原因是為了減少網路互動。採用推通知,再拉取同步訊息的方式,畢竟要多一次網路互動。如果訊息嚴格編號,可以將傳統的推訊息和新的推通知的方式結合起來。推往客戶端的訊息帶有嚴格遞增的訊息ID,客戶端可以根據訊息ID計算出是否需要拉取同步訊息(如果推過來的訊息ID只比客戶端最大的訊息ID大1,則沒有必要拉取同步訊息)。 

實施層面同樣存在不少挑戰,難點是如何將邏輯模型對映到物理模型或具體中介軟體,細節後續再介紹。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31556438/viewspace-2219243/,如需轉載,請註明出處,否則將追究法律責任。

相關文章