浅谈IM系统之消息必达
1 前言
暗恋女神良久,终于鼓起勇气决定向女神写一封情书。但如何表达才能感动女神?自感才疏学浅,于是通读四书五经、熟背唐诗宋词、遍览四大名著,已然腹有诗书气自华。一周末冥思苦想整日才写就一首七言律诗,虽无惊天地泣鬼神之势,但诚挚的爱念在字里行间里流淌,亦歌亦诗,相信会感动到女神,手机欣然发出。
发出一秒后,手心冒汗,感觉脸颊发烫,心脏像受惊吓的野兔一样快速跳动,就像第一次看见女神那时的感觉。闭着眼睛,想象女神看到消息时的情形,她是否也期盼我的表白?看到消息时是否心跳加速、小脸绯红?
一分钟后,紧盯手机屏幕,等待、期盼女神回复。
时间一分一秒地逝去,等一分钟像等一年一样漫长。
一小时后,仍然杳无音讯,难道她没看到消息么?或许在忙什么而没留意手机吧!
一天过去了,坐立不安,等待是一种痛苦的煎熬,期待和煎熬在心中交织翻滚,有几个瞬间甚至希望女神赶快拒绝自己,好让自己解脱!茶饭无味,失眠多天,整日魂不守舍。
一个月过去了,死心。
半年后,女神出嫁,婚礼那天前去祝福。席间亦随众觥筹交错,略有醉意,向女神敬酒:祝福你,但愿以后能遇见像你这样的女人。女神先是愣住、收起笑容,低下头,目光无神地看着大红地毯,长叹一声,言:我等你表白,等了一年!空气凝滞了几秒,女神强作欢颜:从今往后,各自安好吧,干杯!我转身踱回到座位,拿起手机,打开那个App,看着曾经发出的情书,一切仿佛还在昨日,但故事脚本已被别人书写,欲哭无泪,叹老天为何如此捉弄我?为何我发的消息女神没收到啊!
失魂落魄地回到家里,从冰箱里拿出几瓶罗斯福10号来麻醉自己,在酒精强烈的作用下,迷迷入睡。
第二天醒来,我明白了一个道理:对IM系统而言,消息必达永远摆在第一位!-----分割线-----以上是正文,以下开始胡说八道。
2 用离线消息实现消息必达
我们在重构IMS时,需解决上一代设计的痛点之一就是确保消息必达。
2.1 离线消息实现消息必达的流程
自然而然地会想到这么做:
由服务端为每个人保存一个“离线消息列表”,当用户在线时,由IMS主动确保消息下发且收到客户端的应答确认时,才认为消息送达客户端,相应地把消息从“离线消息列表”移除;
如果客户端没有发回应答确认,IMS会再发送。
以此来确保消息一定送到客户端,看起来是很符合逻辑。当时调查过市面上多款IM,行为基本如此。
2.1 海啸般的离线消息
2.1.1 和平时期
重构后的IM上线,内部测试及在公网运行,离线消息的工作一直很正常。
2.1.2 被签到签死了
后来,为某客户部署的私有环境,其用户量达几十万,其中的一个组织接近三万人,全员群也接近三万人;还有,底下的部门也有相应的群组,几百到几千人群不等。
“报到”、“签到”。。。大量的类似消息被发到几千、几万人的群内,然后如果有人一两天没上线,或者被加入到多个组织内,等到其上线时,几万条离线消息像海啸一般涌来,您想象一下:手机用户刚登陆的几分钟内,是什么场景?用户真的很无辜:我不就是登陆了一下App,叮叮咚咚响了几分钟,还卡,还发热。。
客户端承受不起大规模离线消息的轰炸,怎么办?
临时运用对处案:
1、对若干大组织的全员群,对非管理员禁言;
2、通知所有用户不要在大群签到。
3 远离离线消息
我承认,一开始设计离线消息时,真没想到是这样的使用场景。
对于大多数IM的开发者,或许不会碰到这种场景。
3.1 放弃以离线消息的形式实现消息必达
我开始思考什么是消息必达,以前的想法是:把用户该收的消息都送到其客户端,是消息必达。
后来,给消息必达下了新的定义:
用户有新消息时,确保让用户知道;
且当用户要查看这些消息时,确保其可一条不漏地看到。打个比方:
客户要把钱给您,不必送到您家里才算送到;而是转账到您的银行账户上,并告知您;当您要用钱时,直接从银行账户上消费即可。
从此,不会在用户上线时向其发送大量离线消息。
3.2 以会话列表为Base来实现消息必达
客户端在上线时,先从服务端更新会话列表;每一个会话列表项包含如下信息(简化了与本文无关的成员变量)
{// 会话对象的角色类型,比如私聊、群聊、系统通知、业务通知。。。uint32 session_role;// 会话对象的IDuint32 session_id;// 会话时间戳,用于消息同步;// 指会话的最后操作时间,比如清除角标的时间,与会话最后一条的消息时间未必一致uint64 session_timestamp;// true表示新增或更新,false表示被删除bool is_add;// 当is_add=false时,忽略以下信息// 仅用于显示角标的未读数量,当用户查看该会话后清零,且客户端多端同步uint32 new_msg_count;// 会话的最后一条消息MessageItem latest_msg;// 跳转消息的时间戳,即new_msg_count的最旧1条消息的时间uint64 goto_timestamp;
}
为方便讨论,假设以下前提:
- 周五傍晚18:00下班,我关闭App,我是9527;
- 有1小姐姐向我发了5条消息留言,约我周末去海边玩,她是杨幂3306;
- 然后,另1小姐姐也向我发了33条消息留言,内容我不便透露,她是景甜5672;
- 严正声明:我跟她们很清白,其实我喜欢的是6379。
对,既然是假设,假一点也无妨。
我下班回到家,看到手机有通知栏消息,打开App将会发生哪些事呢?
App和IMS的交互:
- 登录后,App以18:00填充参数latest_session_time,向IMS获取会话列表(其实不是以下线时间18:00,但这样更易理解);
- IMS检查发现我从18:00开始,有2个会话更新了,于是向App发送应答,以增量形式携带2个会话项:杨幂3306,景甜5672。
其中景甜5672的会话项信息如下:{uint32 session_role = Role_User; //表示私聊uint32 session_id = 5672; //景甜的IDuint64 session_timestamp = 1594464295335672; //最后一条消息的时间戳,微秒bool is_add = true; // true表示是更新项uint32 new_msg_count = 33; // 景甜向我发了33条消息MessageItem latest_msg = "房号是0520"; //最后1条消息,结构体MessageItem简略不表uint64 goto_timestamp = 1594463697556677; // 向我发的33条消息的最早1条的时间 }
- App收到步骤2的应答,我在App的会话列表窗口里,能看到2项更新,景甜发来的未读消息数33条,杨幂的是5条;
- 点开景甜5672的会话,App将向IMS发起同步消息的请求,获取最新的10条聊天消息,为了显示一屏。
{uint32 session_role = Role_User; //表示私聊uint32 session_id = 5672; //景甜的IDuint64 begin_time = 1594464295335672; //步骤2返回的session_timestampuint64 end_time = 1594434153444222; //景甜上午向我发的最后一条消息的时间uint32 max_pieces = 10; //本次最多取10条,PC屏幕大则不妨取20条 }
- IMS收到步骤4请求,将返回33条新消息的最后10条给App,呈现聊天窗口内;且聊天窗口上方有一个tip:
↑ 33条新消息
;
- 我可以向上翻动聊天记录,那么App将持续向IMS获取第2批同步消息;
或者也可以点击tip:↑ 33条新消息
,直接跳转到33条消息的最旧一条,这样支持从最旧的消息向新的翻看。
相比于客户端简单地被动接收服务端的离线通知方式,这种设计使得客户端的处理逻辑更复杂,主要体现在:
客户端向服务端取的同步消息是未必完整,这些存在客户端的消息,在时间区间上可能不连续的;因此客户端需要知道不同消息之间是否有断代,如果有则需要向服务端查询同步消息来merge本地信息,使其连续,即客户端要实现消息融合。
建议:用C++实现一个统一的底层imsdk库,来负责这些共通的消息处理和存储。避免各客户端(Windows,iOS,Android等)各自实现这些逻辑,减少工作量,也降低各端不一致的风险。
3.3 以会话列表为Base与用离线消息的对比
两种方式各方面对比:
序号 | 视角 | 用离线消息实现的形式 | 用会话列表为Base的形式 |
---|---|---|---|
1 | 实现逻辑 |
由IMS确保消息送达客户端, 客户端存储后发回确认。 逻辑简单 |
客户端先同步会话列表, 由用户驱动不定次获取同步消息。 逻辑复杂,客户端增加不少工作 |
2 | 离线消息量不多(如几百条) | 没有效率问题,且消息全部达到客户端本地,方便进行查找等动作 | 没优势 |
3 | 离线消息量巨大(如几万条) |
用户登录瞬间CS间瞬时流量大, 客户端瞬时要存储、更新的数据量巨大,可能出现卡顿、假死等情况 |
登录时交互数据小, 对IMS、客户端、用户体验,都比较友好 |
4 多终端条件下,如何得到完整消息履历?
由于同一个用户的每个终端,其会话最后更新时间、每个会话的最后一条时间可能都不一样,因此:
- 参照第3.2章节的“App和IMS的交互”第1个步骤,可取到不同的增量变化的会话列表项;
- 参照第3.2章节的“App和IMS的交互”第4个步骤,可取到任一区间的同步消息,得到完整消息。
5 离线消息是否就彻底废弃了?
有若干情况,仍然需要保留离线消息,以确保消息送达:
- 别人向我发送离线文件,这种情况下不能依赖同步消息来获取。因为不以离线消息通知的话,用户在没有拉取到对应的同步消息前,是不知道有离线文件的。
- 撤回消息。即使接收者不拉取同步,仍然要保证在上线后其数据在第一时间被撤回。注意:这里可能存在多端撤回问题。
- 用户在线时的消息下发。由于用户在线时,IMS向客户端发送消息可能碰到网络抖动等情况,导致消息下发失败,这些消息先可以直接存在离线消息队列,IMS可在收到客户端的心跳包时重发消息。相当于维护了一个在线消息的离线队列。
6 后语
曾经有一段真挚的爱情摆在我面前,如果时间倒流到半年前,我会选择一个靠谱的App来发送消息,也许故事的脚本就由自己书写——是否要整一个时光倒流的版本,抱得美人归的那种?不整了不整了,我得不到女神,你们才欢喜,我太了解你们了。。。各位爷欢喜就好
[THE END]
浅谈IM系统之消息必达相关推荐
- [原创] 浅谈ETL系统架构如何测试?
[原创] 浅谈ETL系统架构如何测试? 来新公司已入职3个月时间,由于公司所处于互联网基金行业,基金天然固有特点,基金业务复杂,基金数据信息众多,基金经理众多等,所以大家可想一下,基民要想赚钱真不容易 ...
- 浅谈 Linux 系统中的 SNMP Trap 【转】
文章来源:浅谈 Linux 系统中的 SNMP Trap 简介 本文讲解 SNMP Trap,在介绍 Trap 概念之前,首先认识一下 SNMP 吧. 简单网络管理协议(Simple Network ...
- 2022上海电动物流车|快递物流展浅谈顺丰、三通一达、京东等财报里“黑科技”
2022上海电动物流车|快递物流展浅谈顺丰.三通一达.京东等财报里"黑科技" 2022上海电动物流车展|快递物流展浅谈顺丰.三通一达.百世.德邦.京东-财报里的"黑科技& ...
- 浅谈Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路
原文地址: http://blog.csdn.net/luoshengyang/article/details/6627260 在前面一篇文章浅谈Service Manager成为Android进程间 ...
- html css 命名规范,浅谈css命名规则(新手必看)
头:header 内容:content/container 尾:footer 导航:nav 侧栏:sidebar 栏目:column 页面外围控制整体布局宽度:wrapper 左右中:left rig ...
- 《浅谈-Android系统越用反应越慢的问题》
<浅谈-Android系统越用反应越慢的问题> android应用程序和iphone应用程序不一样,用过iphone的都知道,点击图标进入程序后,如果还想用其他程序,必须先按返回退出然后进 ...
- 浅谈SpaceBuilder系统的缓存机制_缓存思想
在前面的文章中也提及到为了提高系统的性能,SpaceBuilder在内部做了大量的工作,而数据缓存就是其中非常高效的处理方式. 我们知道SpaceBuilder采用了多层架构的处理模式,数据通过业务实 ...
- 浅谈秒杀系统架构设计
秒杀是电子商务网站常见的一种营销手段. 原则 不要整个系统宕机. 即使系统故障,也不要将错误数据展示出来. 尽量保持公平公正. 实现效果 秒杀开始前,抢购按钮为活动未开始. 秒杀开始时,抢购按钮可以点 ...
- 东方木2020浅谈win10系统还原怎么操作
编辑:东方木影院 地点:武汉 时间:2020年2月14日 东方木2020浅谈win10系统还原怎么操作,如果电脑物理内存不足时,会导致工作效率非常的低,我们可以调整win10 32位系统虚拟内存来加快 ...
最新文章
- 2013年,我跟哥们都是大厂Java工程师!后来,他转行了!现在,他的收入是我的5倍!...
- 协议森林08 不放弃 (TCP协议与流通信)
- Python中的堆实现:heapq 模块——利用堆结构实现快速访问数据流中的中位数
- 一个单片机的小问题。
- 阿里二面:RocketMQ 消息积压了,增加消费者有用吗?
- html 开发资料 英文,HTML 中的框架(国外英文资料).doc
- 关于APKsmail中加入代码报错All register args must fit in 4 bits
- left join 最后一条_一条Mysql查询语句的西天取经之路,你真的了解吗?
- cocoapods应用第一部分-xcode创建.framework相关
- 美图秀秀图片修改成圆角
- Spring依赖注入的三种方式
- Junit4 基于 custom Rule retry
- 用“讲故事”的方式,带你认识Python编码问题起源和发展!
- windows 清理助手 3.1
- 什么是TailwindCSS
- 2017 CCPC 秦皇岛 G题
- Eclipse(二)如何给 eclipse 设置快捷键
- 应公司需要,开发了一个CPU卡的发卡工具
- matlab 两幅图求并集,MATLAB交并集运算
- Python Flask 部署到阿里云服务器
热门文章
- 微信支付服务商以及特约商户相关总结
- 0开始学py爬虫(学习笔记)
- 普通石粉的用途_石粉在建筑业上有什么用途
- 软件测试需要学什么(软件测试人员怎么入行)?
- 商品交易系统产品介绍
- 「GitLab CI/CD」- You are not allowed to download code from this project @20210402
- 【CSS】你真的了解font-weight吗?
- Pandownload盘神回来了
- 全自动打码软件与人工打码的区别
- 学习@Transaction异常自动回滚以及手动回滚和回滚部分SQL服务