IM羣聊消息到底是存1份(即擴散讀)仍是存多份(即擴散寫)?

一、前言


IM的羣聊消息,究竟存1份(即擴散讀方式)仍是存多份(即擴散寫方式)?

上一篇文章《IM羣聊消息的已讀回執功能該怎麼實現?》是說,「很容易想到,是存一份」,被網友們罵了,你們爭論的很激烈(見下圖)。

<ignore_js_op>IM羣聊消息到底是存1份(即擴散讀)仍是存多份(即擴散寫)?_WechatIMG109.jpg 

網友罵的對,任何技術方案,都不是天才般靈感乍現想到的,必定是一個演進迭代,逐步優化的過程。今天就聊一聊,IM羣聊消息,爲啥只須要存一份

不過,從公開的技術資料來看,微信的羣聊消息應該使用的是存多份(即擴散寫方式),詳細的方案能夠在微信團隊分享的這篇文章裏找到答案:《微信後臺團隊:微信後臺異步消息隊列的優化升級實踐分享》。php

 

學習交流:html

- 即時通信開發交流3羣:185926912[推薦]算法

- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM數據庫

(本文同步發佈於:http://www.52im.net/thread-1616-1-1.html微信

二、本文做者


<ignore_js_op>IM羣聊消息到底是存1份(即擴散讀)仍是存多份(即擴散寫)?_58同城沈劍.jpg 
沈劍:58技術委員會主席,58高級架構師,58到家技術總監。C2C技術部負責人,58技術學院優秀講師。

沈劍的另外幾篇有關IM的文章也值得你去閱讀:

架構

 

三、IM開發乾貨系列文章


本文是系列文章中的第15篇,總目錄以下:

負載均衡


另外,若是您是IM開發初學者,強烈建議首先閱讀《新手入門一篇就夠:從零開發移動端IM》。異步

四、更多關於IM羣聊的文章


IM系統中的羣聊功能,是個很大話題,下面幾篇在關羣聊的文章您也能夠讀一讀:

學習

>> 更多同類文章 ……

另外,《一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)》一文中也包含了羣聊的完整設計,若是您設計IM不知從何下手,能夠詳細地參考此文。優化

五、最基本的方案:「在線的羣友不存儲消息,離線的羣友才存儲」


羣信息,用戶信息,羣成員關係都是基礎數據:

group_info(gid, group_info);
user_info(uid, user_info);
group_members(gid, uid);


假設一個羣(gid)裏有4個成員,其中三個在線(A, uid1, uid2),一個不在線(uid3)。

A發送了一條消息,很容易想到,對於不一樣的羣友消息存多份,每一個羣友一個隊列來存儲。但因爲在線的用戶會實時的收到消息,因此暫定只爲離線的用戶存儲

用戶收到的羣消息,也是基礎數據:

user_msgs(uid,msgid,gid,sender_uid,time,content);


<ignore_js_op>IM羣聊消息到底是存1份(即擴散讀)仍是存多份(即擴散寫)?_1.jpg 

很容易想到,整個羣消息的發送流程如上圖1-4:

  • 1)發送消息;
  • 2)查詢狀態;
  • 3)不在線的存儲離線;
  • 4)在線的實時推送。


「在線的羣友不存儲,離線的羣友才存儲」會帶來的問題是,若是第四步發生異常,羣友會丟失消息

六、優化的方案:「無論羣員是否在線,都要先存儲消息」


消息的可達性是聊天系統中最重要的要素(沒有之一),故這個方案是不行的,須要優化爲「不論是否在線,都要先存儲」。

<ignore_js_op>IM羣聊消息到底是存1份(即擴散讀)仍是存多份(即擴散寫)?_2.jpg 

發送羣消息的流程優化爲,如上圖1-4:

  • 1)發送消息;
  • 2)全部人都存一份;
  • 3)查詢狀態;
  • 4)在線的實時推送。


先將消息落地,可以保證消息可達性,那什麼時候才能刪除已經落地的羣消息呢?咱們繼續往下看。

<ignore_js_op>IM羣聊消息到底是存1份(即擴散讀)仍是存多份(即擴散寫)?_3.jpg 

