1、前言

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

但实时在线投递针对的是消息收发双方都在线的情况(如当发送方用户A发送消息给接收方用户B时,用户B是在线的),那如果消息的接收方用户B不在线,系统是如何保证消息的可达性的呢?这就是本文要讨论的问题。(本文同步发布于:http://www.52im.net/thread-594-1-1.html

2、学习交流

- 即时通讯开发交流群: 215891622 [推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

3、IM消息送达保证系列文章

本文是讨论IM消息送达保证系列文章中的第2篇,总目录如下:

另外,如果您正在查阅移动端IM开发资料,推荐阅读《新手入门一篇就够:从零开发移动端IM》。

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

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

  • 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开源工程里被称作“伪应答”)。

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
-- 消息接收者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想要的离线消息。

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

如果用户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版微信如何大幅提升交互性能(二)》。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

11、本文小结

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

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

(本文同步发布于:http://www.52im.net/thread-594-1-1.html,本文内容参考了:微信为啥不丢“离线消息”

12、IM技术资料分类

[1] 网络编程基础资料:
TCP/IP详解 - 第11章·UDP:用户数据报协议
TCP/IP详解 - 第17章·TCP:传输控制协议
TCP/IP详解 - 第18章·TCP连接的建立与终止
TCP/IP详解 - 第21章·TCP的超时与重传
技术往事:改变世界的TCP/IP协议(珍贵多图、手机慎点)
通俗易懂-深入理解TCP协议(上):理论基础
通俗易懂-深入理解TCP协议(下):RTT、滑动窗口、拥塞处理
理论经典:TCP协议的3次握手与4次挥手过程详解
理论联系实际:Wireshark抓包分析TCP 3次握手、4次挥手过程
计算机网络通讯协议关系图(中文珍藏版)
UDP中一个包的大小最大能多大?
Java新一代网络编程模型AIO原理及Linux系统AIO介绍
NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示
NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示
NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战
NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战
P2P技术详解(一):NAT详解——详细原理、P2P简介
P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解
P2P技术详解(三):P2P技术之STUN、TURN、ICE详解
高性能网络编程(一):单台服务器并发TCP连接数到底可以有多少
高性能网络编程(二):上一个10年,著名的C10K并发连接问题
高性能网络编程(三):下一个10年,是时候考虑C10M并发问题了
高性能网络编程(四):从C10K到C10M高性能网络应用的理论探索
>> 更多同类文章 ……

[2] 有关IM/推送的通信格式、协议的选择:
为什么QQ用的是UDP协议而不是TCP协议?
移动端即时通讯协议选择:UDP还是TCP?
如何选择即时通讯应用的数据传输格式
强列建议将Protobuf作为你的即时通讯应用数据传输格式
移动端IM开发需要面对的技术问题(含通信协议选择)
简述移动端IM开发的那些坑:架构设计、通信协议和客户端
理论联系实际:一套典型的IM通信协议设计详解
58到家实时消息系统的协议设计等技术实践分享
>> 更多同类文章 ……

[3] 有关IM/推送的心跳保活处理:
Android进程保活详解:一篇文章解决你的所有疑问
Android端消息推送总结:实现原理、心跳保活、遇到的问题等
为何基于TCP协议的移动端IM仍然需要心跳保活机制?
微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)
微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)
移动端IM实践:实现Android版微信的智能心跳机制
移动端IM实践:WhatsApp、Line、微信的心跳策略分析
>> 更多同类文章 ……

[4] 有关WEB端即时通讯开发:
新手入门贴:史上最全Web端即时通讯技术原理详解
Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE
SSE技术详解:一种全新的HTML5服务器推送事件技术
Comet技术详解:基于HTTP长连接的Web端实时通信技术
WebSocket详解(一):初步认识WebSocket技术
socket.io实现消息推送的一点实践及思路
>> 更多同类文章 ……

[5] 有关IM架构设计:
浅谈IM系统的架构设计
简述移动端IM开发的那些坑:架构设计、通信协议和客户端
一套原创分布式即时通讯(IM)系统理论架构方案
从零到卓越:京东客服即时通讯系统的技术架构演进历程
蘑菇街即时通讯/IM服务器开发之架构选择
腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT
微信技术总监谈架构:微信之道——大道至简(演讲全文)
如何解读《微信技术总监谈架构:微信之道——大道至简》
快速裂变:见证微信强大后台架构从0到1的演进历程(一)
17年的实践:腾讯海量产品的技术方法论
>> 更多同类文章 ……

