0%

IM经验总结

IM系统经验总结

背景

IM系统伴随着下面几个问题:

  • 实时性:网络协议、带宽、机器的演变升级
  • 投递可靠性:这里是指消息能够在应用层被对等方接收
  • 一致性
  • 安全性

投递可靠性

消息为什么会丢

使用的TCP协议作为传输层的协议,它是可靠的全双工协议,仅仅只靠它就能保证对等方正常接收消息了吗?

先说结论:仅仅靠TCP的可靠性是不行的,原因主要是以下三点:

  1. 网络不是完美的
  2. 服务问题:应用出现故障导致收发失败
  3. 服务器出现故障

补充:send()成功只能表明上层应用成功将消息发送到发送缓冲区里,在连接断开的时候直接回收连接和释放缓冲区数据;对于接收方在未检测到TCP断开时,会正常处理缓冲区里的数,但存在断开时,上层应用未来得及正常从缓冲区里取出完整的数据,例如RST状态下。

消息的丢失情况,主要分以下几个维度:

  • 发送方发消息失败
  • 接收方收消息失败
  • 接收方发送ack成功,发送方接收来自接收方ack失败
  • 接收方发送ack失败

网络和服务问题

  • 情景描述:弱网的情况下,服务端与客户端之间的传输会出现问题

  • 解决:

    • 网络不佳或者请求服务器等待响应超时,通常会提示发送方失败,发送方再次调用接口发送。
    • 服务端需要根据针对请求的唯一 ID,供服务端去重使用

·

服务器出现故障

  • 情景描述1:网络层已经投递成功,但是接收方在处理过程中出现了失败导致没有看到消息,服务端并不知道对等方是否真正处理了消息

  • 解决:

    • IM服务器在推送消息时,用SID标识本次请求,并将该消息加入”待ACK消息列表”
    • 接收方在处理完消息后,返回ACK,服务端在收到后将“待ACK消息列表”删除掉
    • ack丢失:如果在一段时间没有收到接收方的ack
      • 可能的原因:1. 网络原因 2. 接收方自身崩溃导致处理失败
      • 服务端超时重传
      • 接收方去重:接收方可能收到了消息处理了,可能没有处理
  • 情景描述2:当消息写入db后,下一步便是准备去通知接收方,可能在代码走到socket推送或者已经写入到内核缓冲区时,服务器出现宕机或者断电。在服务器恢复正常后,客户端重连,但是服务端这期间的消息通知不会重发

  • 解决:

    • 当接收方重新上线时,本地存储的时间戳(版本号)返回给服务端,服务端将这个版本号之后的消息推送(返回)给接收方。

一致性

描述:消息的顺序,不影响对client的语义了解。使用类似全局的生成的有序唯一消息ID,使得消息在一定程度内保证了有了时序性,为什么是一定程度上呢,因为伴随了以下几个问题:

  1. 服务端是多线程接收消息,可能先发的消息比后发的消息要先生成msgId落入库中
  2. 服务端都是集群化部署,每个节点的性能上存在差异,可能在找到网关的长连接下推时存在早接收的消息晚下推了的情况

其他问题:需不需要保证全局唯一的msgId

整流

大部分情况下是允许小范围的消息乱序,人在看到或听到消息视图时,会进行自己的一定理解包容当时的聊天场景,但有时可能需要保证消息的绝对时序,操作要保证1->2->3的顺序执行,否则会出错。

  • 解决:
    • 针对需要保证绝对时序的消息,将这些消息定义一个公有的packageId,然后为包内的每条消息加上seqId
    • 服务端整流:在一定超时时间内将它们根据seqId进行排序,然后找到gateway顺序下推
    • 客户端整流:先发的消息后到达,根据收到的msgId进行排序

其他问题

Q:需不需要保证全局唯一的msgId?

A:维护一个公有全局的分布式唯一自增ID会存在性能上的问题,在IM情景单聊便维护在一个会话维度内的自增ID,群聊则维护一个在群里的分布式唯一自增ID

参考

RST