【IM】如何保证消息有序性?
即时通讯系统引入保证消息有序的机制,务必会影响性能,增加开发复杂度。 引入 IM 大佬的一句话: 1. 理想状态 服务端接收到消息的顺序与用户点击发送消息的按钮顺序完全一致,用户接收到的消息的顺序也和发送时的顺序完全一致。 这是几乎不可能实现的!为什么? 1. 网络延迟 不可能按下发送消息的按钮后,消息就会马上到达服务器。每个用户的网速可能不一样。 OK,那我按发送时的时间戳来排序不就行了? 2. 时钟不一致 其实每个人本地的时钟不一定都是同一个,任何物理时钟都有漂移,任何时钟同步协议都受制于网络延迟。这导致我们对时间的测量永远存在一个不确定的误差区间,区间内的事件顺序无法判定。 既然如此,那我们退一步,一个即时通讯系统,以微信举例,是不是真的需要保证消息的全局有序? 答案是不用。我们只需要保证每个会话内的消息有序就行,也就是用户1和用户2的这个聊天会话中,我们看到的消息是一样的,保证两个人发送的消息在这个会话内有序! 再退一步,那我只保证每个人发送的消息有序行不行? 答案是不行!比如用户A发送了两条消息MSG1和MSG2,用户B发送了MSG3,如果在页面上显示了MSG3 -> MSG1 -> MSG2,这样也算是乱序。 2. 如何实现会话内有序? 路径1:客户端 -> 服务端 1. 建立 TCP 长连接 我们都知道,TCP 长连接保证消息可靠性、有序性。 但是,实际并不能只依靠 TCP 连接来保证消息的发送顺序!在弱网环境,比如火车、电梯,可能会快速断网、切网,原来的 TCP 连接断开,连上一条新的TCP 连接,我发送消息MSG1在旧连接上没发出去,消息MSG2 在新的TCP 连接上发送成功了,这就导致了消息乱序。 2. 客户端生成序列号 学习 TCP 保证消息有序的方案,发送消息前,客户端生成本地递增序列号localSeq,这个序列号在本地存储的,服务端也会记录上一条接收成功了localSeq,然后在服务端判断当前消息是不是有序的。 如果乱序,可以通知客户端重发消息。 也就是:客户端发消息 ->服务端接收并校验localSeq -> 服务端发送ack或要求重发 如果一来一回严重影响性能,可以参考使用批量ack等。 路径2:服务端处理入库 因为我们的目标是保证会话有序,所以消息到达服务端时的第一件事应该是分发消息,按照会话的 ID 进行分发,在我的项目中,私聊会话格式:private_{user_id1}_{user_id2},user_id1 小于 user_id2。 分发最简单的就是把相同会话的消息放到一个队列,按顺序处理。 这里可以引入消息队列这个中间件,像 Kafka 的 Partition 分区机制 和 RocketMQ Queue 机制。 ...