剑客
关注科技互联网

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

1、前言

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

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

2、学习交流

– 即时通讯开发交流群: IM消息送达保证机制实现(二):保证离线消息的可靠投递   215891622 [ 推荐 ]

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

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

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

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

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

IM消息送达保证机制实现(二):保证离线消息的可靠投递 如上图所述,通常此类情况下消息的发送流程如下:

  • 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、典型离线消息表的设计以及拉取离线消息的过程

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

-- 消息接收者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语句来描述的话,它可以是:

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想要的离线消息。

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

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

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

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

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

for
(all uid in B’s friend-list){
   
// 与服务器交互
   
get_offline_msg(B,uid);  

}

② 优化方案1:

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

③ 优化方案2:

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

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

④ 方案小结:

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

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

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

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

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

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

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

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

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

如同在线消息的应用层ACK机制一样,离线消息拉时,不能够直接删除数据库中的离线消息,而

必须等应用层的离线消息ACK(说明用户B真的收到离线消息了),才能删除数据库中的离线消息

。这个应用层的ACK可以通过实时消息通道告之服务端,也可以通过服务端提供的REST接口,以更通用、简单的方式通知服务端。

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

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

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

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

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

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

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

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

如上图所示,不用每一页消息都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

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址