IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?
1、前言
IM的群聊消息,究竟存1份(即扩散读方式)还是存多份(即扩散写方式)?
上一篇文章《IM群聊消息的已读回执功能该怎么实现?》是说,“很容易想到,是存一份”,被网友们骂了,大家争论的很激烈(见下图)。
<ignore_js_op>
网友骂的对,任何技术方案,都不是天才般灵感乍现想到的,一定是一个演进迭代,逐步优化的过程。今天就聊一聊,IM群聊消息,为啥只需要存一份。
不过,从公开的技术资料来看,微信的群聊消息应该使用的是存多份(即扩散写方式),详细的方案可以在微信团队分享的这篇文章里找到答案:《微信后台团队:微信后台异步消息队列的优化升级实践分享》。
学习交流:
- 即时通讯开发交流3群:185926912[推荐]
- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》
(本文同步发布于:http://www.52im.net/thread-1616-1-1.html)
2、本文作者
<ignore_js_op>
沈剑:58技术委员会主席,58高级架构师,58到家技术总监。C2C技术部负责人,58技术学院优秀讲师。
沈剑的另外几篇有关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);
<ignore_js_op>
很容易想到,整个群消息的发送流程如上图1-4:
- 1)发送消息;
- 2)查询状态;
- 3)不在线的存储离线;
- 4)在线的实时推送。
“在线的群友不存储,离线的群友才存储”会带来的问题是,如果第四步发生异常,群友会丢失消息。
6、优化的方案:“不管群员是否在线,都要先存储消息”
消息的可达性是聊天系统中最重要的要素(没有之一),故这个方案是不行的,需要优化为“不管是否在线,都要先存储”。
<ignore_js_op>
发送群消息的流程优化为,如上图1-4:
- 1)发送消息;
- 2)所有人都存一份;
- 3)查询状态;
- 4)在线的实时推送。
先将消息落地,能够保证消息可达性,那何时才能删除已经落地的群消息呢?我们继续往下看。
<ignore_js_op>
对于在线的群友:收到群消息后,给个ack确认才能删除。
画外音:逻辑删除,还是物理删除,根据业务是否有消息漫游决定。
<ignore_js_op>
对于离线的群友:在下次登陆后,拉取完离线消息再给ack确认才能删除。
总之:为了保证消息的可达性,不管是在线消息还是离线消息,必须接收方给ack确认,才能删除消息。
7、“不管群员是否在线,都冗余一份群消息”带来的问题
“不管是否在线,都冗余一份群消息”带来的问题是:同一条消息存储了很多次,对磁盘和带宽造成了很大的浪费。
很容易想到的优化是:群消息实体存储一份,用户只冗余消息ID。
<ignore_js_op>
故基础数据可以由:
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); // 不再需要
<ignore_js_op>
即:群消息只存储一份,群友无需冗余任何消息实体,或者消息ID了。
<ignore_js_op>
对于在线的群友:收到群消息后,修改这个last_ack_msgid。
<ignore_js_op>
对于离线的群友:拉取群消息后,也修改这个last_ack_msgid。
画外音:这里的讨论,仅限于接收方收到了哪些消息,和发送方的已读回执没有关系。(这里指的是作者的上篇文章《IM群聊消息的已读回执功能该怎么实现?》)
9、本文小结
任何架构方案都不是灵光一现,而是逐步迭代优化产生的:
- 方案1:群聊消息存多份,只存在线,消息容易丢;
- 方案2:群聊消息存多份,所有群友都存储,消息冗余多;
- 方案3:群聊消息存多份,只存ID,未利用偏序;
- 终极方案:群聊消息存一份,只存last_ack_msgid。
架构不(只)是设计出来的,更是演进出来的。
(本文同步发布于:http://www.52im.net/thread-1616-1-1.html)
IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?的更多相关文章
- IM群聊消息的已读回执功能该怎么实现?
本文引用了架构师之路公众号作者沈剑的文章,内容有改动,感谢原作者. 1.前言 我们平时在使用即时通讯应用时候,每当发出一条聊天消息,都希望对方尽快看到,并尽快回复,但对方到底有没有真的看到?我却并不知 ...
- 第五讲 smart qq poll包处理 以及 私聊 群聊消息收发
发送 poll包 public static void Login_PostPoll() { try { string url = "http://d1.web2.qq.com/channe ...
- XMPP群聊消息重复,自己收到自己发出的消息,群警告消息如何屏蔽
在XMPP的"groupchat"中,创建群的时候会收到群发的"This room is locked from entry until configuration is ...
- 一套高可用、易伸缩、高并发的IM群聊架构方案设计实践
本文原题为“一套高可用群聊消息系统实现”,由作者“于雨氏”授权整理和发布,内容有些许改动,作者博客地址:alexstocks.github.io.应作者要求,如需转载,请联系作者获得授权. 一.引言 ...
- 环信SDK 头像、昵称、表情自定义和群聊设置的实现 一(附源码)
前言: 环信的SDK在公司的项目中有用到,现在用到的是群聊的部分,这里我们分析总结一下自己对环信给的DEMO大概的拆解一下,说说我们怎么样充分的利用这个demo来写我们所需要的业务.这个也由于篇幅的原 ...
- Java Socket通信实现私聊、群聊
前言 闲言少叙,上代码! 代码编写 server服务端 /** * 服务端 */ public class Server { private static ServerSocket server = ...
- WebSocket+Java 私聊、群聊实例
前言 之前写毕业设计的时候就想加上聊天系统,当时已经用ajax长轮询实现了一个(还不懂什么是轮询机制的,猛戳这里:https://www.cnblogs.com/hoojo/p/longPolling ...
- Python3 itchat微信获取好友、公众号、群聊的基础信息
Python3 itchat微信获取好友.公众号.群聊的基础信息 一.简介 安装 itchat pip install itchat 使用个人微信的过程当中主要有三种账号需要获取,分别为: 好友 公众 ...
- ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(四) 添加表情、群聊功能
休息了两天,还是决定把这个尾巴给收了.本篇是最后一篇,也算是草草收尾吧.今天要加上表情功能和群聊.基本上就差不多了,其他功能,读者可以自行扩展或者优化.至于我写的代码方面,自己也没去重构.好的,我们开 ...
随机推荐
- 【转载】SQL Server中的Merge关键字
简介 原文地址 Merge关键字是一个神奇的DML关键字.它在SQL Server 2008被引入,它能将Insert,Update,Delete简单的并为一句.MSDN对于Merge的解释非常的短小 ...
- 5N - 考试排名
C++编程考试使用的实时提交系统,具有即时获得成绩排名的特点.它的功能是怎么实现的呢? 我们做好了题目的解答,提交之后,要么“AC”,要么错误,不管怎样错法,总是给你记上一笔,表明你曾经有过一次错误提 ...
- double team
队长博客链接 https://www.cnblogs.com/98-10-22-25/p/9806296.html 团队队名 泡面 团队成员 211606361 何承华(队长) 211606356 陈 ...
- python基础 (函数名,闭包,和迭代器)
1.函数名作用 函数名本质上就是函数的内存地址或对象. 1.可以被引用 2.可以被当作容器类型的元素 3.可以当作函数的参数和返回值 4.如果记不住的话,那就记住一句话,就当普通变量用 2.闭包 什么 ...
- 常用Linux VPS/服务器SSH连接工具 - Xshell下载与使用
我们很多网友可能初次接触Linux VPS.服务器,所以在购买完毕VPS主机不知道如何登录.有些网友甚至直接类似WIN系统一样直接在桌面远程连接工具连接,可想而知肯定是无法连接的.因为如果我们购买的是 ...
- pyadb关于python操作adb的资料
3.最后adb命令由于是android的原生操作命令,支持实现的功能非常多.这里举几个pyapp里实现的功能例子:获取,修改手机当前使用的输入法(adb shell ime list),获取当前手机界 ...
- hdfs数据采集场景示意图
- JAVA实训第四次作业
编写"电费管理类"及其测试类. 第一步 编写"电费管理"类 私有属性:上月电表读数.本月电表读数 构造方法:无参.2个参数 成员方法:getXXX()方法.se ...
- RT-thread-------------------信号量
信号量:用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的.(互斥量只能由持有线程释放,而信号量则可以由任何线程释放) 在rtt中,信号量分为计数型信号量和二值信号量(作 ...
- 修改maven的源地址为阿里源
在放maven的安装文件里,找到settings.xml,如下图所示 将默认的源地址改为阿里源,需要在settings.xml文件相应的位置上加上如下的一串: <mirror> < ...