im消息送达保证机制实现(二):保证离线消息的可靠投递 -凯发k8网页登录

我的最新工程mobileimsdk:http://git.oschina.net/jackjiang/mobileimsdk
posts - 336, comments - 13, trackbacks - 0, articles - 0

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

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

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

- 移动端im开发推荐文章:《》

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

  • 《》
  • 《》(本文

另外,如果您正在查阅移动端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包的真正发起者,实际上有两种可能性:一种是由接收方发出、而另一种是由服务端代为发送(这在里被称作“伪应答”)。

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

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


 

如果用户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的用户体验(对于移动端应用而言,省电、省流量同样是用户体验的一部分)。这方面的文章,可以进一步参阅《》、《》、《》。

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

 

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

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

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

 

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

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

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

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

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

 

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

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

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


(本文同步发布于:,本文内容参考了:

[1] 网络编程基础资料:
《 - 》
《 - 》
《 - 》
《 - 》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
>> 

[2] 有关im/推送的通信格式、协议的选择:
《》
《》
《》
《》
《》
《》
《》
《》
>> 

[3] 有关im/推送的心跳保活处理:
《》
《》
《》
《》
《》
《》
《》
>> 

[4] 有关web端即时通讯开发:
《》
《》
《》
《》
《》
《》
>> 

[5] 有关im架构设计:
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
>> 

[6] 有关im安全的文章:
《》
《》
《》
《》
《》
《》
《》
《》
>> 

[7] 有关实时音视频开发:
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
>> 

[8] im开发综合文章:
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
>>  

[9] 开源移动端im技术框架资料:
《》
《》
《》
>> 

[10] 有关推送技术的文章:
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
《》
>> 

[11] 更多即时通讯技术好文分类:

作者: (点击作者姓名进入github) 
出处: 
交流:欢迎加入即时通讯开发交流群  
讨论:
jack jiang同时是和的作者,可前往下载交流。
本博文 欢迎转载,转载请注明出处(也可前往  找到我)。 



作者: (点击作者姓名进入github)
出处:
交流:欢迎加入即时通讯开发交流群
讨论:
jack jiang同时是和的作者,可前往下载交流。
本博文 欢迎转载,转载请注明出处(也可前往 找到我)。


只有注册用户后才能发表评论。


网站导航:
              
 
jack jiang的 mail: jb2011@163.com, 联系qq: 413980957, 微信: hellojackjiang
网站地图