1、前言

本文的上篇《IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》中,我们讨论了在线实时消息的投递可以通过应用层的确认、发送方的超时重传、接收方的去重等手段来保证业务层面消息的不丢不重。

但实时在线投递针对的是消息收发双方都在线的情况(如当发送方用户A发送消息给接收方用户B时,用户B是在线的),那如果消息的接收方用户B不在线,系统是如何保证消息的可达性的呢?这就是本文要讨论的问题。

2、IM开发干货系列文章

  • 《如何保证IM实时消息的“时序性”与“一致性”?》
  • 《IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?》

3、消息接收方不在线时的典型消息发送流程

如上图所述,通常此类情况下消息的发送流程如下:

  • Step 1:用户A发送一条消息给用户B;
  • Step 2:服务器查看用户B的状态,发现B的状态为“offline”(即B当前不在线);
  • Step 3:服务器将此条消息以离线消息的形式持久化存储到DB中(当然,具体的持久化方案可由您IM的具体技术实现为准);
  • Step 4:服务器返回用户A“发送成功”ACK确认包(注:对于消息发送方而言,消息一旦落地存储至DB就认为是发送成功了)。

关于 “Step 4” 的补充说明:

请一定要理解“Step 4”,因为现在无论是传统的PC端IM(类似QQ这样的——可以在UI上看到好友的在线、离线状态)还是目前主流的移动端IM(强调的是用户全时在线——即你看不到好友到底在线还是离线,反正给你的假像就是这个好友“应该”是在线的),消息发送出去后,无论是对方实时在线收到还是对方不在线而被服务端离线存储了,对于发送方而言只要消息没有因为网络等原因莫名消失,就应该认为是“被收到了”。

从技术的角度讲,消息接收方收到的消息应答ACK包的真正发起者,实际上有两种可能性:一种是由接收方发出、而另一种是由服务端代为发送(这在MobileIMSDK开源工程里被称作“伪应答”)。

4、典型离线消息表的设计以及拉取离线消息的过程

① 存储离线消看书的表主要字段大致如下:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

-- 消息接收者ID

receiver_uid varchar(50),

-- 消息的唯一指纹码(即消息ID),用于去重等场景,单机情况下此id可能是个自增值、分布式场景下可能是类似于UUID这样的东西

msg_id varchar(70),

-- 消息发出时的时间戳(如果是个跨国IM,则此时间戳可能是GMT-0标准时间)      

send_time time,

-- 消息发送者ID

sender_uid varchar(50),

-- 消息类型(标识此条消息是:文本、图片还是语音留言等)

msg_type int,

-- 消息内容(如果是图片或语音留言等类型,由此字段存放的可能是对应文件的存储地址或CDN的访问URL)

msg_content varchar(1024),

② 离线消息拉取模式:

接收方B要拉取发送方A给ta发送的离线消息,只需在receiver_uid(即接收方B的用户ID), sender_uid(即发送方A的用户ID)上查询,然后把离线消息删除,再把消息返回B即可。

③ 离线消息的拉取,如果用SQL语句来描述的话,它可以是:

1

2

3

SELECT msg_id, send_time, msg_type, msg_content

FROM offline_msgs

WHERE receiver_uid = ? and sender_uid = ?

④ 离线拉取的整体流程如下图所示:

  • Stelp 1:用户B开始拉取用户A发送给ta的离线消息;
  • Stelp 2:服务器从DB(或对应的持久化容器)中拉取离线消息;
  • Stelp 3:服务器从DB(或对应的持久化容器)中把离线消息删除;
  • Stelp 4:服务器返回给用户B想要的离线消息。

5、上述流程存在的问题以及优化方案

如果用户B有很多好友,登陆时客户端需要对所有好友进行离线消息拉取,客户端与服务器交互次数就会比较多。

① 拉取好友离线消息的客户端伪代码:

1

2

3

4

5

// 登陆时所有好友都要拉取

for(all uid in B’s friend-list){

     // 与服务器交互

     get_offline_msg(B,uid);  

}

② 优化方案1:

先拉取各个好友的离线消息数量,真正用户B进去看离线消息时,才往服务器发送拉取请求(手机端为了节省流量,经常会使用这个按需拉取的优化)。

③ 优化方案2:

如下图所示,一次性拉取所有好友发送给用户B的离线消息,到客户端本地再根据sender_uid进行计算,这样的话,离校消息表的访问模式就变为->只需要按照receiver_uid来查询了。登录时与服务器的交互次数降低为了1次。

④ 方案小结:

