即时消息技术剖析与实战
袁武林
微博研发中心技术专家
立即订阅
6503 人已学习
课程目录
已完结 24 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 搞懂“实时交互”的IM技术,将会有什么新机遇?
免费
基础篇 (8讲)
01 | 架构与特性:一个完整的IM系统是怎样的?
02 | 消息收发架构:为你的App,加上实时通信功能
03 | 轮询与长连接:如何解决消息的实时到达问题?
04 | ACK机制:如何保证消息的可靠投递?
05 | 消息序号生成器:如何保证你的消息不会乱序?
06 | HttpDNS和TLS:你的消息聊天真的安全吗?
07 | 分布式锁和原子性:你看到的未读消息提醒是真的吗?
08 | 智能心跳机制:解决网络的不确定性
场景篇 (4讲)
09 | 分布式一致性:让你的消息支持多终端漫游
10 | 自动智能扩缩容:直播互动场景中峰值流量的应对
11 | 期中实战:动手写一个简易版的IM系统
12 | 服务高可用:保证核心链路稳定性的流控和熔断机制
进阶篇 (10讲)
13 | HTTP Tunnel:复杂网络下消息通道高可用设计的思考
14 | 分片上传:如何让你的图片、音视频消息发送得更快?
15 | CDN加速:如何让你的图片、视频、语音消息浏览播放不卡?
16 | APNs:聊一聊第三方系统级消息通道的事
17 | Cache:多级缓存架构在消息系统中的应用
18 | Docker容器化:说一说IM系统中模块水平扩展的实现
19 | 端到端Trace:消息收发链路的监控体系搭建
20 | 存储和并发:万人群聊系统设计中的几个难点
21 | 期末实战:为你的简约版IM系统,加上功能
22 | 答疑解惑:不同即时消息场景下架构实现上的异同
结束语 (1讲)
结束语 | 真正的高贵,不是优于别人,而是优于过去的自己
即时消息技术剖析与实战
登录|注册

04 | ACK机制:如何保证消息的可靠投递?

袁武林 2019-09-04
你好,我是袁武林。
在第一节的课程中,我们说到了即时消息系统中的四个重要特性,实时性、可靠性、一致性、安全性。
上一节课我们从如何保证消息实时性方面,了解了业界常用的一些方式以及背后具体的原理。那么今天我们接着来讲一讲,在即时消息的系统架构设计里,如何来保证消息的可靠投递。
首先,我们来了解一下,什么是消息的可靠投递?
站在使用者的角度来看,消息的可靠投递主要是指:消息在发送接收过程中,能够做到不丢消息、消息不重复两点。
这两个特性对于用户来讲都是非常影响体验的。我们先说一下不丢消息。
试想一下,你把辛辛苦苦攒到的零花钱打赏给了中意的“主播小姐姐”,但由于系统或者网络的问题,这条对你来说至关重要的打赏消息并没有成功投递给“主播小姐姐”,自然也就没有后续小姐姐和你一对一的互动环节了,想想是不是很悲剧?
消息重复也不用多说,谁也不愿意浪费时间在查看一遍又一遍的重复内容上。
那么在一般的 IM 系统的设计中,究竟是如何解决这两大难题的呢?下面我们结合一些简单的案例,来看一看“不丢消息”“消息不重复”这些能力,在技术上到底是怎么实现的。

消息丢失有哪几种情况?

