即时通讯系统单聊、群聊消息顺序一致性解决方案

TLDR,服务端给消息分配分布式id,客户端重排顺序

发送端 —> 服务端 —> (若干或单个,包括不同用户、或统一用户不同设备)接收端
发送端和接收端是客户端的两种角色,客户端既是发送端又是接收端。

接收端是一个时,属于单聊
接收端是多个时,属于群聊

消息发送与接受流程涉及到如下顺序:

  1. 用户发送消息的顺序(用户认知中的顺序)
  2. 发送端发送消息的顺序
  3. 服务器接收消息的顺序
  4. 服务端处理消息的顺序
  5. 服务器发送消息的顺序
  6. 接收端接收消息的顺序
  7. 接收端处理消息的顺序
  8. 用户看到消息的顺序(正确顺序是消息发送者认知中的顺序)

目的是保证步骤1的顺序和步骤7的顺序一致

步骤1:当用户点击发送按钮,消息显示在聊天界面且没有提示发送失败就算消息发送成功,用户看到的顺序就是用户认知中的顺序,也是需要在接收端重现的顺序。

步骤2:客户端发送消息的顺序可能会和用户认知发送消息的顺序不一致,这取决于具体实现。

例一:用户点击按钮后,消息1正在被客户端处理时,消息2也被点击发送,但是消息2先处理完并发送且服务器成功接收,随后消息1发送且服务器成功接收。

例二:用户点击按钮后,消息1被发送出去后,随后消息2也被发送出去且发送成功,随后消息1由于网络原因发送失败,此时假定客户端会自动重试一次,结果重试时发送成功,就导致顺序不一致。

例三:
在用户的前一条消息发送消息没有最中结果之前,发送按钮不能按下,最终结果是指服务端成功接收消息,或者客户端发现有网络问题且在设计的有限次数内重试均失败,这样可以保证有序

例四:
可以要求服务端接收到消息并且接收端都接收到消息才算成功(不过这样太复杂,要考虑部分成功部分失败之后才能算客户端发送成功的问题,复杂程度和并发性能显然例三均占优势)

步骤3:服务端接收消息的顺序取决于客户端发送消息的顺序以及网络情况,客户端发送消息的顺序根据实现方案可控或者不可控,由于网络情况导致的服务端接收顺序是不可控的。

步骤4:服务端接收和处理消息并不需要一定按照同一顺序,可以选择先接收收集一批之后再统一处理,但由于即时通讯场景要求一个即时,此处接收和处理顺序保持一致。

步骤5,同理,服务器处理消息和发送消息的顺序并不需要按照同一顺序,可以选择先收集一批之后再统一发送,但由于即时通讯场景主打一个即时,此处处理和发送顺序保持一致。

步骤6,接收端接收消息的顺序取决于服务端发送消息的顺序和网络情况,按照当前步骤服务端发送消息的顺序会受到接收消息过程的网络情况以及消息处理过程的影响,整体不可控,以及服务端发送到接收端的网络情况不能控,所以接收端接收消息的顺序整体不可控。

步骤7:接收端处理消息的顺序同样取决于具体实现,通常是接受了就处理。

步骤8,用户接收消息的顺序就是界面呈现的消息的顺序,这个应当于发送者认知中的消息一致,由于即时性的要求,当接收端接收到消息,就一定会展示在界面上,而由于接收端接收消息顺序不可控,则界面展示的消息顺序不可控。

结论,上述是自然发送消息的流程,整体上消息顺序不可控,主要因素是网络情况和服务端消息处理情况。

在理想情况下,网络传输不会失败,任何时间任何地点任何人发送任何消息到服务端的耗时一样,任何时间任何地点任何人发送的任何消息在服务端任何机器处理时耗时一样,任何时间任何地点任何人发送的任何消息由服务端转发到接收端耗时一样,满足以上条件,则自然发送的任何消息才能百分之百保证顺序,且这个有序是整个即时通讯系统所有消息有序。

在实际情况下,自然发送的消息顺序是不可控的,但是站在需求的角度,只需要保证某一个场景下的消息顺序一致即可。单聊场景下,A与B聊天,A与C聊天,只需要保证AB之间、AC之间分别有序即可,B和C同时给A发送了一条消息,哪条消息晚到并不会影响A和对方的理解,群聊同理,一个群内保证有序即可。