通常情况下,主流的的移动端IM(比如微信、手Q等)通常都是以“优化方案2”为主,因为移动网络的不可靠性加上电量、流量等资源的昂贵性,能尽量一次性干完的事,就尽可能一次搞定,从而提供整个APP的用户体验(对于移动端应用而言,省电、省流量同样是用户体验的一部分)。这方面的文章,可以进一步参阅《谈谈移动端 IM 开发中登录请求的优化》、《移动端IM实践:iOS版微信界面卡顿监测方案》、《移动端IM实践:Android版微信如何大幅提升交互性能(二)》。

6、消息接收方一次拉取大量离线消息导致速度慢、卡顿的解决方法

用户B一次性拉取所有好友发给ta的离线消息,消息量很大时,一个请求包很大、速度慢,容易卡顿怎么办?

正如上图所示,我们可以分页拉取:根据业务需求,先拉取最新(或者最旧)的一页消息,再按需一页页拉取,这样便能很好地解决用户体验问题。

7、优化离线消息的拉取过程,保证离线消息不会丢失

如何保证可达性,上述步骤第三步执行完毕之后,第四个步骤离线消息返回给客户端过程中,服务器挂点,路由器丢消息,或者客户端crash了,那离线消息岂不是丢了么(数据库已删除,用户还没收到)?

确实,如果按照上述的1、2、3、4步流程,的确是的,那如何保证离线消息的绝对可靠性、可达性?

如同在线消息的应用层ACK机制一样,离线消息拉时,不能够直接删除数据库中的离线消息,而必须等应用层的离线消息ACK(说明用户B真的收到离线消息了),才能删除数据库中的离线消息。这个应用层的ACK可以通过实时消息通道告之服务端,也可以通过服务端提供的REST接口,以更通用、简单的方式通知服务端。

8、进一步优化,解决重复拉取离线消息的问题

如果用户B拉取了一页离线消息,却在ACK之前crash了,下次登录时会拉取到重复的离线消息么?

确实,拉取了离线消息却没有ACK,服务器不会删除之前的离线消息,故下次登录时系统层面还会拉取到。但在业务层面,可以根据msg_id去重。SMC理论:系统层面无法做到消息不丢不重,业务层面可以做到,对用户无感知。

优化后的拉取过程,如下图所示:

9、进一步优化,降低离线拉取ACK带来的额外与服务器的交互次数

假设有N页离线消息,现在每个离线消息需要一个ACK,那么岂不是客户端与服务器的交互次数又加倍了?有没有优化空间?

如上图所示,不用每一页消息都ACK,在拉取第二页消息时相当于第一页消息的ACK,此时服务器再删除第一页的离线消息即可,最后一页消息再ACK一次(实际上:最后一页拉取的肯定是空返回,这样可以极大地简化这个分页过程,否则客户端得知道当前离线消息的总页数,而由于消息读取延迟的存在,这个总页数理论上并非绝对不变,从而加大了数据读取不一致的可能性)。这样的效果是,不管拉取多少页离线消息,只会多一个ACK请求,与服务器多一次交互。

10、本文小结

正如本文中所列举的问题所描述的那样,保证“离线消息”的可达性比大家想象的要复杂一些,常见优化总结如下:

  • 1)对于同一个用户B,一次性拉取所有用户发给ta的离线消息,再在客户端本地进行发送方分析,相比按照发送方一个个进行消息拉取,能大大减少服务器交互次数;
  • 2)分页拉取,先拉取计数再按需拉取,是无线端的常见优化;
  • 3)应用层的ACK,应用层的去重,才能保证离线消息的不丢不重;
  • 4)下一页的拉取,同时作为上一页的ACK,能够极大减少与服务器的交互次数。

网易云信,你身边的即时通讯和音视频技术专家,了解我们,请戳网易云信官网

想要行业洞察和技术干货,请关注网易云信博客

本文转载自52im,作者:JackJiang

