IM系統海量訊息資料是怎麼儲存的?

普通程式設計師發表於2019-03-26

一、與訊息相關的主要場景

1、儲存和離線訊息。

現在的IM系統,訊息都要落地儲存。這樣如果接收訊息的使用者不線上,等他下次上線時,能獲取到訊息資料。

2、訊息漫遊

訊息漫遊包括主要兩種場景,

(1)使用者新安裝IM軟體,要能看到以前的聊天記錄

(2)聊天軟體有PC版和App版,在App上聊的天,開啟PC版要能夠看到

二、不同場景讀取 訊息關鍵點

1、拉取離線訊息

每個使用者開啟App就需要拉取離線,網路中斷重連後要拉取離線,收到訊息序列號不連續也要拉取離線,拉取離線訊息是一個高頻操作 。離線訊息包括單聊、群聊、控制類等訊息,訊息型別型別眾多。因此離線訊息需要以使用者ID(多端情況下需要以端)為檢索維度。說的直白一點,就是每個人(端)都需要一個收件箱,拉離線訊息就是把個人(端)收件箱裡的訊息取到客戶端。

2、訊息漫遊

訊息漫遊的典型使用場景是,開啟某個會話(單聊、群聊、公眾號),下拉介面,客戶端向服務端請求這個會話的聊天資料。訊息漫遊需要以會話為檢索維度。訊息漫遊拉取資料的頻率相對較低。我們把這類獲取訊息的方式成為拉取歷史訊息。

三、儲存訊息關鍵點

1、離線訊息

離線訊息讀取頻繁(寫也有一定壓力),但是檢索邏輯簡單(參看《一個海量線上使用者即時通訊系統(IM)的完整設計》拉取離線訊息章節)。我們採用記憶體資料庫(Redis)儲存,主要結構使用SortedSet(可以有更高效的儲存結構,但Redis不支援)。對於群訊息,採用擴散寫方式(一條群訊息給每個群成員都寫一份)。按照訊息接受者ID水平分庫

2、歷史訊息

歷史訊息的訪問頻率低,但是每條訊息都需要儲存,我們採用關係型資料庫(MySQL)儲存,重點考慮寫入效率。對於群訊息,採用擴散讀方式(每條群訊息只寫一條記錄)。按照訊息傳送者ID(單聊),或群ID(群聊)進行水平分庫

四、訊息存取方案

1、離線訊息

儲存離線訊息。按照訊息接收者ID(toID),取模Hash分庫(也可以用一致性Hash)。每個使用者建立一個SortedSet結構的Key,用於儲存離線訊息。離線訊息按照時間先後順序排列即可。SortedSet新增一個元素時間複雜度是O(log(N)),N 是有序集的基數,由於離線訊息的msgid是有序的,所以實際插入時間複雜度很可能退化為O(1)

IM系統海量訊息資料是怎麼儲存的?

讀取離線訊息。離線訊息讀取策略參看《一個海量線上使用者即時通訊系統(IM)的完整設計》拉取離線訊息章節。理論上讀取離線訊息的時間複雜度為O(log(N)+M), N 為離線訊息的條數, M 為一次讀取訊息的條數。實際上,由於離線訊息從有序集的頭部開始讀取,實際時間複雜度比這個值低

2、歷史訊息

歷史訊息分為兩大類,單聊訊息、群聊訊息。

單聊訊息按照傳送者ID(fromId)水平(取模Hash)分庫,存到一張資料表(例如叫msg_user_send)中。核心欄位包括msgid(訊息ID),fromId(傳送者Id),toId(接收者Id),content(訊息內容)。

拉取單聊歷史訊息時(假設拉取userId1跟userId2的聊天),分別讀取兩人給對方傳送的訊息(因為分庫原因,兩人傳送的訊息可能分佈在不同資料庫中),然後進行Merge。

群聊訊息按照群ID(groupId)水平(取模Hash)分庫,存到一張表中(例如叫:msg_group)。核心欄位包括msgid(訊息ID),fromId(傳送者Id),groupId(群Id),content(訊息內容)。

另外一張msg_group_user表記錄使用者加入群的時間,如下圖。某個人(如張三)加入群的時間,相當於一個遊標,群訊息表中,這個遊標之後的聊天訊息,是這個人(張三)能夠檢視的資料(當然,也可以做檢視加入群之前若干條訊息)。

IM系統海量訊息資料是怎麼儲存的?

拉取群歷史訊息,直接倒序讀取這個群訊息表資料即可。

由於MySQL和Redis都採用了水平分庫,儲存能力幾乎可以線性擴充套件!是不是這樣就足夠了呢?答案是否定的,最佳化永遠沒有盡頭。如果我在非洲某個國家登入系統,從北京的機房讀取訊息資料顯然不太合適!如何讓資料靠近使用者,是一個更加有挑戰的問題。

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

相關文章