IM如何保证消息不乱序

6 篇文章 0 订阅

对于聊天、直播互动等业务来说,消息的时序代表的是发送方的意见表述和接收方的语义逻辑理解,如果时序一致性不能保证,科能就i造成聊天语义不连贯,容易出现曲解和误会。
对于点对点的聊天场景,时序一致性需要保证接收方的接收顺序和发送方的发出顺序一致;而对于群组聊天,时序一致性保证的是群里所有接收人看到的消息展现顺序都一样。

从理论上来说,保持消息的时序一致性不难,理论上,我们想象的消息收发场景中,只有单一的发送方,单一的接收方。

如果发送方和接收方的消息都是单线程操作,并且和IM服务端都只有唯一的一个TCP连接,来进行消息传输,IM服务端也只有一个线程来处理消息接收和消息推送。这种场景下,消息的时序一致性是比较容易能得到保障的。

但在实际的后端工程实现上,由于单发送方,单接收方,单处理线程的模型吞吐量和效率都太低。更多的场景下,我们可能需要面对的多发送方,多接收方,服务端多线程并发处理的情况。
消息的时序一致性其实是要求我们的消息具备“时序可比较性”,也就是消息相对某一个共同的“时序基准”可以进行比较,所以,要保证消息的时序一致性的一个关键问题是:我们能否找到这么一个时序基准。

我们可以分为以下步骤:
1.如何找到时序基准
2.时序基准的可用性问题
3.有了时序基准,还有其他的误差吗?有什么办法可以减少这些误差。

如何找到时序基准?

如果用本地序号和本地时钟是否可以作为“时序基准”?
用本地时间戳或本地维护的一个序号给到IM服务端,IM服务端再把这个时间戳或者序号和消息一起发送给消息接收方,消息接收方根据这个时间戳或者序号来进行消息排序。这种做法有什么缺点?
A.发送方时钟存在较大不稳定因素,用户可以随时调整时钟导致序号回退。
B.发送本地序号如果重装应用会导致序号清零,也会出现回退。
C.类似“群组消息”和“单用户的多点登录” 这种多方发送场景,都存在:同一时钟的某一时间点,可能有多条消息发送给同一接收对象。比如同一个群里,多个人同时发言;

IM服务器的本地时钟是否可以作为“时序基准”?
IM服务器作为时序基准是指:发送方把消息交给IM服务器后,IM服务器依据自身服务器的时钟生成一个时间戳,再把消息推送给接收方时携带这个时间戳,接收方依据这个时间戳来进行消息的排序。

可是在实际工程中,IM服务器都是集群化部署,虽然多台服务器可以通过NTP时间同步服务,能降低服务集群机器间的时钟差异到毫秒级别,但仍然还是存在一定的时钟误差,而且IM服务器规模相对比较大,时钟的同一比较有挑战。

既然单机本地化的时钟或者序号都存在问题,那么如果有一个全局的时钟或者序号是不是就能解决这个问题?所有的消息的排序都依托于这个全局的序号,这样就不存在时钟不同步的问题了。那么,

IM服务端的全局序列是否可能作为“时序基准”?

用Redis的原子自增命令incr,DB自带的自增id,或者类似Twitter的snowflake算法。

但是也有一些问题:Redis的原子自增和DB的自增id,都要求再主库上来执行“取号”操作,而主库基本都是单点部署,可用性上的保障会相对较差,另外,针对高并发的取号操作这个个单点的主库容易出现性能瓶颈。

从业务层面来考虑,对于群聊和多点登录这种场景,没有必要保证全局的跨多个群的绝对时序性,只要保证某一个群的消息有序即可。
如果可以针对每一个群有独立一个“ID生产器”,能通过哈希规则把压力分散到多个主库实例上,大量降低多个群公用一个“ID”生成器的并发压力

时序基准之外的其他误差
有了时序基准,是不是就能保证消息能按照“既定顺序” 到达接收方呢?
不一定:
1.IM服务器都是集群化部署,每台服务器的机器性能存在差异,因此处理效率有差别,并不能保证先到的消息一定可以先推送到接收方,比如有的服务器处理的很慢,或者正好碰到一次GC,导致它接收的更早消息,反而比其他消息更晚推送出去。
2.IM 服务端接收到发送方的消息后,都是多线程处理,比如“取号”“ 暂存消息”等,并不能保证完全一致性。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用Java编写IM(即时通讯)应用可以通过网络发送消息。在Java中,可以使用以下步骤来发送消息: 1. 导入相关的Java网络库,例如java.net和java.io。 2. 创建一个客户端Socket对象,用于与服务器进行通信。 3. 创建一个输出流对象,用于向服务器发送消息。 4. 使用Socket对象的connect()方法连接到服务器。 5. 使用输出流对象的write()方法将消息发送给服务器。 6. 清空输出流对象,确保所有的消息都被发送。 7. 关闭输出流对象和Socket对象。 以下是一个简单的Java程示例,演示了如何发送消息IM服务器: ```java import java.io.*; import java.net.*; public class IMClient { public static void main(String[] args) { try { // 创建客户端Socket对象并连接到服务器 Socket socket = new Socket("服务器地址", 1234); // 创建输出流对象 OutputStream outputStream = socket.getOutputStream(); // 发送消息 String message = "Hello, IM Server!"; outputStream.write(message.getBytes()); // 清空和关闭输出流对象 outputStream.flush(); outputStream.close(); // 关闭Socket对象 socket.close(); } catch (IOException e) { e.printStackTrace(); } } } ``` 在上述示例中,我们创建了一个IMClient类,并在其main()方法中执行了发送消息的操作。我们首先创建了一个Socket对象并使用指定的服务器地址和端口号连接到服务器。然后,我们创建一个输出流对象,并通过Socket对象的getOutputStream()方法获取该对象。接下来,我们使用输出流对象的write()方法发送消息。最后,我们调用flush()方法确保所有消息都被发送,然后关闭输出流和Socket对象。 请注意,上述示例中的服务器地址和端口号应该根据实际情况进行替换。此外,为了实现完整的IM功能,还需要实现接收消息和与其他客户端进行交互的代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值