1、前言

IM的群聊訊息,究竟存1份(即擴散讀方式)還是存多份(即擴散寫方式)?

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

IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?

網友罵的對,任何技術方案,都不是天才般靈感乍現想到的,一定是一個

演進迭代

,逐步最佳化的過程。今天就聊一聊,IM群聊訊息,為啥只需要存一份。

不過,從公開的技術資料來看,微信的群聊訊息應該使用的是存多份(即擴散寫方式),詳細的方案可以在微信團隊分享的這篇文章裡找到答案:《微信後臺團隊:微信後臺非同步訊息佇列的最佳化升級實踐分享》。

學習交流:

- 即時通訊開發交流3群:185926912[推薦]

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

(本文同步釋出於:

http://www。

52im。net/thread-1616-1-

1。html

2、本文作者

IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?

沈劍:

58技術委員會主席,58高階架構師,58到家技術總監。C2C技術部負責人,58技術學院優秀講師。

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

《理論聯絡實際:一套典型的IM通訊協議設計詳解》

《IM群聊訊息的已讀回執功能該怎麼實現?》

《IM開發基礎知識補課(三):快速理解服務端資料庫讀寫分離原理及實踐建議》

3、IM開發乾貨系列文章

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

《IM訊息送達保證機制實現(一):保證線上實時訊息的可靠投遞》

《IM訊息送達保證機制實現(二):保證離線訊息的可靠投遞》

《如何保證IM實時訊息的“時序性”與“一致性”?》

《IM單聊和群聊中的線上狀態同步應該用“推”還是“拉”?》

《IM群聊訊息如此複雜,如何保證不丟不重?》

《一種Android端IM智慧心跳演算法的設計與實現探討(含樣例程式碼)》

《移動端IM登入時拉取資料如何作到省流量?》

《通俗易懂:基於叢集的移動端IM接入層負載均衡方案分享》

《淺談移動端IM的多點登陸和訊息漫遊原理》

《IM開發基礎知識補課(一):正確理解前置HTTP SSO單點登陸介面的原理》

《IM開發基礎知識補課(二):如何設計大量圖片檔案的服務端儲存架構?》

《IM開發基礎知識補課(三):快速理解服務端資料庫讀寫分離原理及實踐建議》

《IM開發基礎知識補課(四):正確理解HTTP短連線中的Cookie、Session和Token》

《IM群聊訊息的已讀回執功能該怎麼實現?》

《IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?》(本文)

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

4、更多關於IM群聊的文章

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

《如何保證IM實時訊息的“時序性”與“一致性”?》

《IM單聊和群聊中的線上狀態同步應該用“推”還是“拉”?》

《IM群聊訊息如此複雜,如何保證不丟不重?》

《微信後臺團隊:微信後臺非同步訊息佇列的最佳化升級實踐分享》

《移動端IM中大規模群訊息的推送如何保證效率、實時性?》

《現代IM系統中聊天訊息的同步和儲存方案探討》

《關於IM即時通訊群聊訊息的亂序問題討論》

《IM群聊訊息的已讀回執功能該怎麼實現?》

>>更多同類文章 ……

另外,《一套海量線上使用者的移動端IM架構設計實踐分享(含詳細圖文)》一文中也包含了群聊的完整設計,如果您設計IM不知從何下手,可以詳細地參考此文。

5、最基本的方案:“線上的群友不儲存訊息,離線的群友才儲存”

群資訊,使用者資訊,群成員關係都是基礎資料:

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);

IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?

很容易想到,整個群訊息的傳送流程如上圖1-4:

1)傳送訊息;

2)查詢狀態;

3)不線上的儲存離線;

4)線上的實時推送。

“線上的群友不儲存,離線的群友才儲存”會帶來的問題是,如果第四步發生異常,群友會丟失訊息。

6、最佳化的方案:“不管群員是否線上,都要先儲存訊息”

訊息的可達性是聊天系統中最重要的要素(沒有之一),故這個方案是不行的,需要最佳化為“不管是否線上,都要先儲存”。

IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?

傳送群訊息的流程最佳化為,如上圖1-4:

1)傳送訊息;

2)所有人都存一份;

3)查詢狀態;

4)線上的實時推送。

先將訊息落地,能夠保證訊息可達性,那何時才能刪除已經落地的群訊息呢?我們繼續往下看。

IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?

對於線上的群友:

收到群訊息後,給個ack確認才能刪除。

畫外音:

邏輯刪除,還是物理刪除,根據業務是否有訊息漫遊決定。

IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?

對於離線的群友:

在下次登陸後,拉取完離線訊息再給ack確認才能刪除。

總之:

為了保證訊息的可達性,不管是線上訊息還是離線訊息,必須接收方給ack確認,才能刪除訊息。

7、“不管群員是否線上,都冗餘一份群訊息”帶來的問題

“不管是否線上,都冗餘一份群訊息”帶來的問題是:同一條訊息儲存了很多次,對磁碟和頻寬造成了很大的浪費。

很容易想到的最佳化是:

群訊息實體儲存一份,使用者只冗餘訊息ID。

IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?

故基礎資料可以由:

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

8、終級方案:利用群訊息的“

偏序

”特性優雅地實現“只存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); // 不再需要

IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?

即:

群訊息只儲存一份,群友無需冗餘任何訊息實體,或者訊息ID了。

IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?

對於線上的群友:

收到群訊息後,修改這個

last_ack_msgid

IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?

對於離線的群友:

拉取群訊息後,也修改這個last_ack_msgid。

畫外音:

這裡的討論,僅限於接收方收到了哪些訊息,和傳送方的已讀回執沒有關係。(這裡指的是作者的上篇文章《IM群聊訊息的已讀回執功能該怎麼實現?》)

9、本文小結

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

方案1:群聊訊息存多份,只存線上,訊息容易丟;

方案2:群聊訊息存多份,所有群友都儲存,訊息冗餘多;

方案3:群聊訊息存多份,只存ID,未利用偏序;

終極方案:群聊訊息存一份,只存last_ack_msgid。

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

(本文同步釋出於:

http://www。

52im。net/thread-1616-1-

1。html