即时通讯-N-如何保证消息的可靠性展示

Posted 潘永强_IT大厂老潘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了即时通讯-N-如何保证消息的可靠性展示相关的知识,希望对你有一定的参考价值。

结论先行

客户端如何在推拉结合的模式下保证消息的可靠性展示?

  1. 原则: server拉取的消息一定是连续的
  2. 原则: 端侧记录的消息的连续段有两个作用: 1. 记录消息的连续性, 即起始中间没有断层, 2. 消息连续, 同时意味着消息是最新的,消息不是过期的。
  3. 同步协议过载(SyncGapOverflow)时,通过清空会话消息连续段的机制,可以简单粗暴有效的处理同步过载,保证了端侧的连续段一定是连续,并且是最新的
  4. 同步协议过载(SyncGapOverflow)时, 也可以通过端侧不清空连续段的方式, (多个连续段, 中间有间隔)。 会话消息做展示时, 缓存+远端的方式, 保证消息的连续性。 但是这种方式对于历史的消息,可能会存在更新丢失的case, 即消息不是最新的, 因而不推荐使用。如果要使用, 需要配套其他的解决方案。 本文不做展开。
  5. 同步协议中, 需要区分会话消息是新增消息,还是更新消息。两者处理机制不同 新增消息除了入库, 还需要处理消息的连续段; 而更新消息,如果端侧有此消息的缓存, 消息更新,不处理连续段; 没有缓存此消息时, 不需要做消息的更新。
  6. 对于App内发送消息需要特殊处理 App内发送的消息, 仅仅做上屏, 不做连续段, 连续段的更新操作是同步协议下发的此条消息,才会做处理。

引言

同步过载(GAP)指的是,同步协议同步过载,具体是指server向端侧同步时发现端侧是 “首次登录此设备” 或者“ 之前登录过这次再登录”。同步过载的处理细节对于端到端的可靠性展示的重要部分。为了能够更好的解释消息过载, 我们从消息的可靠性入手。
端到端的消息的可靠性, 指的是消息的可靠性生产可靠性消费
此处的消息可以理解为事件。 可靠性生产, 包含了消息不重复生产。
而可靠性消费, 指的是消息传递时, 不丢失, 不重复, 有序,及时

在做及时通讯时, 除了要保证端到端的可靠性传递, 还需要保证展示给用户时, 会话的消息层面的可靠性展示。

什么是可靠性展示

可靠性展示,指的是在用户查看消息时, 展示给用户的消息是不丢失的,不重复的, 并且是有序,最新的的。即有序+不丢+不重+最新

以下我们从简单到复杂, 逐步设计出推拉结合的模式下会话消息的可靠性展示。

通知消息的可靠性展示(普通消费-离店概不负责)

以我们普通的非及时通讯的App为例, 都会有一个消息通知的盒子。 用于接收软件发送给我们的消息。 那么这样的通知消息, 在推拉模式下如何实现呢?

在实现前,先看下这个功能有那些细分的功能。

案例: 西游记开公司了

西游师徒四人开了家西游记的公司, 运营了西游记的App, 主要记录西游记的八十一难以及各路妖怪为什么要这么处理的幕后故事。老板是唐僧, 悟空是CTO, 八戒是运营,沙僧是一线研发。
八戒看到其他的App有通知消息, 于是告知悟空西游记的App也要增加通知消息。悟空指给了沙僧做此项目的负责人。
沙僧先后做了如下的工作,

  1. 功能细分
  2. 技术方案设计
  3. 研发上线等其他工作

细分功能

  1. 展示通知消息的未读数
  2. 展示历史消息
  3. 支持向上翻
  4. 支持实时推送新消息

技术方案

在仅有通知消息的一个单一会话时, 通过推拉结合的方式能够很方便的实现此功能。 为了与下文的即时通讯的会话消息的feature匹配,我们将会话单独作为一个功能。与其他篇一致,根据四个场景 用户的新设备登录暂短离线后再次在线用户长期离线后再次在线 以及用户在线收到消息做问题解决。

