消息時序
理想狀態下,客戶端和服務端數據是一致的。實際情況,涉及到用戶上線或下線。(詳見下圖)
- 用戶在線:服務實時發送消息。
- 用戶離線:服務保存消息;用戶重新上線後,向服務獲取離線消息。
-
羣組離線消息數據(分頁獲取)。
如上圖:client 每條消息都是有時序的,像鏈表一樣,串聯起來,每個 node 都可以通過 next 指向上一條消息:
- 如果上一條消息 msg_id 是 0,說明當前結點是第一條消息(如上圖 msg_id == 1 的消息)。
- 如果上一條消息 msg_id 不是 0,且消息存在於本地,那麼消息是連續的,不需要向服務同步(如上圖 msg_id == 2 的消息)。
- 如果上一條消息 msg_id 不是 0,但本地消息不存在,那麼需要向服務器獲取。(如上圖 msg_id == 9 的消息)。
終端通過消息鏈表方式的檢查,很容易確認是否需要向服務同步數據。
-
羣組未讀消息總條數。
從 client 的緩存中提取最新(lastest)的 msg_id,對應消息體有 recv_time。
服務端消息的時序通過 redis 的 sortset 存儲的:
key: group_id, score: recv_time, value: msg_id
redis 的 sortset 結構,很容易通過一個 score 獲取一個區間的數據總數。
redis 設計
-
sortset 存儲存儲消息時序
key: group_id, score: recv_time, value: msg_id
-
string 存儲消息體
key: msg_id, value: msg_body
因爲消息體數量較多,而且活躍時間比較短(因爲大部分用戶只關心最近接收的消息),所以把它獨立出來。便於 timeout 後 redis 能刪除節省內存。
-
set 存儲未讀消息對象
key: uid, member:group_id/send_uid
每個用戶都可能有 N 個羣組,N 個好友。用戶重新上線後,不可能遍歷所有好友或羣組對象。所以服務在處理離線消息時,需要記錄未讀消息對象。
database 設計
-
羣組和羣組成員關係
group_id, uid
-
消息結構
msg_id, group_id, send_uid, recv_uid, recv_time, msg_body
服務存儲架構
im 即時通訊,服務是讀多寫少類型。服務端有三層存儲(如下圖),通過熱點數據的緩存,讓服務高效讀取。
- msg server 服務進程內存 session 緩存熱點數據。
緩存當前活躍的數據:頭像信息,用戶名稱,消息實體等數據,緩存一般 5 - 30 分鐘,根據具體的業務需要
- redis 第二層緩存熱點數據。
緩存大量的熱點數據,減少對 db 的訪問頻率,緩存時間相對較長,幾個月不等。
- database 數據落地。
數據讀寫時序
寫數據
讀數據
總結
基於以上分析,羣組消息,每個用戶發送的消息,不需要針對每個羣組成員都存一條到 database,否則千人羣組,每個成員存一條數據就是一千條,那麼消息的存儲就是個大坑。每個用戶發送消息 database 只需要存一條即可。通過多級緩存的架構,服務的性能一般體量的消息實時通訊是沒有問題的。當然這裏面還有很多細節問題需要在實際的業務場景中調優。