[6] 有关IM安全的文章:
即时通讯安全篇(一):正确地理解和使用Android端加密算法
即时通讯安全篇(二):探讨组合加密算法在IM中的应用
即时通讯安全篇(三):常用加解密算法与通讯安全讲解
即时通讯安全篇(四):实例分析Android中密钥硬编码的风险
传输层安全协议SSL/TLS的Java平台实现简介和Demo演示
理论联系实际:一套典型的IM通信协议设计详解(含安全层设计)
微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解
来自阿里OpenIM:打造安全可靠即时通讯服务的技术实践分享
>> 更多同类文章 ……

[7] 有关实时音视频开发:
即时通讯音视频开发(一):视频编解码之理论概述
即时通讯音视频开发(二):视频编解码之数字视频介绍
即时通讯音视频开发(三):视频编解码之编码基础
即时通讯音视频开发(四):视频编解码之预测技术介绍
即时通讯音视频开发(五):认识主流视频编码技术H.264
即时通讯音视频开发(六):如何开始音频编解码技术的学习
即时通讯音视频开发(七):音频基础及编码原理入门
即时通讯音视频开发(八):常见的实时语音通讯编码标准
即时通讯音视频开发(九):实时语音通讯的回音及回音消除概述
即时通讯音视频开发(十):实时语音通讯的回音消除技术详解
即时通讯音视频开发(十一):实时语音通讯丢包补偿技术详解
即时通讯音视频开发(十二):多人实时音视频聊天架构探讨
即时通讯音视频开发(十三):实时视频编码H.264的特点与优势
即时通讯音视频开发(十四):实时音视频数据传输协议介绍
即时通讯音视频开发(十五):聊聊P2P与实时音视频的应用情况
即时通讯音视频开发(十六):移动端实时音视频开发的几个建议
即时通讯音视频开发(十七):视频编码H.264、V8的前世今生
学习RFC3550:RTP/RTCP实时传输协议基础知识
简述开源实时音视频技术WebRTC的优缺点
良心分享:WebRTC 零基础开发者教程(中文)
开源实时音视频技术WebRTC中RTP/RTCP数据传输协议的应用
基于RTMP数据传输协议的实时流媒体技术研究(论文全文)
声网架构师谈实时音视频云的实现难点(视频采访)
浅谈开发实时视频直播平台的技术要点
还在靠“喂喂喂”测试实时语音通话质量?本文教你科学的评测方法!
实现延迟低于500毫秒的1080P实时音视频直播的实践分享
移动端实时视频直播技术实践:如何做到实时秒开、流畅不卡
如何用最简单的方法测试你的实时音视频方案
技术揭秘:支持百万级粉丝互动的Facebook实时视频直播
>> 更多同类文章 ……

[8] IM开发综合文章:
移动端IM开发需要面对的技术问题
开发IM是自己设计协议用字节流好还是字符流好?
请问有人知道语音留言聊天的主流实现方式吗?
IM消息送达保证机制实现(一):保证在线实时消息的可靠投递
IM消息送达保证机制实现(二):保证离线消息的可靠投递
谈谈移动端 IM 开发中登录请求的优化
完全自已开发的IM该如何设计“失败重试”机制?
微信对网络影响的技术试验及分析(论文全文)
即时通讯系统的原理、技术和应用(技术论文)
开源IM工程“蘑菇街TeamTalk”的现状:一场有始无终的开源秀
>> 更多同类文章 ……

[9] 开源移动端IM技术框架资料:
开源移动端IM技术框架MobileIMSDK:快速入门
开源移动端IM技术框架MobileIMSDK:常见问题解答
开源移动端IM技术框架MobileIMSDK:压力测试报告
>> 更多同类文章 ……

[10] 有关推送技术的文章:
iOS的推送服务APNs详解:设计思路、技术原理及缺陷等
Android端消息推送总结:实现原理、心跳保活、遇到的问题等
扫盲贴:认识MQTT通信协议
一个基于MQTT通信协议的完整Android推送Demo
IBM技术经理访谈:MQTT协议的制定历程、发展现状等
求教android消息推送:GCM、XMPP、MQTT三种方案的优劣
移动端实时消息推送技术浅析
扫盲贴:浅谈iOS和Android后台实时消息推送的原理和区别
绝对干货:基于Netty实现海量接入的推送服务技术要点
移动端IM实践:谷歌消息推送服务(GCM)研究(来自微信)
为何微信、QQ这样的IM工具不使用GCM服务推送消息?
>> 更多同类文章 ……

