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

为方便讨论,假设以下前提:

  1. 周五傍晚18:00下班,我关闭App,我是9527;
  2. 有1小姐姐向我发了5条消息留言,约我周末去海边玩,她是杨幂3306;
  3. 然后,另1小姐姐也向我发了33条消息留言,内容我不便透露,她是景甜5672;
  4. 严正声明:我跟她们很清白,其实我喜欢的是6379。

对,既然是假设,假一点也无妨。
我下班回到家,看到手机有通知栏消息,打开App将会发生哪些事呢?

App和IMS的交互:

  1. 登录后,App以18:00填充参数latest_session_time,向IMS获取会话列表(其实不是以下线时间18:00,但这样更易理解);
  2. 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条的时间
    }
    
  3. App收到步骤2的应答,我在App的会话列表窗口里,能看到2项更新,景甜发来的未读消息数33条,杨幂的是5条;
  4. 点开景甜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条
    }
    
  5. IMS收到步骤4请求,将返回33条新消息的最后10条给App,呈现聊天窗口内;且聊天窗口上方有一个tip:↑ 33条新消息
  6. 我可以向上翻动聊天记录,那么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系统之消息必达相关推荐

  1. [原创] 浅谈ETL系统架构如何测试?

    [原创] 浅谈ETL系统架构如何测试? 来新公司已入职3个月时间,由于公司所处于互联网基金行业,基金天然固有特点,基金业务复杂,基金数据信息众多,基金经理众多等,所以大家可想一下,基民要想赚钱真不容易 ...

  2. 浅谈 Linux 系统中的 SNMP Trap 【转】

    文章来源:浅谈 Linux 系统中的 SNMP Trap 简介 本文讲解 SNMP Trap,在介绍 Trap 概念之前,首先认识一下 SNMP 吧. 简单网络管理协议(Simple Network ...

  3. 2022上海电动物流车|快递物流展浅谈顺丰、三通一达、京东等财报里“黑科技”

    2022上海电动物流车|快递物流展浅谈顺丰.三通一达.京东等财报里"黑科技" 2022上海电动物流车展|快递物流展浅谈顺丰.三通一达.百世.德邦.京东-财报里的"黑科技& ...

  4. 浅谈Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路

    原文地址: http://blog.csdn.net/luoshengyang/article/details/6627260 在前面一篇文章浅谈Service Manager成为Android进程间 ...

  5. html css 命名规范,浅谈css命名规则(新手必看)

    头:header 内容:content/container 尾:footer 导航:nav 侧栏:sidebar 栏目:column 页面外围控制整体布局宽度:wrapper 左右中:left rig ...

  6. 《浅谈-Android系统越用反应越慢的问题》

    <浅谈-Android系统越用反应越慢的问题> android应用程序和iphone应用程序不一样,用过iphone的都知道,点击图标进入程序后,如果还想用其他程序,必须先按返回退出然后进 ...

  7. 浅谈SpaceBuilder系统的缓存机制_缓存思想

    在前面的文章中也提及到为了提高系统的性能,SpaceBuilder在内部做了大量的工作,而数据缓存就是其中非常高效的处理方式. 我们知道SpaceBuilder采用了多层架构的处理模式,数据通过业务实 ...

  8. 浅谈秒杀系统架构设计

    秒杀是电子商务网站常见的一种营销手段. 原则 不要整个系统宕机. 即使系统故障,也不要将错误数据展示出来. 尽量保持公平公正. 实现效果 秒杀开始前,抢购按钮为活动未开始. 秒杀开始时,抢购按钮可以点 ...

  9. 东方木2020浅谈win10系统还原怎么操作

    编辑:东方木影院 地点:武汉 时间:2020年2月14日 东方木2020浅谈win10系统还原怎么操作,如果电脑物理内存不足时,会导致工作效率非常的低,我们可以调整win10 32位系统虚拟内存来加快 ...

最新文章

  1. 2013年,我跟哥们都是大厂Java工程师!后来,他转行了!现在,他的收入是我的5倍!...
  2. 协议森林08 不放弃 (TCP协议与流通信)
  3. Python中的堆实现:heapq 模块——利用堆结构实现快速访问数据流中的中位数
  4. 一个单片机的小问题。
  5. 阿里二面:RocketMQ 消息积压了,增加消费者有用吗?
  6. html 开发资料 英文,HTML 中的框架(国外英文资料).doc
  7. 关于APKsmail中加入代码报错All register args must fit in 4 bits
  8. left join 最后一条_一条Mysql查询语句的西天取经之路,你真的了解吗?
  9. cocoapods应用第一部分-xcode创建.framework相关
  10. 美图秀秀图片修改成圆角
  11. Spring依赖注入的三种方式
  12. Junit4 基于 custom Rule retry
  13. 用“讲故事”的方式,带你认识Python编码问题起源和发展!
  14. windows 清理助手 3.1
  15. 什么是TailwindCSS
  16. 2017 CCPC 秦皇岛 G题
  17. Eclipse(二)如何给 eclipse 设置快捷键
  18. 应公司需要,开发了一个CPU卡的发卡工具
  19. matlab 两幅图求并集,MATLAB交并集运算
  20. Python Flask 部署到阿里云服务器

热门文章

  1. 微信支付服务商以及特约商户相关总结
  2. 0开始学py爬虫(学习笔记)
  3. 普通石粉的用途_石粉在建筑业上有什么用途
  4. 软件测试需要学什么(软件测试人员怎么入行)?
  5. 商品交易系统产品介绍
  6. 「GitLab CI/CD」- You are not allowed to download code from this project @20210402
  7. 【CSS】你真的了解font-weight吗?
  8. Pandownload盘神回来了
  9. 全自动打码软件与人工打码的区别
  10. 学习@Transaction异常自动回滚以及手动回滚和回滚部分SQL服务