IM 消息數據存儲結構設計

1.背景


在移動互聯網高速發展的時代,生活中 IM 類產品已經是我們離不開的應用了,像微信、釘釘等都是以 IM 爲核心功能的社交產品。另外也有一些應用不是以 IM 爲核心,但是也是其重要功能,比如在線遊戲、電商直播等應用。


在 IM 龐大的體系中,消息系統無疑是最核心的,而消息系統中,最關鍵的部分是消息的分發和存儲。


在以往傳統消息系統中,對於在線的用戶,消息會直接實時發送到在線的接收方,消息發送完成後,服務器端並不會對消息進行落地存儲。對於離線的用戶,服務器端會將消息存入到離線庫,當用戶登錄後,從離線庫中將離線消息拉走,然後服務器端將離線消息刪除,這樣的缺點是消息不持久化,導致消息無法支持消息漫遊,降低了消息的可靠性。而在我們的消息系統中,服務器只要接收到了發送方發上來的消息,在轉發給接收方的同時也會在離線數據庫以及歷史消息庫中進行消息的落地存儲,消息的落地也就支持了整體的消息漫遊等相關功能。


2.離線消息和歷史消息的區別


離線消息,就是用戶在離線過程中收到的消息,這些消息大多是用戶比較關心的消息,具有一定的時效性。我們的系統設計,離線消息默認只保存最近七天的消息。離線消息在用戶登錄後會全量的獲取,然後客戶端根據會話進行整體離線消息的展示。


歷史消息,存儲了用戶所有的消息,包括髮的消息以及接收的消息。在客戶端獲取歷史消息時,是按照會話進行分頁獲取的。歷史消息的存儲時間我們系統設計默認爲半年,當然這個是可配置的。


3.消息的發送以及存儲的流程


融雲整體的消息發送以及存儲的流程如下圖所示:


圖片圖片


用戶發送消息到服務器端後,首先會進入到消息系統中,消息系統會對消息進行分發以及存儲。對於在線的接收方,會選擇直接推送消息,但是遇到接收方不在線或者是消息推送失敗的情況下,也會有另外的消息獲取方式,接收方會主動向服務器拉取未收到的消息,但是接收方何時來服務器拉取消息以及從哪裏拉取是未知的,所以消息存入到離線庫的意義也就在這裏。


消息系統存儲離線的過程中,爲了不影響整個系統的更爲平穩,融雲使用了消息隊列,消息是異步存入到離線庫中的。


在分發完消息後,消息服務會同步一份消息數據到歷史消息服務中,歷史消息服務會對消息進行落地存儲。對於新的同步設備,會有消息漫遊的需求,這也是歷史消息的主要作用。在歷史消息庫中,客戶端可以拉取任意會話的全量歷史消息。


4.離線消息以及歷史消息存儲區別


上述的圖中我們能清晰的看到,離線消息我們存儲介質選用的是 Redis,歷史消息我們選用的是 HBase。爲何選用不同的存儲介質針對的是不同的業務場景和讀寫模式。下面我們重點介紹一下離線消息和歷史消息存儲的區別。


離線消息的存儲模式是放大寫,如下圖所示,每個用戶都有自己單獨的收件箱和發件箱,收件箱存放需要向這個接收端同步的所有消息,發件箱裏存的是發送端發送的所有消息。二人會話中的消息會產生兩次寫,發送者的發件箱以及接收端的收件箱。而在羣的場景下,寫入會被更加的放大,如果羣裏有 N 個人,那一條羣消息就會被放大寫 N 次。


放大寫的優點是,接收端的邏輯會非常清晰簡單,只需要從收件箱裏讀取一次即可,大大降低了同步消息所需的讀的壓力,但是缺點就是寫入會被放大,特別是針對羣這種場景。


圖片圖片


歷史消息的存儲模式是放大讀,因爲歷史消息中,每個會話都保存了整個會話的全量消息。在放大讀這種模式下,每個會話的消息只保存一次。相比放大寫的那種模式,寫入次數大大降低,特別是針對羣消息,只需要存一次即可。但是缺點是接收端接收消息非常的複雜和低效,因爲這種模式客戶端想拉取到所有消息就只能每個會話同步一次,讀就會被放大,而且可能會產生很多次無效的讀,因爲有些會話可能根本沒有新消息。


圖片圖片

在 IM 這種應用場景下,通常會用到寫擴散這種消息同步模型,一條消息產生一條,但是可能會被讀多次,是典型的讀多寫少的場景。一個優化好的系統,必須從設計上平衡讀寫壓力,避免讀或者寫任意一個維度達到天花板。當然寫擴展這種模式也有其弊端,比如萬人羣,會導致一條消息,寫入了一萬次。綜合來講,我們需要根據自己的業務場景做相應設計選擇,我們的系統是根據了離線和歷史消息的不同場景選擇了寫擴散和讀擴散的組合模式。


5.客戶端拉取消息


離線消息的獲取針對的是自己的整個離線消息,包括所有的會話。離線消息的獲取是自上而下的方式,一次獲取 200 條。在客戶端拉取離線消息的信令中,需要帶上當前客戶端緩存的消息的最大時間戳,上面的圖我們應該知道,離線消息我們存儲的是一個線性結構,Server 會根據這個時間戳向下查找離線消息,重裝或者新安裝 App 時,客戶端可以傳 0 上來,Server 也會緩存客戶端拉取到的最後一條消息的時間戳,然後根據業務場景,客戶端類型等因素來決定從哪裏開始拉取,如果沒有拉取完 Server 會在拉取消息的應答中帶相應的標記位,告訴客戶端繼續拉取,客戶端循環拉取,直到所有離線消息拉完。


歷史消息的獲取針對的是單一會話,在拉取過程中需要帶上來對方的 ID(如果是單聊的話就是對方的 UserID,如果是羣,則是羣組 ID 以及當前會話的最前面消息的時間戳,Server 會定位到這個人的這個會話然後一次獲取 20 條,採用的是自下而上的方式,即從最後面往前翻。只要有消息,客戶端可以一直向前翻,手動觸發獲取會話的歷史消息。

 

6.總結


本篇文章主要講了 IM 中消息系統的消息分發、存儲等,重點介紹了離線消息和歷史消息的區別以及兩者存儲中所選用的不同存儲方式以及其優缺點。關於文中內容,也歡迎大家隨時留言討論。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章