消息的可靠性,即消息的不丢失和不重复,是im系统中的一个难点。当初qq在技术上(当时叫oicq)因为以下两点原因才打败了icq:
1)qq的消息投递可靠(消息不丢失,不重复)
2)qq的垃圾消息少(它antispam做得好,这也是一个难点,但不是本文重点讨论的内容)
今天,本文将用十分通俗的语言,来讲述webim系统中消息可靠性的问题。

一、报文类型
im的客户端与服务器通过发送报文(也就是请求包)来完成消息的传递,报文分为三种,请求报文(request,后简称为为R),应答报文(acknowledge,后简称为A),通知报文(notify,后简称为N),这三种报文的解释如下:

R:客户端主动发送给服务器的报文
A:服务器被动应答客户端的报文,一个A一定对应一个R
N:服务器主动发送给客户端的报文

二、普通消息投递流程
用户A给用户B发送一个“你好”,很容易想到,流程如下:

1)client-A向im-server发送一个消息请求包,即msg:R
2)im-server在成功处理后,回复client-A一个消息响应包,即msg:A
3)如果此时client-B在线,则im-server主动向client-B发送一个消息通知包,即msg:N(当然,如果client-B不在线,则消息会存储离线)

三、上述消息投递流程出现的问题
从流程图中容易看到,发送方client-A收到msg:A后,只能说明im-server成功接收到了消息,并不能说明client-B接收到了消息。在若干场景下,可能出现msg:N包丢失,且发送方client-A完全不知道,例如:
1)服务器崩溃,msg:N包未发出
2)网络抖动,msg:N包被网络设备丢弃
3)client-B崩溃,msg:N包未接收
结论是悲观的:接收方client-B是否有收到msg:N,发送方client-A完全不可控,那怎么办呢?

四、应用层确认+im消息可靠投递的六个报文
upd是一种不可靠的传输层协议,tcp是一种可靠的传输层协议,tcp是如何做到可靠的?答案是:超时、重传、确认。
要想实现应用层的消息可靠投递,必须加入应用层的确认机制,即:要想让发送方client-A确保接收方client-B收到了消息,必须让接收方client-B给一个消息的确认,这个应用层的确认的流程,与消息的发送流程类似:

4)client-B向im-server发送一个ack请求包,即ack:R
5)im-server在成功处理后,回复client-B一个ack响应包,即ack:A
6)则im-server主动向client-A发送一个ack通知包,即ack:N
至此,发送“你好”的client-A,在收到了ack:N报文后,才能确认client-B真正接收到了“你好”。
会发现,一条消息的发送,分别包含(上)(下)两个半场,即msg的R/A/N三个报文,ack的R/A/N三个报文,一个应用层即时通讯消息的可靠投递,共涉及6个报文,这就是im系统中消息投递的最核心技术(如果某个im系统不包含这6个报文,不要谈什么消息的可靠性)。

五、可靠消息投递存在什么问题
期望六个报文完成消息的可靠投递,但实际情况下:
1)msg:R,msg:A报文可能丢失,此时直接提示“发送失败”即可,问题不大
2)msg:N,ack:R,ack:A,ack:N这四个报文都可能丢失(原因如第二章所述,可能是服务器奔溃、网络抖动、或者客户端奔溃),此时client-A都收不到期待的ack:N报文,即client-A不能确认client-B是否收到“你好”,那怎么办呢?

六、消息的超时与重传
client-A发出了msg:R,收到了msg:A之后,在一个期待的时间内,如果没有收到ack:N,client-A会尝试将msg:R重发。可能client-A同时发出了很多消息,故client-A需要在本地维护一个等待ack队列,并配合timer超时机制,来记录哪些消息没有收到ack:N,以定时重发。

一旦收到了ack:N,说明client-B收到了“你好”消息,对应的消息将从“等待ack队列”中移除。

七、消息的重传存在什么问题
第五章提到过,msg:N报文,ack:N报文都有可能丢失:
1)msg:N报文丢失,说明client-B之前压根没有收到“你好”报文,超时与重传机制十分有效
2)ack:N报文丢失,说明client-B之前已经收到了“你好”报文(只是client-A不知道而已),超时与重传机制将导致client-B收到重复的消息,那怎么办呢?
启示:
平时使用qq,或许大伙都有类似的体验,弹出一个对话框“因为网络原因,消息发送失败,是否要重发”,此时,有可能是对方没有收到消息(发送方网络不好,msg:N丢失),也可能已经收到了消息(接收方网络不好,反复重传后,ack:N依然丢失),出现这个提示时,大伙不妨和对端确认一下,看是哪种情况。

八、消息的去重
解决方法也很简单,由发送方client-A生成一个消息去重的msgid,保存在“等待ack队列”里,同一条消息使用相同的msgid来重传,供client-B去重,而不影响用户体验。

九、其他
1)上述设计理念,由客户端重传,可以保证服务端无状态性(架构设计基本准则)
2)如果client-B不在线,im-server保存了离线消息后,要伪造ack:N发送给client-A
3)离线消息的拉取,为了保证消息的可靠性,也需要有ack机制,但由于拉取离线消息不存在N报文,故实际情况要简单的多,即先发送offline:R报文拉取消息,收到offline:A后,再发送offlineack:R删除离线消息

