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

  • 2019 年 10 月 3 日
  • 筆記

IM 系统中,保证消息的可靠投递主要体现在两方面,一是消息的不丢失,二是消息的不重复。

一、消息不丢失

消息丢失的原因

首先看一下发送消息的流程,如下图所示:

图片来源于《即时消息技术剖析与实战》第 04 讲

用户 A 发出的消息,先到达IM服务端(步骤1),由服务端暂存(步骤2),成功后,服务端将成功的结果返回给用户A(步骤3),同时将消息推送给用户B(步骤4)。
在这个过程中,丢失消息有以下几种情况:
1)步骤 1 因为网络不通等原因导致用户A把消息发送到IM服务器失败;
2)步骤 2 IM服务器存储消息失败;
3)步骤 3 用户A在超时时间内未收到IM服务器返回的结果;
4)步骤 4 由于IM服务器断电等原因导致消息未能成功推送给用户B(但步骤 3 用户A可以收到IM服务器返回的响应成功结果);
5)步骤 4 消息成功推送给用户B的设备,但用户B的设备因为一些原因如设备写入本地DB失败等,也会导致消息丢失。
前三种情况,用户A将被提示消息发送失败;后两种情况,用户B未收到消息。

消息丢失的解决方案

大部分场景中,业务层ACK确认机制 + 消息重传机制 + 消息完整性检查,能解决消息丢失的问题。
1.业务层的ACK确认机制和重传机制

ACK是确认字符(Acknowledge character)的意思,TCP协议默认提供了ACK机制,如果接收方成功接收到数据,就会回复一个ACK数据,表示发送方发出的数据已确认接收无误,在“三次握手”、“四次挥手”中经常见到。
ACK确认机制:TCP传输时将每个字节的数据都进行编号,即序列号。TCP传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答,也就是发送ACK报文。这个ACK报文当中带有对应的确认序列号,告诉发送方,接收到了哪些数据,下一次的数据从哪里发。有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据。这也是TCP传输可靠性的保证之一。
重传机制:发送方发送一部分数据后,都会等待接收方发送的ACK报文,并解析ACK报文,判断数据是否传输成功。发送方迟迟收不到ACK报文的原因可能有两个:
1)数据在传输过程中由于网络原因等直接全体丢包,接收方没有接收到;
2)接收方接收到了响应的数据,但是发送的ACK报文响应却由于网络原因丢包了。
超时重传机制就是发送方在发送完数据后等待一个时间,如果在超时时间内没有接收到ACK报文,就重新发送数据。如果是上述的第一个原因,接收方收到二次重发的数据后,便进行ACK应答。如果是第二个原因,接收方发现接收的数据已存在,就直接丢弃,仍旧发送ACK应答。

业务层的ACK确认机制参考了TCP的ACK确认机制,其策略是IM服务器在推送消息时,携带一个标识SID(安全标识符,类似TCP的sequenceId),推送出消息后会将当前消息添加到“待ACK消息列表”,客户端B成功接收完消息后,会给IM服务器回一个业务层的ACK包,包中携带有本条接收消息的SID,IM服务器接收后,会从“待ACK消息列表”记录中删除此条消息,本次推送才算真正结束。

业务层的消息重传机制也参考了TCP协议的重传机制,IM服务器的“等待ACK队列”一般会维护一个超时计时器,一定时间内如果没有收到用户B发回的ACK包,就从“等待ACK队列”中重新拉取并进行重推。

为什么有了TCP协议本身的ACK机制,还需要业务层的ACK机制?
这是因为TCP属于传输层,而IM服务属于应用层。TCP的ACK保证网络传输层的可靠性,即消息是否送达,但不能保证数据能够被应用层正确可靠处理;业务层ACK进行消息是否送达和是否正确处理的逻辑,达到不丢消息、消息不重复的目的。

2.时间戳比对检查消息完整性
在上面列举的丢失消息的第 4 种可能性中,如果步骤 4 IM服务器将消息推送出去后就宕机了,而这条消息又因为某些原因丢失了,服务器由于宕机无法触发重传机制,导致用户B收不到该消息。可以采取“时间戳比对”机制进行完整性检查。

图片来源于《即时消息技术剖析与实战》第 04 讲

时间戳比对过程如下:
1)IM服务器给用户B推送msg1,同时带上一个最新时间戳timestamp1。用户B收到msg1后,更新本地的时间戳为timestamp1;
2)IM服务器给用户B推送msg2,同时带上一个最新时间戳timestamp2。由于某种原因,用户B和IM服务器的连接断开,导致msg2没有成功推送到用户B;
3)用户B和IM服务器重新建立连接后,将本地的时间戳timestamp1发送给IM服务器,IM服务器将时间戳大于timestamp1的所有消息全部发送给用户B,同时带上时间戳timestamp2(这里假设时间戳大于timestamp1的消息只有msg2,如果有msg3、msg4等多条消息,应取最新消息的时间戳);
4)用户B收到msg2后,更新本地的时间戳为timestamp2。
通过这样的比对可以有效解决消息丢失的问题。但时间戳由于有时钟不同步、或者一个时间戳内多条消息的可能性,存在误差,因此可以使用全局的自增序列版本号来代替。

二、消息不重复

消息重复的原因

在上面列举的丢失消息的几种可能性中,第 3 种可能性存在一种场景,步骤 4 将消息成功推送给用户B,但步骤 3 因为某些原因导致超时、用户A收不到响应,这个时候会触发重传机制,用户A重新发送请求,用户B可能会收到重复消息。

消息重复的解决方案

IM服务器推送消息时,携带一个Sequence ID,这个Sequence ID在本次连接会话中唯一,同时针对同一条消息不变。当接收方接收到消息后,会根据这个Sequence ID来进行业务层的去重,可以有效地保证消息的不重复。

三、小结

通过业务层的ACK机制、重传机制和完整性检查,可以有效解决推送过程中消息丢失的问题;
通过客户端的去重机制,可以有效解决消息重复的问题。