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

相关文章
相关标签/搜索