1、前言
IM的群聊消息,究竟存1份(即擴散讀方式)還是存多份(即擴散寫方式)?
上一篇文章《IM群聊消息的已讀回執(zhí)功能該怎么實現(xiàn)?》是說,“很容易想到,是存一份”,被網友們罵了,大家爭論的很激烈(見下圖)。

網友罵的對,任何技術方案,都不是天才般靈感乍現(xiàn)想到的,一定是一個演進迭代,逐步優(yōu)化的過程。今天就聊一聊,IM群聊消息,為啥只需要存一份。
不過,從公開的技術資料來看,微信的群聊消息應該使用的是存多份(即擴散寫方式),詳細的方案可以在微信團隊分享的這篇文章里找到答案:《微信后臺團隊:微信后臺異步消息隊列的優(yōu)化升級實踐分享》。
學習交流:
- 即時通訊開發(fā)交流3群:185926912[推薦]
- 移動端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動端IM》
(本文同步發(fā)布于:http://www.52im.net/thread-1616-1-1.html)
2、本文作者

沈劍:58技術委員會主席,58高級架構師,58到家技術總監(jiān)。C2C技術部負責人,58技術學院優(yōu)秀講師。
沈劍的另外幾篇有關IM的文章也值得你去閱讀:
- 《理論聯(lián)系實際:一套典型的IM通信協(xié)議設計詳解》
- 《IM群聊消息的已讀回執(zhí)功能該怎么實現(xiàn)?》
- 《IM開發(fā)基礎知識補課(三):快速理解服務端數(shù)據(jù)庫讀寫分離原理及實踐建議》
3、IM開發(fā)干貨系列文章
本文是系列文章中的第15篇,總目錄如下:
- 《IM消息送達保證機制實現(xiàn)(一):保證在線實時消息的可靠投遞》
- 《IM消息送達保證機制實現(xiàn)(二):保證離線消息的可靠投遞》
- 《如何保證IM實時消息的“時序性”與“一致性”?》
- 《IM單聊和群聊中的在線狀態(tài)同步應該用“推”還是“拉”?》
- 《IM群聊消息如此復雜,如何保證不丟不重?》
- 《一種Android端IM智能心跳算法的設計與實現(xiàn)探討(含樣例代碼)》
- 《移動端IM登錄時拉取數(shù)據(jù)如何作到省流量?》
- 《通俗易懂:基于集群的移動端IM接入層負載均衡方案分享》
- 《淺談移動端IM的多點登陸和消息漫游原理》
- 《IM開發(fā)基礎知識補課(一):正確理解前置HTTP SSO單點登陸接口的原理》
- 《IM開發(fā)基礎知識補課(二):如何設計大量圖片文件的服務端存儲架構?》
- 《IM開發(fā)基礎知識補課(三):快速理解服務端數(shù)據(jù)庫讀寫分離原理及實踐建議》
- 《IM開發(fā)基礎知識補課(四):正確理解HTTP短連接中的Cookie、Session和Token》
- 《IM群聊消息的已讀回執(zhí)功能該怎么實現(xiàn)?》
- 《IM群聊消息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?》(本文)
另外,如果您是IM開發(fā)初學者,強烈建議首先閱讀《新手入門一篇就夠:從零開發(fā)移動端IM》。
4、更多關于IM群聊的文章
IM系統(tǒng)中的群聊功能,是個很大話題,下面幾篇在關群聊的文章您也可以讀一讀:
- 《如何保證IM實時消息的“時序性”與“一致性”?》
- 《IM單聊和群聊中的在線狀態(tài)同步應該用“推”還是“拉”?》
- 《IM群聊消息如此復雜,如何保證不丟不重?》
- 《微信后臺團隊:微信后臺異步消息隊列的優(yōu)化升級實踐分享》
- 《移動端IM中大規(guī)模群消息的推送如何保證效率、實時性?》
- 《現(xiàn)代IM系統(tǒng)中聊天消息的同步和存儲方案探討》
- 《關于IM即時通訊群聊消息的亂序問題討論》
- 《IM群聊消息的已讀回執(zhí)功能該怎么實現(xiàn)?》
另外,《一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)》一文中也包含了群聊的完整設計,如果您設計IM不知從何下手,可以詳細地參考此文。
5、最基本的方案:“在線的群友不存儲消息,離線的群友才存儲”
群信息,用戶信息,群成員關系都是基礎數(shù)據(jù):
group_info(gid, group_info);
user_info(uid, user_info);
group_members(gid, uid);
假設一個群(gid)里有4個成員,其中三個在線(A, uid1, uid2),一個不在線(uid3)。
A發(fā)送了一條消息,很容易想到,對于不同的群友消息存多份,每個群友一個隊列來存儲。但由于在線的用戶會實時的收到消息,所以暫定只為離線的用戶存儲。
用戶收到的群消息,也是基礎數(shù)據(jù):
user_msgs(uid,msgid,gid,sender_uid,time,content);