结论先行

  1. 消息的不重复, 不丢失, 有序, 通过推拉结合机制+端内缓存实现的
    不重复和不丢失, 在推送场景下通过ack实现; 在拉取场景下, 通过标记会话的gap, 以及server拉取到的消息是连续的保证的。
    有序, 在推送时保证有序, 在拉取时,保证连续和有序实现的。
  2. 消息与会话完全独立。 消息的未读数由server完全控制。client仅做同步。 如新收到一条未读消息, 那么同步协议需要产生两个事件, 一个是新消息的事件, 另外一个是会话的变更。
  3. 消息过载时,通过清空连续段的方式,可以简单的处理消息过载。即端侧缓存的消息一定是位于连续段中的, 远端仅补充缓存消息两侧消息的方式, 用于保证整体连续。
  4. 消息过载时, 也可以通过增加标记lastMsg与历史消息的连续段的方式。 会话消息做展示时, 缓存+远端的方式, 保证消息的连续性。 这种方式更加标准, 但是却更加复杂。
  5. 消息的同步事件, 仅需要包含新增消息 新增消息是可以追加到队尾的, 消息段自动是连续的。

新设备用户登录

状态图

具体case处理图示

单一连续段的示意图(同步过载即清空)

多连续段的示意图(过载不清空)

逻辑示意图

Case 用户新设备登录同步与拉取会话

端侧标记SyncGapOverflow的处理 直接在会话层面清空连续段。

Case 点击进入会话

检查是否有连续段: 对于端内没有连续段的或者连续段内的消息列表不满足一屏的, 拉取最新消息; 否则, 直接展示端内缓存的消息即可

Case 翻上一页

携带最上方一条消息的时间戳或者消息id, 做上一页的数据拉取: 直接根据时间戳, 或者最上方一条消息的msgId做分割拉取即可。

Case 实时推送

处理时, 需要根据lastMsg以及连续段做处理对于有连续段,并且连续段的截止的消息是lastMsg, 消息追加到前一个连续段;对于端侧没有连续段,新增连续段; 如果消息已经位于某个连续段, 不需要处理连续段; 如果消息未位于某个连续段, 新增连续段。

其他场景

  1. 暂短离线后再次在线与在线同步的逻辑一致, 不再赘述。
  2. 用户长期离线后再次在线, 处理逻辑基本一致, 除了上传的同步位点不同, 以及存储的同步位点不同。
  3. 用户在线收到消息: 同在线同步, 不再赘述。

结论

  1. 消息的不重复, 不丢失, 有序, 通过推拉结合机制+端内缓存实现的
    不重复和不丢失, 在推送场景下通过ack实现; 在拉取场景下, 通过标记会话的gap, 以及server拉取到的消息是连续的保证的。
    有序, 在推送时保证有序, 在拉取时,保证连续和有序实现的。
  2. 消息与会话完全独立。 消息的未读数由server完全控制。client仅做同步。 如新收到一条未读消息, 那么同步协议需要产生两个事件, 一个是新消息的事件, 另外一个是会话的变更。
  3. 消息过载时,通过清空连续段的方式,可以简单的处理消息过载。即端侧缓存的消息一定是位于连续段中的, 远端仅补充缓存消息两侧消息的方式, 用于保证整体连续。
  4. 消息过载时, 也可以通过增加标记lastMsg与历史消息的连续段的方式。 会话消息做展示时, 缓存+远端的方式, 保证消息的连续性。 这种方式更加标准, 但是却更加复杂。
  5. 消息的同步事件, 仅需要包含新增消息 新增消息是可以追加到队尾的, 消息段自动是连续的。

支持撤回或者更新的通知消息的可靠性展示(消费升级-退款)

案例: 西游记App出事了, “端午节快乐”的消息要更新或者撤回

八戒在端午节给大家推送了一条端午节快乐, 被老板唐僧看到后, 说端午节的祝福是端午节安康, 而不是端午节快乐。八戒这时候, 找到沙僧, 能不能将已有的推送信息更改掉? 按照一般的推送消息, 推送只能增加, 却不能删除, 或者更新, 这时候就尴尬了。 八戒找唐僧投诉, 唐僧找悟空投诉, 悟空骂你, 为何当时不支持消息更新或者消息撤回。你说当时没说要支持这个呀。。。 挨了骂, 再次重新设计下吧。

相比较普通的通知消息, 支持撤回以及已有消息的更新, 此新增 feature, 更加符合通知消息的特征。 尤其是对于出现上面的紧急情况, 有此利器, 应对自如。

技术方案

