IM羣聊消息的已讀未讀功能在存儲空間方面的實現思路探討

本文由做者「fzully」投稿,收錄時,有較大範圍修訂和改動,感謝原做者的分享。html

一、引言

IM系統中,特別是在企業應用場景下,消息的已讀未讀狀態是一個強需求。緩存

以阿里的釘釘爲例,釘釘的產品定位是用於商務交流,其「強制已讀回執」功能,讓職場人沒法再「僞裝不在線」、「僞裝沒收到」。更有甚者,釘釘的羣聊「強制已讀回執」功能,甚至可以知道誰讀了消息,誰沒有讀消息(老闆的福音啊)。微信

▲ 釘釘裏的羣聊消息已讀未讀功能效果分佈式

功能看起來很酷,但用起來是一言難盡(上班族內心苦.... )。實際上,技術實現也並不容易。性能

那麼,對於已讀未讀狀態:優化

  • 1)若是是私聊:消息的閱讀狀態比較容易實現,在性能和存儲上也不存在問題;
  • 2)若是是羣聊:考慮到存儲和處理性能,特別當處於一個雲環境時,如何高效地處理羣聊的已讀未讀狀態是一個很是值得探討的話題。

這裏提到的「高效」含3個方面:url

  • 1)存儲空間;
  • 2)處理速度;
  • 3)傳輸字節數。

本文將從服務端的角度來探討已讀未讀狀態,在具體的技術實現上對於存儲空間佔用方面的思路差別。能力有限,權當我的筆記,歡迎交流。spa

本文已同步發佈於「即時通信技術圈」公衆號。.net

二、內容點評

在收錄本文前,Jack Jiang建議原做者對某些具體的技術點進行更深刻的分享,但因做者工做較忙,本文中的某些關鍵技術點將來的及做進一步展開。設計

因此,本文能夠做爲IM聊天消息(主要是羣聊)中已讀未讀功能的基本實現思路方面的參考,但不建議盲目迷信文中的結論或方案,避免被一些不夠具體的技術指標而誤導。

三、相關文章

若是你還想了解更多有關IM羣聊中已讀未讀功能的實現邏輯,能夠進一步閱讀乾貨文章《IM羣聊消息的已讀回執功能該怎麼實現?》(強烈推薦)。

若是你對IM中的已讀未讀功能有產品方面的痛點困惑,能夠參考一下微信對已讀未讀功能的設計定位,詳見《IM熱門功能思考:爲何微信裏沒有消息「已讀」功能?》。

更多IM羣聊技術方面的文章詳見文本附錄部分。

四、已讀未讀狀態交互流程

發送者發送的IM聊天消息,在接收者閱讀消息後,是否要求閱讀者通知已讀,多是由系統配置、組織配置、羣組配置等決定,也可能由發送者根據業務需求決定。如下的討論,均假設消息須要已讀未讀狀態。

客戶端與服務端之間,關於閱讀狀態的命令只需3個,每一個命令含請求和應答。

4.1 通知消息已讀(私聊、羣聊通用)

當小寶閱讀了一條或若干條消息,需向服務端發送消息已讀通知:「衆愛卿發的x+y+z消息,朕已閱」。

服務端收到小寶的已讀通知時,需完成如下事項:

  • 1)存儲消息的已讀狀態;
  • 2)返回應答給小寶;
  • 3)向已讀列表的消息的原始發送者通知消息已讀。

對於第「3)」步:

  • 1)私聊的場合,比較好理解,就是發送給私聊的對方;
  • 2)羣聊的場合,可很不同:由於小寶發送的已讀消息列表,多是由衆愛卿發送的。考慮這種假設:張3、李4、王五發出的羣聊消息,被小寶一下都閱讀了,那麼小寶發出的已讀通知包含的消息列表,須要被IMS分解成3個已讀通知(3個不一樣的消息列表),分別通知給張3、李4、王五,通知內容是「愛卿(不含'"衆")發的這些消息,朕已閱」。

下面是大體的邏輯流程圖: 

4.2 查詢消息的未讀人數(私聊、羣聊通用)

消息的發送者,加載消息列表到聊天窗口時,可能須要展現消息是否被已讀。

對羣聊而言,顯示的信息多是n人未讀的提示,那麼須要向服務端查詢消息的未讀人數,因爲客戶端可能在UI顯示本身發出的多條消息,需支持一次請求查詢多條消息。

以未讀人數的方式來表示消息的閱讀狀態,統一了私聊、羣聊的查詢,使得客戶端-服務端間的接口更簡單,同時使客戶端的實現邏輯更統一。

就像下面這樣:

  • 1)對於私聊:若是未讀人數n>0,表示消息未讀;
  • 2)對於羣聊:直接顯示n人未讀便可,固然,當n等於0時表示所有已讀。

4.3 查詢羣消息的已讀、未讀人員清單(羣聊)

當客戶端但願顯示某一條羣聊消息的已讀、未讀人員列表,需向服務端發起查詢。

大體的邏輯流程圖以下:

五、幾種具體的已讀未讀狀態存儲思路探討

5.1 基本約定

羣聊的閱讀狀態比私聊複雜,所以這裏着重討論羣聊的閱讀狀態。

假設羣成員數是n,各個客戶端當即IM服務端發送已讀通知。服務端需存儲每一個人的閱讀狀態,包括那些未讀的成員。因爲羣的成員清單可能變化,好比今天增長了一個成員,則昨天發的消息、與今天發的消息,其接收者列表不同。