十、总结
1)im系统是通过超时、重传、确认、去重的机制来保证消息的可靠投递,不丢不重
2)切记,一个“你好”的发送,包含上半场msg:R/A/N与下半场ack:R/A/N的6个报文

个人消息是一个1对1的ack,群消息就没有这么简单了,群消息存在一个扩散系数,如果大家感兴趣,下一次将和大家讨论im群消息的可靠投递。

IM系统中如何保证消息的可靠投递(即QoS机制)的更多相关文章

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

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

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

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

  3. RabbitMQ 消息的可靠投递

    mq 提供了两种方式确认消息的可靠投递 confirmCallback 确认模式 returnCallback 未投递到 queue 退回模式 在使用 RabbitMQ 的时候,作为消息发送方希望杜绝 ...

  4. RabbitMQ如何保证发送端消息的可靠投递-发生镜像队列发生故障转移时

    上一篇最后提到了mandatory这个参数,对于设置mandatory参数个人感觉还是很重要的,尤其在RabbitMQ镜像队列发生故障转移时. 模拟个测试环境如下: 首先在集群队列中增加两个镜像队列的 ...

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

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

  6. 《即时消息技术剖析与实战》学习笔记4——IM系统如何保证消息的可靠性

    IM 系统中,保证消息的可靠投递主要体现在两方面,一是消息的不丢失,二是消息的不重复. 一.消息不丢失 消息丢失的原因 首先看一下发送消息的流程,如下图所示: 消息.可以采取"时间戳比对&q ...

  7. 【RabbitMQ】如何进行消息可靠投递【下篇】

    说明 上一篇文章里,我们了解了如何保证消息被可靠投递到RabbitMQ的交换机中,但还有一些不完美的地方,试想一下,如果向RabbitMQ服务器发送一条消息,服务器确实也接收到了这条消息,于是给你返回 ...

  8. 聊聊业务系统中投递消息到mq的几种方式

    背景 电商中有这样的一个场景: 下单成功之后送积分的操作,我们使用mq来实现 下单成功之后,投递一条消息到mq,积分系统消费消息,给用户增加积分 我们主要讨论一下,下单及投递消息到mq的操作,如何实现 ...

  9. LruCacahe在美团DSP系统中的应用演进

    背景 DSP系统是互联网广告需求方平台,用于承接媒体流量,投放广告.业务特点是并发度高,平均响应低(百毫秒). 为了能够有效提高DSP系统的性能,美团平台引入了一种带有清退机制的缓存结构LruCach ...

随机推荐

  1. lucene7.1.0实现搜索文件内容

    Lucene的使用主要体现在两个步骤: 1 创建索引,通过IndexWriter对不同的文件进行索引的创建,并将其保存在索引相关文件存储的位置中. 2 通过索引查寻关键字相关文档. 首先,我们需要定义 ...

  2. pytorch实战(7)-----卷积神经网络

    一.卷积: 卷积在 pytorch 中有两种方式: [实际使用中基本都使用 nn.Conv2d() 这种形式] 一种是 torch.nn.Conv2d(), 一种是 torch.nn.function ...

  3. 【剑指Offer】55、链表中环的入口结点

      题目描述:   给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null.   解题思路:   本题是一个比较典型的链表题目,难度适中.首先,对于大多人来说,看到这道题是比较开心的 ...

  4. (C/C++学习)3.C++中cin的成员函数(cin.get();cin.getine()……)

    说明:流输入运算符,在一定程度上为C++程序的开发提供了很多便利,我们可以避免C语言那种繁琐的输入格式,比如在输入一个数值时,还需指定其格式,而cin以及cout则不需要.但是cin也有一些缺陷,比如 ...

  5. eclipse IED 创建springboot项目教程

    学习使我沉淀变成淀粉.-- 沃兹基硕德[美] 首先给你的 eclipse 安装插件 Help> Eclipse Marketplace uploading-image-736738.png 所搜 ...

  6. 百度API的经历,怎样为多个点添加带检索功能的信息窗口

    不管我们要做什么样的效果,APIKey(密钥)都是不可缺少的要件,所以我们需要先去百度申请我们的APIKey!!! 伸手党,请直接到页面底部获取完整代码! 最近做一个门店查询的内容展示,考虑到用户直观 ...

  7. lua返回页面时中文乱码

    1.在nginx.conf文件中的server标签里添加charset utf-8; 2.查看lua文件编码是否为utf-8

  8. python网络编程系列

    计算机基础 网络基础 套接字 socket模块 TCP协议和UDP协议 struct 模块简介 struct 模块解决 TCP黏包问题 socket 客户端的认证 socketserver模块初识 客 ...

  9. jQuery源码分析 开篇(一)

    解读一个开源框架,最终目的当然就是学习程序的设计思想和实现技巧. JavaScript宗旨就是Write Less, Do More,简洁的API,优雅的链式,强大的查询与便捷的操作都是我们喜欢他的原 ...

  10. 【习题4-1 Uva1589】Xiangqi

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 车是可以被吃掉的... 注意这个情况. 其他的模拟即可. [代码] #include <bits/stdc++.h> u ...