對於在線的羣友:收到羣消息後,給個ack確認才能刪除。

畫外音:邏輯刪除,仍是物理刪除,根據業務是否有消息漫遊決定。

<ignore_js_op>IM羣聊消息到底是存1份(即擴散讀)仍是存多份(即擴散寫)?_4.jpg 

對於離線的羣友:在下次登錄後,拉取完離線消息再給ack確認才能刪除

總之:爲了保證消息的可達性,不論是在線消息仍是離線消息,必須接收方給ack確認,才能刪除消息。

七、「無論羣員是否在線,都冗餘一份羣消息」帶來的問題


「不論是否在線,都冗餘一份羣消息」帶來的問題是:同一條消息存儲了不少次,對磁盤和帶寬形成了很大的浪費。

很容易想到的優化是:羣消息實體存儲一份,用戶只冗餘消息ID。

<ignore_js_op>IM羣聊消息到底是存1份(即擴散讀)仍是存多份(即擴散寫)?_5.jpg 

故基礎數據能夠由:

user_msgs(uid,msgid,gid,sender_uid,time,content);
優化爲:
group_msgs(msgid,gid,sender_uid,time,content);
user_msgs(uid, msgid, gid);


這個優化,對於消息投遞,以及消息刪除的核心流程沒有影響,幾個實踐爲:

  • 在線用戶投遞消息實體,ack消息ID;
  • 離線用戶先拉取消息ID,再拉取消息實體,再ack消息ID。


如此這般,假如在某個羣友A期間,羣裏陸續發送了N條消息,則user_msgs(uid, msgid, gid)裏,會有 uidA -> mid1,mid2, mid3, … midN 等N條離線記錄,拉取離線消息時,能夠把這N條消息一次性拉取出來,而後再刪除:

delete from user_msgs  where msgid in($mid1,$mid2…, $midN) and gid=$gid

 

八、終級方案:利用羣消息的「偏序」特性優雅地實現「只存1份」


然而,羣消息具有「偏序」特性,上面的一次性刪除徹底能夠優化爲:

delete from user_msgs 
where msgid >= $mid1 and gid=$gid


這就意味着,每一個用戶只須要記錄「最近一次收到的消息ID」,而不用記錄「全部未收到的消息ID集合」,每當收在線消息ack,以及拉離線消息ack時,只須要更新這個「最近一次收到的消息ID」便可。

因而乎,基礎數據能夠由:
group_members(gid, uid);
group_msgs(msgid,gid,sender_uid,time,content);
user_msgs(uid, msgid, gid);


優化爲:
group_members(gid, uid, last_ack_msgid);
group_msgs(msgid,gid,sender_uid,time,content);
user_msgs(uid, msgid, gid); // 再也不須要


<ignore_js_op>IM羣聊消息到底是存1份(即擴散讀)仍是存多份(即擴散寫)?_6.jpg 
即:羣消息只存儲一份,羣友無需冗餘任何消息實體,或者消息ID了。

<ignore_js_op>IM羣聊消息到底是存1份(即擴散讀)仍是存多份(即擴散寫)?_7.jpg 
對於在線的羣友:收到羣消息後,修改這個last_ack_msgid。

<ignore_js_op>IM羣聊消息到底是存1份(即擴散讀)仍是存多份(即擴散寫)?_8.jpg 
對於離線的羣友:拉取羣消息後,也修改這個last_ack_msgid。

畫外音:這裏的討論,僅限於接收方收到了哪些消息,和發送方的已讀回執沒有關係。(這裏指的是做者的上篇文章《IM羣聊消息的已讀回執功能該怎麼實現?》)

九、本文小結


任何架構方案都不是靈光一現,而是逐步迭代優化產生的:

  • 方案1:羣聊消息存多份,只存在線,消息容易丟;
  • 方案2:羣聊消息存多份,全部羣友都存儲,消息冗餘多;
  • 方案3:羣聊消息存多份,只存ID,未利用偏序;
  • 終極方案:羣聊消息存一份,只存last_ack_msgid。


架構不(只)是設計出來的,更是演進出來的。

(本文同步發佈於:http://www.52im.net/thread-1616-1-1.html

相關文章
相關標籤/搜索