很容易想到,整個群消息的發(fā)送流程如上圖1-4:
- 1)發(fā)送消息;
- 2)查詢狀態(tài);
- 3)不在線的存儲離線;
- 4)在線的實時推送。
“在線的群友不存儲,離線的群友才存儲”會帶來的問題是,如果第四步發(fā)生異常,群友會丟失消息。
6、優(yōu)化的方案:“不管群員是否在線,都要先存儲消息”
消息的可達性是聊天系統(tǒng)中最重要的要素(沒有之一),故這個方案是不行的,需要優(yōu)化為“不管是否在線,都要先存儲”。

發(fā)送群消息的流程優(yōu)化為,如上圖1-4:
- 1)發(fā)送消息;
- 2)所有人都存一份;
- 3)查詢狀態(tài);
- 4)在線的實時推送。
先將消息落地,能夠保證消息可達性,那何時才能刪除已經落地的群消息呢?我們繼續(xù)往下看。

對于在線的群友:收到群消息后,給個ack確認才能刪除。
畫外音:邏輯刪除,還是物理刪除,根據(jù)業(yè)務是否有消息漫游決定。

對于離線的群友:在下次登陸后,拉取完離線消息再給ack確認才能刪除。
總之:為了保證消息的可達性,不管是在線消息還是離線消息,必須接收方給ack確認,才能刪除消息。
7、“不管群員是否在線,都冗余一份群消息”帶來的問題
“不管是否在線,都冗余一份群消息”帶來的問題是:同一條消息存儲了很多次,對磁盤和帶寬造成了很大的浪費。
很容易想到的優(yōu)化是:群消息實體存儲一份,用戶只冗余消息ID。

故基礎數(shù)據(jù)可以由:
user_msgs(uid,msgid,gid,sender_uid,time,content);
優(yōu)化為:
group_msgs(msgid,gid,sender_uid,time,content);
user_msgs(uid, msgid, gid);
這個優(yōu)化,對于消息投遞,以及消息刪除的核心流程沒有影響,幾個實踐為:
- 在線用戶投遞消息實體,ack消息ID;
- 離線用戶先拉取消息ID,再拉取消息實體,再ack消息ID。
如此這般,假如在某個群友A期間,群里陸續(xù)發(fā)送了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
8、終級方案:利用群消息的“偏序”特性優(yōu)雅地實現(xiàn)“只存1份”
然而,群消息具備“偏序”特性,上面的一次性刪除完全可以優(yōu)化為:
delete from user_msgs
where msgid >= $mid1 and gid=$gid
這就意味著,每個用戶只需要記錄“最近一次收到的消息ID”,而不用記錄“所有未收到的消息ID集合”,每當收在線消息ack,以及拉離線消息ack時,只需要更新這個“最近一次收到的消息ID”即可。
于是乎,基礎數(shù)據(jù)可以由:
group_members(gid, uid);
group_msgs(msgid,gid,sender_uid,time,content);
user_msgs(uid, msgid, gid);
優(yōu)化為:
group_members(gid, uid, last_ack_msgid);
group_msgs(msgid,gid,sender_uid,time,content);

即:群消息只存儲一份,群友無需冗余任何消息實體,或者消息ID了。

對于在線的群友:收到群消息后,修改這個last_ack_msgid。

對于離線的群友:拉取群消息后,也修改這個last_ack_msgid。
畫外音:這里的討論,僅限于接收方收到了哪些消息,和發(fā)送方的已讀回執(zhí)沒有關系。(這里指的是作者的上篇文章《IM群聊消息的已讀回執(zhí)功能該怎么實現(xiàn)?》)
9、本文小結
任何架構方案都不是靈光一現(xiàn),而是逐步迭代優(yōu)化產生的:
- 方案1:群聊消息存多份,只存在線,消息容易丟;
- 方案2:群聊消息存多份,所有群友都存儲,消息冗余多;
- 方案3:群聊消息存多份,只存ID,未利用偏序;
- 終極方案:群聊消息存一份,只存last_ack_msgid。
架構不(只)是設計出來的,更是演進出來的。
(本文同步發(fā)布于:http://www.52im.net/thread-1616-1-1.html)
作者:Jack Jiang (點擊作者姓名進入Github)
出處:http://www.52im.net/space-uid-1.html
交流:歡迎加入即時通訊開發(fā)交流群 215891622
討論:http://www.52im.net/
Jack Jiang同時是【原創(chuàng)Java
Swing外觀工程BeautyEye】和【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
本博文
歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。