理解IM消息“可靠性”和“一致性”问题,以及解决方案探讨

本文做者“商文默”,有修订和改动。php

一、写在前面

我整理的大量IM技术文章中(见本文末“参考资料”一节),有关消息可靠性和一致性问题的文章占了很大比重,缘由是IM这类系统抛开各类眼花缭乱的产品功能和技术特性,保证消息的可靠性和一致性几乎是IM产品必需的素质。html

试想若是一个IM连发出的消息都不知道对方到底能不能收到、发出的聊天内容对方看到的究竟是不是“胡言乱语”(严重乱序问题),这样的APP用户确定不会让他在手机上过夜(确定第一时间卸载了),由于最基本的聊天逻辑都没法实现,它已经失去了IM软件自己的意义。前端

不过,另外一个方面来说,IM系统是不标准的(虽然曾经XMPP这种协议试图解决这个问题,但事实证实那根本不现实),各家几乎都是自已的私有协议、不一样的实现逻辑,这也决定了即便同一个技术问题,对于IM来讲很难有固定的实现套路和标准的解决方案。git

因此,对于本文来讲,文中做者虽然提供了有关IM消息“可靠性”与“一致性”问题的解决方案,但方案到底合不合理、适不适合你,这就是仁者见仁、智者见智的事了。用人话说就是:本文内容仅供参考,具体的解决方案请务结合自已的系统构架和实现状况,多阅读几篇有关这个技术话题的文章,取其精华,找到适合自已的技术方案和思路才是最明智的。github

二、本文引言

丛所周之,即时通信聊天(IM)系统必须要解决消息可靠性及消息一致性问题(**PS:**若是具体IM系统是什么你都还没弄明白,先读这篇《零基础IM开发入门(一):什么是IM系统?》)。算法

这两个问题,通俗来讲就是:服务器

  • 1)消息可靠性:简单来讲就是不丢消息,会话一方发送消息,消息成功到达对方并正确显示;
  • 2)消息一致性:包括发送一方消息一致及会话双方消息一致,要求消息不重复,不乱序。

本文会从典型的IM消息发送逻辑开始,简单易懂地阐明消息可靠性、一致性问题的原理及可参考的技术解决方法,或许技术方案并不完美,但但愿能为你的IM技术问题解决带来启发。微信

三、典型IM消息发送过程

IM的消息发送通常的实现过程能够分为两个阶段:markdown

  • 1)发送方发送消息、服务端接收、返回消息 ACK 给发送方;
  • 2)服务端将消息推送到接收方。

判断消息发送是否成功主要依据第一阶段——即服务器是否接受到消息。架构

对于消息发送者来讲,消息状态能够分为三类:

  • 1)正在发送;
  • 2)发送成功;
  • 3)发送失败。

具体来讲,这三类状态的具体意义是:

  • 1)正在发送:发送方触发发送事件开始,到收到服务端返回消息对应 ACK 以前;
  • 2)发送成功:发送方收到消息对应 ACK 回复;
  • 3)发送失败:超过必定重发次数,未收到消息对应 ACK 回复。

对应的消息发送流程以下图所示:

四、IM消息可靠性

限于篇幅,对于IM消息可靠性的基本概念和详细原理建议阅读《零基础IM开发入门(三):什么是IM系统的可靠性?》,本文着重谈谈解决思路。

4.1 重发机制

保证消息发送第一阶段(见本文“三、典型IM消息发送过程”一节)消息成功发送的方法是设立重发机制:

  • 1)依据必定时长内是否收到消息对应 ACK,判断消息是否要重发;
  • 2)若是超过预设时长,就从新发送;
  • 3)当重发次数超过预设次数,就再也不重发,断定该消息发送失败,修改消息发送状态。

PS: 具体的完整方案级代码实现,能够参考MobileIMSDK 中有关QoS机制的代码实现。

4.2 会话记录检查

消息发送第二阶段(见本文“三、典型IM消息发送过程”一节)服务端推送消息到接收方,若是链接断开,会丢失消息。

因此要保证消息完整,就须要在创建链接后,根据上一条消息(已经 ACK)时间戳,获取会话记录,一次返回一段时间内全部消息(PS: 中大型应用中,消息的拉取也不是个简单事情,详情能够阅读《IM开发干货分享:如何优雅的实现大量离线消息的可靠投递》)。

另外一种保证方法是加入定时轮询,检查消息完整性,具体的思路以下图所示。

创建链接流程图:

4.3 须要考虑的两个问题

消息重发、会话记录检查须要考虑两个问题:

  • 1)消息是否会重复发送;
  • 2)消息顺序是否会被打乱。

举两个例子。

关于消息重发问题:

  • 1)若是丢消息的点在消息达到服务端以前,服务端并无收到消息,发送方从新发送丢失消息,服务端接收成功,不会产生两条相同消息;
  • 2)而若是服务端接收到消息,返回 ACK 丢失,这时再发送一次相同消息,就可能形成消息重复。

关于消息顺序问题:

  • 1)若是发送方连发三条消息,第1、第三条成功被服务端接收,第二条丢了,那第三条消息是否会被记录?
  • 2)若是这时第二条消息达到服务端,其顺序是在第三条时间以前仍是以后(服务端通常都会给记录打一个时间戳)?

五、IM消息一致性

同上节同样,对于IM消息一致性的基本概念和详细原理建议阅读《零基础IM开发入门(四):什么是IM系统的消息时序一致性?》。

5.1 使用 uuid 消息去重

对于消息重发问题,能够给每条消息增长属性 uuid 做为消息惟一标识,重发消息 uuid 不变,前端根据 uuid 去重。大体思路就是这样。

PS: 对于IM来讲,消息ID也是个很大的技术话题,有兴趣能够读下面这个系列:

IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)

IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)

IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略

IM消息ID技术专题(四):深度解密美团的分布式ID生成算法

IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现

IM消息ID技术专题(六):深度解密滴滴的高性能ID生成器(Tinyid)

5.2 使用向量时钟进行消息排序

对于消息排序问题: 由于在聊天中,消息的顺序对于发送方的表述有重要的影响,消息不完整或顺序颠倒均可能形成语意不连贯,甚至曲解。因此须要保证发送方发送消息顺序,而会话双方消息排序须要考虑实际状况。

在通常的认知里: 状态是正在发送的消息,应该尚未被对方看到,只有发送成功的消息,才会被对方看到。但在实现中,消息发送成功是以服务器接收消息并返回 ACK 成功为判断依据,而不是被对方接收到。

那么就会出现这样一个问题: 若是一条消息状态是正在发送,此时收到一条消息,那么收到的消息是在正在发送的消息以前仍是以后?

这是一个上下文关系,关键问题是:发送方是以哪条所见消息为依据发送消息的。

这里提供一种思路: 借鉴分布式系统中的向量时钟算法(见《分布式系统中的向量时钟算法》)。

先简单描述向量时钟算法:

向量时钟算法用于在分布式系统中生成事件偏序关系,并纠正因果关系。一个系统包含 N 个节点,每一个节点产生的消息体中包含该节点的逻辑时钟,总体系统的向量时钟由 N 维逻辑时钟组成,并在每一个节点产生的消息体中传递。

简单来讲,向量时钟算法的实现原理以下:

  • 1)初始状态,向量值为 0;
  • 2)每次节点处理完节点事件,该节点时钟+1;
  • 3)每次节点发送消息,将包含自身时钟的系统向量时钟一块儿发送;
  • 4)每次节点收到消息,更新向量时钟,该节点时钟+1,其余节点对比每一个节点本地保留的向量时钟值和消息体中向量时钟值,取最大值;
  • 5)节点同时收到多条消息,判断接收消息的向量时钟之间是否存在偏序关系。

针对上述的第5)点:

  • 1)若是存在偏序关系,则合并向量时钟,取偏序较大的向量时钟;
  • 2)若是不存在偏序关系,则不能合并。

偏序关系: 若是 A 向量中的每一维都大于等于 B 向量,则 A、B 之间存在偏序关系,不然不存在偏序关系。

对于IM为聊天消息排序来讲,其实就是处理聊天消息的上下文语境,决定消息之间的因果关系。

参考向量时钟算法: 假设有 N 个消息会话方,系统的向量时钟由 N 维时钟组成,向量时钟在各方发送的消息体中传递,并依据向量时钟排序。

具体实现思路以下:

  • 1)系统向量时钟设为 (0, 0, …, N);
  • 2)节点发送消息,更新系统向量时钟,该节点时钟加一,其余节点不变;
  • 3)节点接收消息,更新系统向量时钟,该节点时钟加一;其余节点对比每一个节点本地保留的向量时钟的值和消息中向量时钟的值,取最大值;
  • 4)依据消息体内系统向量时钟的偏序关系决定消息顺序。

针对上述第4)点:

  • 1)若是能够肯定偏序关系,则根据偏序关系由小到大显示;
  • 2)若是多条消息不能肯定偏序关系,则按照天然顺序(接收到的顺序)显示。

向量时钟在理论上能够解决大部分消息一致性的问题,但在实现中还须要考虑实际使用时的体验。

这其中最须要关注的问题是: 是否要强制排序,或者说,若是实际显示顺序和向量时钟之间的偏序关系不一致,是否要移动消息之间的顺序。

举个例子: 在一个有多人的会话中,若是有一方网速特别慢,收不到消息,也发不出消息。在他看到的最后的消息以后,其余人已经开始新的话题,这时他关于上一个话题的消息终于发送成功,并被其余人收到。

此时就存在这样一个问题: 这条关于上一个话题的消息是显示在最后,仍是移到较早时间?

  • 1)若是显示在最后,但消息内容和目前的话题不相关,其余人可能会感到莫名其妙;
  • 2)若是把消息移到较早时间,那么这条消息可能不会被其余人看到,或者看到前面多了一条消息,会有种突兀的感受。

IM 的场景不少,也很复杂,更多的时候须要从产品角度考虑问题。

对于消息是否须要排序的问题,这里只提出一个比较通用的方案: 建议会话中不强制排序,会话历史记录中按照向量时钟的偏序关系进行排序。

六、本文小结

对于 IM 系统消息可靠性及一致性问题,经过消息重发机制保证消息成功被服务端接收,经过会话记录检查保证收取消息完整,从而保证整个消息发送过程的可靠性。使用 uuid 消息去重,参考向量时钟算法进行消息排序,为保证消息一致性提供一种解决方案。

总之,IM这类系统看似简单,实则水深似海,若是你是IM开发新手,能够从《新手入门一篇就够:从零开发移动端IM》这篇入手系统学习。若是你自认为已经是IM老手,这里整理的 IM中大型架构设计 方面的文章或许能够参考一下。

七、参考资料

[1] 零基础IM开发入门(三):什么是IM系统的可靠性?

[2] 零基础IM开发入门(四):什么是IM系统的消息时序一致性?

[3] IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

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

[5] 如何保证IM实时消息的“时序性”与“一致性”?

[6] 一个低成本确保IM消息时序的方法探讨

[7] IM群聊消息如此复杂,如何保证不丢不重?

[8] 彻底自已开发的IM该如何设计“失败重试”机制?

[9] IM开发干货分享:如何优雅的实现大量离线消息的可靠投递

[10] 从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

[11] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

[12] 重新手到专家:如何设计一套亿级消息量的分布式IM系统

本文已同步发布于**:** www.52im.net/thread-3574…