[11] 更多即时通讯技术好文分类:
http://www.52im.net/forum.php?mod=collection&op=all

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

  1. Thinkphp利用微信多客服消息推送取货二维码消息

    首先看微信官方的说法: 当用户主动发消息给公众号的时候(包括发送信息.点击自定义菜单.订阅事件.扫描二维码事件.支付成功事件.用户维权), 微信将会把消息数据推送给开发者,开发者在一段时间内(目前修改 ...

  2. 离线消息如何实现?-- ESFramework 4.0 快速上手(02)

    在ESFramework 4.0 快速上手一文中,主要介绍了如何使用ESPlus.Rapid命名空间中的引擎来快速地构建基于TCP的网络通信系统,即使是使用ESPlus.Rapid来进行ESFrame ...

  3. XMPP——Smack[6]离线消息和离线文件的实现

    终篇,三天所学所用,也就这些,如果需要大家要自己去查资料研究研究,功能其实可以很强大的 可惜界面做得不好,一大短处,从大一迄今没整好,主要是个人审美不行,哎 毕业季呀毕业季,明天摆摊卖书,再半月就可能 ...

  4. openfire接收离线消息

    先接收离线消息后再通知openfire上线 //获取离线消息 OfflineMessageManager offlineMessageManager=new OfflineMessageManager ...

  5. IM系统中如何保证消息的可靠投递(即QoS机制)(转)

    消息的可靠性,即消息的不丢失和不重复,是im系统中的一个难点.当初qq在技术上(当时叫oicq)因为以下两点原因才打败了icq:1)qq的消息投递可靠(消息不丢失,不重复)2)qq的垃圾消息少(它an ...

  6. IM系统中如何保证消息的可靠投递(即QoS机制)

      消息的可靠性,即消息的不丢失和不重复,是im系统中的一个难点.当初qq在技术上(当时叫oicq)因为以下两点原因才打败了icq:1)qq的消息投递可靠(消息不丢失,不重复)2)qq的垃圾消息少(它 ...

  7. 【消息队列】kafka是如何保证消息不被重复消费的

    一.kafka自带的消费机制 kafka有个offset的概念,当每个消息被写进去后,都有一个offset,代表他的序号,然后consumer消费该数据之后,隔一段时间,会把自己消费过的消息的offs ...

  8. kafkaspot在ack机制下如何保证内存不溢

    新浪微博:intsmaze刘洋洋哥.   storm框架中的kafkaspout类实现的是BaseRichSpout,它里面已经重写了fail和ack方法,所以我们的bolt必须实现ack机制,就可以 ...

  9. RabbitMQ如何保证发送端消息的可靠投递

    消息发布者向RabbitMQ进行消息投递时默认情况下是不返回发布者该条消息在broker中的状态的,也就是说发布者不知道这条消息是否真的抵达RabbitMQ的broker之上,也因此会发生消息丢失的情 ...

随机推荐

  1. Python学习日志(一)

    Python的安装 访问http://www.python.org 点击downloads,选择Windows 我在这里选择了Latest Python 3 Release - Python 3.5. ...

  2. asp.net(C#)读取文件夹和子文件夹下所有文件,绑定到GRIDVIEW并排序 .

    Asp部分: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="MyFiles ...

  3. SQl 2005 For XMl 简单查询(Raw,Auto,Path模式)(1)

    很多人对Xpath可能比较熟悉,但不知道有没有直接操作过数据库,我们都知道 在Sql2005里公支持的几种查询有Raw,Auto模式,页并没有Path和Elements用法等,如果在2000里使用过 ...

  4. twitter.common.concurrent deadline and defer

    此defer非golang中的defer https://tour.golang.org/flowcontrol/12 from twitter.common.concurrent import Ti ...

  5. [8.2] Robot in a Grid

    Imagine a robot sitting on the upper left corner of grid with r rows and c columns. The robot can on ...

  6. 【签名之坑】Decmail.GetBits()

    decimal类型有GetBits()方法 可以获取到值的int[4]值,进而获取到byte[16]值 在c#里,0m和0.00m获取到的byte[]是不一样的(具体为何不一样,自己百度) 在sql里 ...

  7. druid sql黑名单 报异常 sql injection violation, part alway true condition not allow

    最近使用druid,发现阿里这个连接池 真的很好用,可以监控到连接池活跃连接数 开辟到多少个连接数 关闭了多少个,对于我在项目中查看错误 问题,很有帮助, 但是最近发现里面 有条sql语句 被拦截了, ...

  8. JavaScript数组去重的几种方法

    这个老问题,网上普遍都有了,为什么要再写一遍呢,主要是为了记个笔记... 遍历时搜索结果数组 思路很明确,如下 新建一个数组存放结果 循环遍历原数组,对于每一个元素,在结果数组中搜索是否存在 若不存在 ...

  9. Jquery学习插件之手风琴

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  10. 实现textarea高度自适应内容,无滚动条

    最近接触到一个很好用的js插件,可以实现textarea高度随内容增多而改变,并且不显示滚动条,推荐给大家: http://www.jacklmoore.com/autosize/