即:

  • 1)同一個羣的不一樣消息,對應的接收者列表可能不同。
  • 2)換言之,每一條消息都須要記錄完整的接收者列表和已讀人員列表。

爲了方便討論,本章假設羣成員有640人爲前提。

5.2 存儲思路1

每一條消息都維護:

  • 1)接收人員列表receiver_list;
  • 2)已讀人員列表read_list。

具體是:

  • 1)IM Server收到一條消息時,用全體羣成員構建receiver_list;
  • 2)IM Server收到羣成員對這條消息的已讀通知時,將此成員加入到read_list。

客戶端獲取此消息的數據:

  • 1)當須要獲取未讀人數時,用receiver_list的個數減去read_list的個數;
  • 2)當須要獲取已讀、未讀人員列表時,需用receiver_list減去read_list獲得未讀人員列表。

那麼,思路1每條消息的存儲空間是:

640個ID + 不定數量的已讀人員ID

5.3 存儲思路2

每一條消息維護:

  • 1)未讀人員列表unread_list;
  • 2)已讀人員列表read_list。

具體是:

  • 1)IM Server收到一條消息時,用全體羣成員構建unread_list;
  • 2)IM Server收到羣成員對這條消息的已讀通知時,將此成員從unread_list移出,同時加入到read_list。

客戶端獲取此消息的數據:

  • 1)當須要獲取未讀人數時,直接計算unread_list的個數;
  • 2)當須要獲取已讀、未讀人員列表時,直接返回unread_list和read_list。

那麼,思路2每條消息的存儲空間是:

未讀人員ID + 已讀人員ID,合計640個ID

思路2的實現,佔用的空間是案1的0.5倍~1.0倍。即案2佔用的空間少,但在每次收到客戶端的已讀通知時,比案1多了一個操做:從unread_list進行減員。

5.4 存儲思路3(個人實現)

5.4.1)探討5.2節、5.3節的不足:

5.2節、5.3節這兩種思路,都能知足功能需求,但存在巨大的存儲浪費。

該羣有640人,若是羣內聊天天天有1024條消息,人員ID以4字節存儲計算,那麼爲該羣天天的消息閱讀狀態須要消耗的空間是:

5.2節思路1:1024 * (640 * 4 + 已讀人數 * 4),範圍是 2.5MB ~ 5MB;

5.3節思路2:1024 * 640 * 4,等於2.5MB。

這僅僅是一個羣在一天以內產生的閱讀狀態數據,若是是在雲平臺運行,單此功能消耗的空間,呵呵~~

題外話:若是成員不是用4字節整型存儲,而改用字符串,好比"1123356777",那就更可觀了。

5.4.2)如何減小存儲空間:

考慮羣成員並不是時時刻刻都在變化,多數狀況下,羣成員的列表是相對穩定的,今天的和上週(甚至更久之前)的列表甚至多是同樣的,那麼有可能幾百條消息,甚至幾萬條消息對應的羣成員列表是相同的。

所以,引出本文的重點思想:

考慮讓不一樣的消息共用羣成員列表,即把消息的閱讀狀態與羣成員列表分開存儲,並記錄它們之間的關聯。

假定平均每1024條消息共用一個羣成員列表,發了1024條消息後,羣成員變化了,此後須要用新的羣成員列表。

那麼這一千條消息的閱讀狀態所佔用的空間是:

羣成員列表空間 + 1024條消息的閱讀狀態:640 * 4 + 1024 * 每條消息的閱讀狀態所佔空間

在具有羣成員列表的前提下,如何減小每條消息的閱讀狀態所佔空間?

很天然會想到用bit來表示已讀人員,由於一個32位整型可表示32我的的已讀狀態。bit的順序只需與羣成員列表的順序一致便可。

當一條消息沒有人已讀時,閱讀狀態佔用0字節;當羣內每一個人都閱讀時,佔用的空間最大,即640 / 32 = 20字節。

所以優化以後,這一千條消息的閱讀狀態所佔用的空間,範圍是2.5KB ~ (2.5KB + 1024 * 20B),即2.5KB ~ 22.5KB,此數值與5.2節思路一、5.3節思路2對比,有了極大幅度地降低。

以下圖所示:

該表格的前提條件:

  • 1)一個羣有640人;
  • 2)該羣連續1024條消息對應的羣成員列表是穩定的。

退一步考慮,哪怕這1024條消息對應的羣成員列表不穩定,中間變化了10次,那麼也僅會多出2.5KB * 10即25KB的存儲空間,與案一、案2相比仍然有極大優點。

六、如何提升已讀未讀狀態的處理速度

小寶往公司羣發了一條消息我來給你們介紹一下新來的女同事,你們當即、立刻、瞬間、閃電般地查看消息,感受遲1秒就會失去秒殺女神的機會同樣,意味着一瞬間會有N多條已讀通知發送到IMS。

對這些消息的處理流程是同樣的:

  • 1)可合併這些操做以批量形式進行存儲、轉發;
  • 2)因爲存儲消息的閱讀狀態是一個設置bit的過程,因此不存在互斥的問題,即便在分佈式環境也能夠放心操做;
  • 3)消息對應的成員列表信息可臨時緩存在內存對象內,以減小查詢IO,提升效率。(本文同步發佈於:http://www.52im.net/thread-3054-1-1.html
相關文章
相關標籤/搜索