所以现在是需要解决同一个单聊和同一个群聊的消息顺序问题,如果将单聊理解为两个人的群聊那么实质上就是解决同一个消息上下文中的消息顺序的问题。

步骤一:由客户控制。
步骤二:采用例三,保证同一个用户在同一个消息上下文中消息发送到服务端且服务端接收成功有序即可。
步骤三:自然接收即可。
步骤四:接受完直接处理。
步骤五:处理完直接发送。
步骤六:自然接收即可。
步骤七:自然处理即可。
步骤八:按自然接收顺序显示?不不不,需要对消息重新排序

上述整个消息发送流程,整体上是遵循先来后到的原则,但由于多种因素(主要是网络因素)的影响,会造成步骤七顺序不可控,在实际网络环境下,可以确定一定时间间隔以外的消息顺序是可信的,例如,十分钟之前发送成功的消息和十分钟之后发送成功的消息我们可以认为其自然顺序是正确的,在现有网络环境下,一条消息从发送到接收中间的影响因素不会造成10分钟这么大误差,也就是说假定这个误差是10分钟,只需要保证当前以及往前十分钟内的消息顺序即可。这个误差时间主要取决于网络质量和服务端消息处理耗时,也就是整个链路的耗时,当前实际情况误差最多不超过秒级别,假定误差是10s,则只需保证10s内消息有序即可。

思路是这样的:
步骤一:客户发送
步骤二:发送端采用例三,严格有序,消息带一个发送时间
步骤三-七:自然处理即可
步骤八:当最新的消息处理完后,用户看到的最新的10秒的消息要经过重新排列,依据就是消息在发送端携带的发送时间

简而言之,确定一个误差间隔,接收端新来一条消息,根据消息发送时间重排在这个时间间隔内重排消息即可。

众所周知,客户端的时间不可信,所以必然使用服务端的时间,一是服务端时间一般是使用NTP方案,存在跳变和略微的延迟情况,二是在消息里记录时间的实现上,是在客户端请求服务端时间记录还是发送到服务端然后再记录,无论前者后者都与该消息实际发送时间有一个网络传输误差,这个大概在1s以内,相对而言,我认为后者更稳妥一些。

总结一下,在消息自然传输情况下,10s之前的消息和十s之后的消息顺序一定没有问题,10s前到现在这个时间内来的消息顺序不能保证,所以要根据消息发送时间排序,而由于发送时间也有一个一秒的误差,导致这1s内的顺序不能保证。

现在的问题是如何解决最后一秒的误差。

实际上可以为每个消息上下文分配一个服务端id序列,这个上下文中的每条消息在记录发送时间的同时记录一个服务端分配的id,由于服务端iid是自增的,这样整个消息上下文中所有的消息都是有序的。

到此为止,方案是,近10s消息按照服务端iid重排即可。

最后看一下这个流程,假设有一个群聊,群里有三个人,首先在这个群中每个人自己发的消息是有序的(客户端发送消息上一条发送成功下一条才发,到服务端有一个自增服务端id,这个id顺序必然是对的),然后整个群里面10s前的消息和10s后的消息顺序一定是对的,那么就剩下近10s内不同人之间发送的消息,关于这个也会根据服务端id进行排序,但这个排序不一定能反应真是顺序,可能会有很小的误差,因为即使三个人消息同时到达服务端由三个线程处理,但服务端id分配一定有先后,这个可能与真是顺序不同,但对人来说是无感的,所以虽然不精确但可以满足需求。

如果实际实现后,发现自然消息传输最大误差为1s,按照上述方案,近1s消息按照服务端id重排即可。

使用服务端id解决发送时间的秒级误差,通常在分布式环境,这个服务端id会使用分布式id,所以要求这个分布式id严格递增,能克服时间跳变问题,例如雪花算法就不太行。
一是时间跳变问题
二是,雪花算法有workid的概念,同一个worker申请的id是递增的,但不同的之间就不是了。

感觉可以参考一下腾讯的文档,https://cloud.tencent.com/product/im

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值