IM消息送达保证机制实现(二):保证离线消息的可靠投递相关推荐

  1. 深入biztalk消息以及消息订阅发布路由机制(二)-消息订阅【转】

    一.消息订阅 订阅消息的主体叫订阅服务器,订阅服务器是可以订阅并消费消息的服务,可以作为订阅服务器的服务类型目前有四类,在BizTalkMgmtDb管理数据库中的adm_ServiceClass的Na ...

  2. IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

    1.前言 互联网发展至今,IM(即时通讯聊天应用)一直是互联网上最为成功也是最为平常的应用类型.尤其现今的移动互联网时代,因即时通讯技术的发展和普及,IM这种即时通讯应用已乎达成了各即时通讯应用运营者 ...

  3. IM消息送达保证机制实现

    一.保证在线实时消息的可靠投递 1.报文类型 报文分为三种: 请求报文(request,后简称为为R): 应答报文(acknowledge,后简称为A): 通知报文(notify,后简称为N). 这三 ...

  4. 国内APP消息推送机制以及微信消息延迟问题剖析

    转自:https://club.huawei.com/thread-15878044-1-1.html 一.前言 随着安卓手机以及QQ/微信/支付宝/滴滴出行/美图外卖等一大批移动通信/移动消费应用的 ...

  5. IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的

    1.引言 好久没写技术文章了,今天这篇不是原理性文章,而是为大家分享一下由笔者主导开发实施的IM即时通讯聊天系统,针对大量离线消息(包括消息漫游)导致的用户体验问题的升级改造全过程. 文章中,我将从如 ...

  6. IM开发干货分享:如何优雅的实现大量离线消息的可靠投递

    1.点评 IM聊天消息的可靠投递,是每个线上产品都要考虑的IM热点技术问题. IM聊天消息能保证可靠送达,对于用户来说,就好比把钱存在银行不怕被偷一样,是信任的问题.试想,如果用户能明显感知到聊天消息 ...

  7. 一种通过xmpp实现离线消息推送的方法及系统

    公开号 : CN 104243271 A 专利申请号 : CN 201310230953 申请人 : 深圳中兴网信科技有限公司 [摘要] 本发明公开了一种通过XMPP实现离线消息推送的方法,在XMPP ...

  8. 微信为啥不丢“离线消息”?

    参考 需求缘起 当发送方用户A发送消息给接收方用户B时,如果用户B在线,之前的文章<微信为啥不丢"在线消息"?>聊过,可以通过应用层的确认,发送方的超时重传,接收方的去 ...

  9. 腾讯通服务器删除离线消息,如何撤回RTX离线消息离线文件.docx

    如何撤回RTX离线消息离线文件 如何撤回已经发出的离线消息.文件(包括群文件和群消息) 1 消息撤回1.1 如何撤回离线消息.群离线消息当一个用户向另一个用户发送离线消息后,该消息会存储在服务端Off ...

最新文章

  1. Android之了解ThreadLocal
  2. SDL播放音频的时候发现SDL_OpenAudioDevice打开一直失败
  3. 数据中心智能安防新突破:腾讯觅踪亮相DCD
  4. 东北大学计算机 大一物理考试题,东北大学大学物理期末考题及答案Word版
  5. RabbitMQ指南之三:发布/订阅模式(Publish/Subscribe)
  6. c#学习笔记01——引用类
  7. cmake cache变量_反复研究好几遍,我才发现关于 CMake 变量还可以这样理解!
  8. matlab矩阵(一)--如何控制矩阵中小数点的位数
  9. 让你提前认识软件开发(3):学校C语言教材的缺陷
  10. 饭后Android 第三餐-XUI框架(XUI介绍,使用方法,控件使用(九个Button,导航栏,可伸缩布局,顶部弹出框))
  11. 联想服务器无线网卡被禁用,无线网卡被禁用怎么办
  12. 目标-过程-结果经验分享及OKR工作法
  13. 微擎上传图片失败——加密版本不能使用__DIR__或者__FILE__
  14. 云服务器可以通过远程打游戏吗,云主机能玩游戏吗_云主机安全防护措施
  15. C语言实现顺序栈的基本操作(初始化、判断空、入栈、出栈、获取栈顶元素)
  16. 华为不招android,抛弃安卓后 ARM华为也不要了:全新CPU架构曝光
  17. 微软亚太研发集团高性能计算首席架构师徐明强访谈:我的成长启示录
  18. 时间戳转换,在线转换时间戳
  19. 经纬度与WGS84坐标转换
  20. onlyoffice document server实时文档协作的部署与开发细节

热门文章

  1. 使用js对来判断一个字符串中括号是否平衡匹配
  2. DICOM 开发工具总结
  3. android手机短信拦截器,垃圾短信退订套路深 手机上装拦截软件是可行方法
  4. python选择日期控件_Flask学习笔记-使用bootstrap-datepicker实现页面日期选择
  5. CListCtrl的用法总结
  6. SQLite学习手册(索引和数据分析/清理)
  7. es6 取数组的第一个和最后一个_ES6:解构——JavaScript 从数组和对象中提取数据的优雅方法...
  8. oracle自动还原,什么是oracle自动恢复操作
  9. 有机发光二极管显示器测试方法_研究人员证明有机激光二极管将不再是梦
  10. 2018android旗舰手机,2018 年发布的 Android 手机,哪一部是你心目中的最佳手机?理由是什么?...