我们以最常见的“服务端路由中转”类型的 IM 系统为例(非 P2P),这里解释一下,所谓的“服务端路由中转”是指:一条消息从用户 A 发出后,需要先经过 IM 服务器来进行中转,然后再由 IM 服务器推送给用户 B,这个也是目前最常见的 IM 系统的消息分发类型。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《即时消息技术剖析与实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(59)

  • 王棕生
    有了 TCP 协议本身的 ACK 机制为什么还需要业务层的ACK 机制?
    答:这个问题从操作系统(linux/windows/android/ios)实现TCP协议的原理角度来说明更合适:
         1 操作系统在TCP发送端创建了一个TCP发送缓冲区,在接收端创建了一个TCP接收缓冲区;
         2 在发送端应用层程序调用send()方法成功后,实际是将数据写入了TCP发送缓冲区;
         3 根据TCP协议的规定,在TCP连接良好的情况下,TCP发送缓冲区的数据是“有序的可靠的”到达TCP接收缓冲区,然后回调接收方应用层程序来通知数据到达;
         4 但是在TCP连接断开的时候,在TCP的发送缓冲区和TCP的接收缓冲区中可能还有数据,那么操作系统如何处理呢?
               首先,对于TCP发送缓冲区中还未发送的数据,操作系统不会通知应用层程序进行处理(试想一下:send()函数已经返回成功了,后面再告诉你失败,这样的系统如何设计?太复杂了...),通常的处理手段就是直接回收TCP发送缓存区及其socket资源;
               对于TCP接收方来说,在还未监测到TCP连接断开的时候,因为TCP接收缓冲区不再写入数据了,所以会有足够的时间进行处理,但若未来得及处理就发现了连接断开,仍然会为了及时释放资源,直接回收TCP接收缓存区和对应的socket资源。

    总结一下就是: 发送方的应用层程序,调用send()方法返回成功的时候,数据实际是写入到了TCP的发送缓冲区,而非已经被接收方的应用层程序处理。怎么办呢?只能借助于应用层的ACK机制。

    作者回复: 👍,嗯,即使数据成功发送到接收方设备了,tcp层再把数据交给应用层时也可能出现异常情况,比如存储客户端的本地db失败,导致消息在业务层实际是没成功收到的。这种情况下,可以通过业务层的ack来提供保障,客户端只有都执行成功才会回ack给服务端。

    2019-09-04
    2
    52
  • 小可
    两个ack的作用不同,tcp的ack表征网络层消息是否送达;业务层ack是真正的业务消息是否送达和是否正确处理,达到不丢消息,消息不重复的目的,即我们要保证的消息可靠性

    作者回复: 👍

    2019-09-04
    12
  • 影随
    老师您好,服务A向客户端B发送消息,第一次发送msg1,timestamp假设为 01(简写),序号为 01,这条消息因为某种原因,未存储时间戳和序号01,也未发送ack通知。A第二次发送msg2,timestamp为 02,序号为02,它做了存储,保存了最新的时间戳和序号。A第三次发送 msg3,此时B宕机了。 等B重启时,向A发送最新的时间戳和序号 02, 那么A发送大于02序号的消息,即 msg3, 那么 msg1如何保证不丢失呢?

    作者回复: 是的,如果只是时间戳或者“只是有序但不连续的序号”的话,是只能保证消息的时序性,不能保证消息的连续性。这种情况可以通过版本号机制来解决,通过两个版本号组成的链表(推送的每条消息携带前一条消息的版本号和当前这条消息的版本号)来检测消息的连续性和时序性。

    2019-09-10
    5
  • 隰有荷
    您好,我在读到在消息完整性检查那里时有些疑惑,如果服务端将msg2发出之后,服务端和客户端断链,导致客户端无法接收消息,那么重新连接之后,是可以发送时间戳检测进行重传的。
    但是,如果在服务端存储了发送方客户端发送的消息后,正准备将该消息推送给接收方客户端时发生宕机,那么当接收方客户端和服务端重新连接之后,服务端该如何知道自己要将之前存储的消息发送给接收方的客户端呢?

    作者回复: 用户上线的时候携带本地最新一条消息的时间戳给服务端,服务端从离线缓存里取比这个时间戳大的消息发给客户端就行了呀

    2019-09-04
    3
  • 墙角儿的花
    1、回答老师的问题:TCP层的ACK只是TCP包分片的ACK,并不能代表整个应用层的消息得到应答。理论上操作系统的TCP栈肯定是知道整个TCP消息得到对方的ACK了,但是操作系统好像并没提供这种接口。发送成功的接口返回成功通常都表示为操作系统发送成功了,至于链路上有没有问题就不知道了。
    2、向老师请教下其他问题,恳请解答。
    A、如果接收方本地保存了所有曾经接收过的消息id,接收方是很方便去重,但是,如果用户clear了本地消息该怎么办,是要一直存储所有已经接收的消息id吗
    B、对于防范服务器宕机的时间戳机制,其实本质是序号,但是网络传输并不能保证服务器按序号发送的消息,低序号的就一定先于高序号的被接收方接收。所以如果高序号的已经被接收方处理且应答,而某个低序号的消息还没得到接收方应答的场景,通过序号保证完整性貌似不可取。

    作者回复: A. 接收方本地去重只需要针对本机已经接收到的存在的消息来做就可以了,服务端接收时实际上已经会做一次存储层的去重了,只会存在没有回ack的消息导致接收方重复接收的情况,这种两次之间一般时间间隔都比较短的。
    B. 如果低序号的消息还没到,由于没有收到客户端的ack服务端会有超时重传机制会重传这条低序号的消息,另外即使这个时候用户关机不等那条消息了,再次上线时,采用版本号机制的话客户端也是可以知道消息不完整,可以触发服务端进行重推。

    2019-09-04
    3
    3
  • null
    老师,您好!

    文中提到:用户 A 等待 IM 服务器返回超时,用户 A 被提示发送失败。但可以通过重试等方式来弥补。

    我有个疑问:客户端在超时时间内没有收到响应然后重试,但实际上,请求已经在服务端成功处理了。这时用户 A 和 IM 服务器的状态就不一致了,用户 A 看到的是发送失败,而 IM 服务器却是处理成功的。

    同样的,IM 服务器在等待​ ACK 通知也存在这样的问题:IM 服务器在有限的重试次数内,一直没收到 ACK 通知,而消息却成功推送给了用户 B,IM 服务器和用户 B 的状态也不一致了。

    在有限的重试次数内(线上不可能无限重试吧?),无法得到确定的返回结果,导致客户端和服务端的状态不一致,如何解决这个问题吖?

    作者回复: 是的,对于重试不可能保证一定会成功,这些情况一般会以服务端中真实处理为准,通过多终端消息同步机制来让客户端有机会重新同步状态。比如发消息服务端处理成功,但是客户端接收响应超时'这种情况,服务端在成功处理完后会给发送方的发送设备推送当前消息的版本号,如果发送方设备没收到这个版本号,下次上线时会重新同步服务端的状态,用服务端消息进行覆盖。对于你说的第二种情况也比较简单,接收方b需要对重复接收的消息进行去重处理就可以了。

    2019-09-29
    2
  • RuBy
    老师,请问消息落地的话传统的redis+mysql是否会有性能瓶颈?是否会考虑leveldb(racksdb)这种持久化kv存储呢?

    作者回复: 看量级吧,我们自己的场景里mysql和hbase做为永久存储,pika作为离线消息的buffer存储 没有碰到瓶颈。

    2019-09-04
    2
  • 长江
    有了 TCP 协议本身的 ACK 机制为什么还需要业务层的 ACK 机制?
    1.TCP属于传输层,而IM服务属于应用层,TCP的ACK只能保证传输层的可靠性,即A端到B端的可靠性,但是不能保证数据能够被应用层正确可靠处理,比如应用层里面的业务逻辑导致消息处理失败了,TCP层是不知道的。
    2.TCP虽然是可靠性传输协议,但是如果传输过程中,假如数据报文还没被接收端接收完毕,接收端进程服务崩溃了,而用户又不再立刻启动这个应用程序,这也会导致消息丢失的吧。
    所以不能只依靠TCP的ACK机制的。

    作者回复: 1没问题哈,对于2的话如果出现接收端进程崩溃,一般这个时候接收端APP也是处于不可用状态了,这种情况实际上也没法通过这个ACK机制来避免丢消息。可以在用户再次上线时进行完整性检查的确认,如果有消息没有被正确接收,再由服务补推。

    2019-09-04
    3
    2
  • 小伟
    思考题:TCP和业务层做在的维度不一样,故虽然两者的ACK机制原理一样,但不能相互替代。TCP的ACK完成只能说明数据包已经正确传输完毕,但不代表数据包里的数据已经被正确处理完毕。业务层的ACK就是来保证数据包里的数据正确处理完毕的。TCP的ACK完成是业务层ACK的前提,业务层ACK完成是业务规则上的保证。

    作者回复: 👍

    2019-09-12
    1
  • 卫江
    Tcp的ack机制可以保证通过tcp传输的数据被对端内核接受并放入对应的socket接受缓存区里面,但是接下来进程读取缓存区以及进行逻辑处理可能会出现问题,所以需要应用层的ack机制保证数据包被进程读取并正确的处理。
    2019-09-04
    1
  • 阳阳
    请问一下老师:如果im服务器发给用户的第一条消息,用户b没有给业务层ack,但是im服务器紧接着又收到了第二条消息往用户b发送,此时用户b返回了正常ack。那么第一条消息在超时的时候会从队列里拿出来重传,假设用户b收到且返回ack成功,这个时候第一条消息

    作者回复: 留言没完整哈,如果是重复接收的问题,需要客户端来进行去重的。

    2019-09-04
    1
  • Better me
    老师您好,我想问下服务端IM先后推送两条消息msg0、msg1、msg2到客户端B,如果msg0、msg2先到达,此时客户端B应该不会更新到msg2的发送时间戳吧,而是等待msg1到达。如果此时陆续msg3、msg4消息到达,msg1还没到达,客户端是否也可像tcp一样,发送3个msg0消息到服务端IM,而不需要等待超时就让IM服务端立即重发msg1,这样可以降低延时,等到msg1到达客户端B后,可以直接将时间戳更新至最新到达消息的时间戳吧,而不是msg1的时间戳,不知道的理解的对不对,老师有时间看看

    作者回复: 首先这里要能让客户端知道有一条消息msg1还没到,所以单纯使用时间戳可能是不够的,时间戳只能代表时序,并不能判断出完整性,所以可能还需要配合连续的序号来实现感知(比如每次建连,服务端针对这条连接的所有推送从0开始自增,随消息一起下推)。至于你说的类似tcp的重传机制,这个理论上是可以这么实现的,就是复杂度上会高一些。

    2019-09-04
    1
  • 我行我素
    tcp ack只能保证消息的可达性;业务层面的ack才是保证业务的正确性
    2019-09-04
    1
  • 阳仔
    保证消息不丢失的做法:
    1、发送消息阶段通过客户端的发送重试机制,和服务端的去重,保证发送时消息不丢失不重复
    2、服务端推送阶段通过ACK确认机制和客户端去重保证推送时消息不丢失不重复
    3、最后使用时间戳的同步机制来保证消息的完整性,这个应该要在服务端无法触发重推消息时才进行的一个操作

    作者回复: 👍

    2019-09-04
    1
  • 段先森
    想问一下老师,一般来说对于直播的业务场景,消息存储这个环节用什么MQ会比较好些

    作者回复: 如果只是离线消息暂存的话,可以用pika或者redis,如果是消息的永久存储还是落db比较好。

    2019-09-04
    1
  • 海罗沃德
    如果客户端B离线了一段时间,当重连的时候服务器端已经积累了大量B的消息,比如999+条聊天记录,那么服务器端要逐条推送并且逐一更新时间戳么?还是可以批量推送,批量推送又如何保证客户端B的时间戳是最后一条的?
    2019-12-12
  • 老师有个问题请教您,如果在说到服务端发送消息到客户端时为了保证消息重复发送客户端时客户端能够识别重复需要带上一个当前会话范围唯一的sid,同时为了防止服务端发送消息和客户端接收消息步调不一致需要传送一个timestamp(也可用全局顺序号代替,这样的话是否可以直接让sid为一个全局顺序号,从而替代上面所提到sid+timestamp的功能?

    作者回复: sid是一个在一个长连接的会话期间内连续且有序的id,它的作用除了让客户端识别消息的顺序还能让客户端识别消息是否有丢失的情况。而timestamp可以认为是一个全局的用于跨多次长连接的消息排序的。所以不推荐sid来直接代替。

    另外还可以让客户端通过链表的方式来识别消息的顺序性,这样就可以让每条消息都只需要携带一个uuid就能解决排序和丢失识别的问题了。

    2019-12-04
  • HELSING
    ack 重传需要设置重传次数吗?比如接收端业务逻辑有异常,每次处理都抛异常,那服务端要怎么处理?

    作者回复: 是的,ack一般都需要设置次数限制避免由于对端异常导致无法正常回ack包,线上的经验一般是2-3次就可以了。

    2019-11-26
  • 无形
    tcp保证的是网络传输,业务层保证的是消息处理阶段,这两个在不同的层级

    作者回复: 👍

    2019-11-24
  • 唯我天棋
    tcp的ack只保证网络传输过程中的消息可靠达到。但是,在业务场景上网络传输只是其中的一个步骤,所以,不能保证。

    作者回复: 是的,传输层可靠不代表业务层可靠。

    2019-11-04
收起评论
59
返回
顶部