訊息已讀、未讀是怎麼設計的?

ML李嘉圖發表於2022-05-03

比如企業微信、釘釘裡面的群訊息的有個已讀未讀的功能,傳送者剛發出訊息時,當前群裡其他群成員都是未讀狀態,陸陸續續有人看了這個訊息,這時候訊息的詳情變成x人已讀,y人未讀。

每條訊息對應一個唯一的 messageid(uint64_t),每個使用者對應一個唯一的 userid(uint64_t),應該如何儲存這個訊息對應的已讀未讀詳情呢?


其實未讀已讀就是一個0/1的標記而已,可以維護一個Bitmap來實現呢。

儲存 userid 到自增 mapid 的對映

struct UserInfo

這樣群成員每加入一個群裡,就有 mapid<->usreid 的雙向對映了。

假如群裡有5個成員 ABCDE, 那就對應 mapid 1-5,messageid 對應的訊息詳情儲存就可以設計成

{ uint32_t maxid, uint8_t readbit[]}

如上面的案例就是 {5, readbit[0] = bin(0000 0000)}; 就佔用了 5B(4+1) 。

A發訊息,D已讀訊息時,就更新成 {5,readbit[0]= bin(0000 1000)}。

其餘4人都已讀訊息時,更新為 {5, readbit[0]=bin(0001 1110)} 。


這是個粗略的方案,裡面還有一些細節值得思考:

  1. 退出的成員呢?

    比如C退出群,發訊息時maxid還是5,已讀 + 未讀總人數應該是3(不包括髮訊息者本人),目前資訊只有5個bit(0/1),識別不出來誰已經退出群聊了。

  2. 退出群聊的成員如何處理?從GruopMetaInfo裡面刪除麼?退出群聊成員重新加入又如何分配id呢?


首先2這個點:

退出群聊的成員只能標記刪除,不能物理刪除,不然客戶端展示已讀未讀詳情時,通過mapid找不到對應的userid,退出的成員又重新加入群聊這個就好辦了,把標記刪除改成非標記刪除,還是用舊的mapid。


至於1呢:

可以再加多一個 Bitmap ,記錄成員在訊息傳送時是否已經退出群聊了,退出群聊就置為1。


所以最終方案就是:

群資訊增加userid,自增mapid雙向對映,退出群聊成員標記刪除。

messageid 已讀未讀詳情儲存 {maxid, readbit[], quitbit[]}

新的方案帶來怎樣的收益呢?

  1. 增加自增mapid欄位,一個群聊維護一份,成本幾乎可以忽略不計。

  2. 每個成員已讀未讀由8B(64bit)優化成2bit,減少62/64, 200人已讀未讀舊的方案1600B,現在只需要(200/8) * 2 + 4 = 54 , 每條訊息節約95%+。


如果maxid如果到百萬甚至千萬級別,那豈不是災難?

一般實際場景,群聊是會限制人數的,就算不斷踢人加新人,那maxid最多也只能到企業人數。

如果maxid達到一個特別大數字,已讀未讀對應的儲存可以增加多一個flag,如果bitmap儲存成本遠超過最初的方案,可以用最初的方案來實現,客戶端提前埋好相容邏輯。

相關文章