结论先行

  1. 将消息撤回 以及 消息更新的操作统一认作是更新消息,并且仅是逻辑删除, 非物理删除。更新消息,如果端侧有此消息的缓存, 做更新, 没有缓存到, 不需要做更新。
  2. 更新消息, 采用出现同步GAP时, 清空连续段的方式是简单而且有效的, 不建议采用标记多个连续段, 否则逻辑会变得特别复杂。
  3. 对于收取到的实时推送消息, 在会话没有过期时, 需要区分是新增消息还是更新消息,对于更新消息的case下, 检查本地是否存在已有的消息, 如果不存在, 忽略即可, 如果存在,更新消息, 不需要处理连续段。
  4. 其他结论: 同普通的消息通知的处理。

新设备用户登录

状态图

同上, 不变

具体case处理图示

同步过载时不清空连续段示意图

在上图的场景下,消息更新可以认为有两个场景:

  1. 同步协议未过载时
  2. 同步协议过载时,需要漫游处理时。

同步协议未过载时(对应的场景: 消息更新-同步协议不过载时),通过同步协议可以直接同步到端侧, 消息入库+更新UI即可。
而同步协议过载时(对应的场景是: 消息更新-端内漫游拉取历史), 用户在进入到会话中, 通过漫游拉取的形式时,由于多连续段并未清空, 因而导致以为消息均是有效的, 最终在将连续段合并起来时, m3的消息,依然是老的消息, 不是server的m3_v2的版本的消息。

同步过载时清空连续段的示意图

同上, 根据两个场景分析,

  1. 同步协议未过载时(对应的场景: 消息更新-同步协议不过载时),通过同步协议可以直接同步到端侧, 消息入库+更新UI即可。
  2. 同步协议过载时(对应的场景是: 消息更新-端内漫游拉取历史), 用户在进入到会话中, 通过漫游拉取的形式时,由于多连续段在发生过载时已经清空, 因而导致以为消息均是无效的, 因而历史消息均是通过漫游拉取下来的。所以消息翻到m3的page时,m3的消息是被server覆盖的, 因而展示的数据便是m3_v2。 保证了消息的准确性。
逻辑示意图

Case 用户新设备登录同步与拉取会话

同上, 不变

点击进入会话

同上, 不变

翻上一页

同上, 不变

实时推送

  1. 将消息撤回 以及 消息更新的操作统一认作是更新消息,并且仅是逻辑删除, 非物理删除。
  2. 对于收取到的实时推送消息, 需要区分是新增消息还是更新消息,对于更新消息的case下, 不需要处理连续段。

其他场景

  1. 暂短离线后再次在线与在线同步的逻辑一致, 不再赘述。
  2. 用户长期离线后再次在线, 如果采用不清空连续段的方式, 即在后方追加GAP的形式, 更新消息会存在问题, 因为没有办法感知到历史消息的变化。 因而采用单一连续段的形式是比较合适的。
  3. 用户在线收到消息: 同在线同步, 不再赘述。

结论

  1. 将消息撤回 以及 消息更新的操作统一认作是更新消息,并且仅是逻辑删除, 非物理删除。更新消息,如果端侧有此消息的缓存, 做更新, 没有缓存到, 不需要做更新。
  2. 更新消息, 采用出现同步GAP时, 采用清空连续段的方式
  3. 对于收取到的实时推送消息, 在会话没有过期时, 需要区分是新增消息还是更新消息,对于更新消息的case下, 检查本地是否存在已有的消息, 如果不存在, 忽略即可, 如果存在,更新消息, 不需要处理连续段。
  4. 其他结论: 同普通的消息通知的处理。

正常IM单个会话的可靠性展示(生产消息+消费消息)

案例: 唐僧要把通知消息变为公众号对话

西游记App的用户越来越多, 用户对于通知类的消息, 每次都需要点击到具体的消息,而为了促进用户的活跃以及问题能够及时反馈, 唐僧说, 这个通知消息里面,要支持用户跟我们互动: 如我们发起了一个活动, 直接推送一个文案到这个消息里面, 用户可以直接回复消息。 以及用户常见的疑问, 在我们这个里面,我们可以直接解决。悟空说, 这个活本来也是你干的, 现在需要继续想解决方案和实施方案。

思考

核心问题: 生产消息与消费消息两者并存, 如何解决连续性的问题。
上面提到的通知消息以及通知消息支持撤回或者更新的case,解决了生产消息连续段的标记。

而支持发送消息与前面的仅仅展示型的消息是不同的。 发送消息是生产消息,同步与拉取消息是消费消息。出现了生产, 那么端上的处理机制会产生比较大的变化。如:

  1. 发送消息时, 消息未发送成功, 如何跟已经发送成功的消息混排?
  2. 消息发送成功后, 连续段应该如何处理?
  3. 弱网情况下, 如果消息没有同步下来, 但是用户先去发送消息了,这时候连续段又应该如何处理?
  4. 弱网情况下, 用户先发送消息, 并且成功了, 稍后同步过载的事件通知到端上了, 此时的连续段有应该如何处理?
    此时我们会发现, 增加了发送消息后, 技术方案会变得复杂了许多。

技术方案

思路:

  1. 连续段的处理逻辑: 消息漫游触发变更连续段, server的同步事件触发变更连续段, 本地发送的消息, 不触发连续段的计算。
  2. 本地发送的消息虽然不参与连续段的计算, 但是却需要参与数据的展示, 否则, 会导致消息丢失。

根据这个思路, 那么弱网的两种case也是迎刃而解的。

  1. 弱网,消息同步失败,用户先发送消息,发送成功, 稍后,在网络恢复后, 同步消息会同步到端上,只不过此次弱网情况下, 对于用户而言, 有些消息看不到
  2. 弱网, 同步过载事件同步失败, 用户先发送消息, 发送成功, 稍后, 在网络恢复后, 同步过载的事件同步到端上。端上清空连续段, 再次进入到会话时,通过漫游更新的消息内容。

结论先行

  1. 连续段的处理逻辑: 消息漫游触发变更连续段, server的同步事件触发变更连续段, 本地发送的消息, 不触发连续段的计算。
  2. 本地发送的消息虽然不参与连续段的计算, 但是却需要参与数据的展示, 否则, 会导致消息丢失。
  3. 其他结论, 同支持撤回或者更新的通知消息。

具体case处理图示

由于过载不清空连续段存在消息不是最新的case, 因而以下我们只展示过载时清空连续段的方案。

结论

  1. 连续段的处理逻辑: 消息漫游触发变更连续段, server的同步事件触发变更连续段, 本地发送的消息, 不触发连续段的计算。
  2. 本地发送的消息虽然不参与连续段的计算, 但是却需要参与数据的展示, 否则, 会导致消息丢失。
  3. 其他结论, 同支持撤回或者更新的通知消息。

总结

上文通过三种场景,普通通知消息(不支持撤回/更新历史消息), 支持更新历史消息的通知消息,到最终的正常IM单个会话,通过三种场景, 最终得出客户端如何在推拉结合的模式下保证消息的可靠性展示的关键思路:

  1. 原则: server拉取的消息一定是连续的
  2. 原则: 端侧记录的消息的连续段有两个作用: 1. 记录消息的连续性, 即起始中间没有断层, 2. 消息连续, 同时意味着消息是最新的,消息不是过期的。
  3. 同步协议过载(SyncGapOverflow)时,通过清空会话消息连续段的机制,可以简单粗暴有效的处理同步过载,保证了端侧的连续段一定是连续,并且是最新的
  4. 同步协议过载(SyncGapOverflow)时, 也可以通过端侧不清空连续段的方式, (多个连续段, 中间有间隔)。 会话消息做展示时, 缓存+远端的方式, 保证消息的连续性。 但是这种方式对于历史的消息,可能会存在更新丢失的case, 即消息不是最新的, 因而不推荐使用。如果要使用, 需要配套其他的解决方案。 本文不做展开。
  5. 同步协议中, 需要区分会话消息是新增消息,还是更新消息。两者处理机制不同 新增消息除了入库, 还需要处理消息的连续段; 而更新消息,如果端侧有此消息的缓存, 消息更新,不处理连续段; 没有缓存此消息时, 不需要做消息的更新。
  6. 对于App内发送消息需要特殊处理 App内发送的消息, 仅仅做上屏, 不做连续段, 连续段的更新操作是同步协议下发的此条消息,才会做处理。

IM系统如何保障消息高可靠性

        消息传输的高可靠性在即时通讯系统中是最为基础、也是最核心的部分之一,同时也是衡量通讯系统的质量的重要指标。本文主要描述常见通讯系统模型的实现原理,分析影响消息传输可靠性的常见问题,并介绍有度即时通服务体系是如何通过架构及技术细节实现高可靠的消息送达机制。
快递式系统模型是常见的即时通讯系统模型,主要结构如下图所示:
技术图片
该模型的消息发送过程不是一个完整事务,以下这些场景将出现丢消息的问题:
1、 ClientA将发消息送给Server,Server收到后回复ClientA发送成功,后续中转由Server保障,此时如果Server进程重启,内存中的消息没有存储,将出现消息丢失。
2、 ClientA将消息成功发送给Server,Server转发给ClientB,ClientB网络层收到后回复接收成功,但ClientB尚未将消息保存到数据库,此时ClientB进程关闭,也将出现消息丢失;
3、 ClientA将消息成功发送给Server,Server发现ClientB为离线,将消息发送给离线消息服务,离线消息服务回复接收成功,但离线消息未保存进磁盘前进程异常退出,也将出现丢失消息;
       后面出现了一种改进模型,这种改进可以保障网络层不丢消息,但从整体系统架构来看依然有漏洞,网络程序设计架构大体如下:
技术图片
        网络层(Net)收到请求,回复对方消息已收到,接下来将请求抛给Logic层,Logic层将消息保存到Store层并抛给UI层。
通过实践发现,Net回复对方消息已收到后,消息未保存进数据库之前程序退出,将出现消息丢失。
        如果是离线消息服务,网络层收到很多离线消息抛给Logic层,Logic落地磁盘速度如果跟不上,Logic的队列中将堆积有消息,此时服务重启,Logic队列中未保存的离线消息都将丢失。
这种演进解决原来系统中的问题1,但问题2、3并没有解决,快递式即时通讯系统消息到达率难以有效保障,容易由中间环节、程序设计等导致消息丢失。
        邮件服务是一种历史悠久的通讯系统,客户端发送邮件是一个完整的事务,只有消息落地后才返回成功,中间任何一个环节出现故障都回复失败,该模式能保障发消息不丢,如果再保障接收方能成功拉取到消息,将演进出一种更极致的消息高到达率解决方案,邮件服务模型如下:

技术图片
        有度即时通讯系统参考邮件服务模型,消息服务器漫游,将邮件轮询机制改造为新消息push通知,系统采用微服务模式设计,支持服务器集群式部署及扩展,多种措施并用,保障消息到达率。
        系统设计即采用先进的数据存储技术在服务器端存储消息,只要服务器确认接收到消息,消息将一直保存在服务器,不会随用户读取而删除。在网络异常、客户端设备异常时,仍能通过二次请求获取到消息。
        微服务架构将业务相对独立的拆分为多个服务,可以分机部署,这种模式能够有效的实现故障隔离,提升整体服务的可靠性和可用性。
        每个会话参与者的消息数据是共享的,避免从发送者发送成功转换到接收方待收消息的故障问题,只要发送的消息成功(成功即入库),整个会话参与人一定能拉取该消息。
        客户端发送消息时通过调用RESTFUL API接口。API接口保障回复成功时即入库。当客户端调用接口失败或超时会采用多次重试机制,发送失败的消息客户端也会给出明确的提示来告诉发送方。
        服务器的消息存储机制经过多次演进,从最开始的MySQL存储演变到现在的Redis+Leveldb,其可靠性接受极致。客户端发送消息立即入库,入库操作包含写入内存数据表(双表缓冲)和pending日志(异常保障),内存数据表满时转换为只读并持久化,持久化也是使用高效的顺序存储方式(受益于LevelDB的实现机制),同时可以使用备份服务器实现数据主从同步,确保文件系统发生故障时容灾恢复能力。
        客户端通过服务器通知事件拉取消息,获取消息时会自动补全完整的消息记录。在网络发生波动时,客户端会重新建立连接并立即拉取所有的未读会话。

详细了解请查看

以上是关于即时通讯-N-如何保证消息的可靠性展示的主要内容,如果未能解决你的问题,请参考以下文章

IM即时通讯开发架构:可靠性有序性弱网优化

IM即时通讯架构技术:可靠性有序性弱网优化等

从客户端的角度来看移动端IM即时通讯的消息可靠性和送达机制

即时通讯怎么做到安全可靠的传输信息

im即时通讯开发:如何保证消息的“时序性”与“一致性”

浅析IM即时通讯开发中消息“可靠性”和“一致性”