第13章IM服务——13.10 本章小结:最终架构

13.10 本章小结:最终架构

在学习完本章全部内容后,这里我们对整个IM服务做一个总结。

在用户之间建立聊天关系的渠道是会话,它被用作消息收发的容器,消息能被发送、存储、接收的核心是我们介绍的那些模式,其中负责消息存储的模式有如下两种。

  • 读扩散模式:消息被发送到会话消息链,用户在读取新消息时需要拉取全部相关会话。此模式适合会话数较少的场景。
  • 写扩散模式:消息被分别发送到每个会话中用户的用户消息链。此模式更适合会话参与用户较少的场景,如单聊和小群。

负责消息收发的模式有如下三种。

  • 推模式:每条消息都经过长连接被直接推送给在线用户,消息的实时性强,但易丢失。
  • 拉模式:客户端主动轮询访问服务端获取新消息,消息不易丢失,但实时性一般。
  • 推拉结合模式:既将新消息推送给在线用户,客户端又周期性地拉取消息,消息的实时性强,也不易丢失。

本章设计了一个既支持读扩散模式,又支持写扩散模式消息存储的IM服务,且采用推拉结合模式收发消息。我们为数据库存储设计了多个数据表,它们分别负责如下事情。

  • 消息表:存储消息本身。
  • 会话表:存储会话元信息。
  • 用户会话链:存储会话与用户的关联关系,以及每个用户在会话中的配置、权限、最近已读消息等。
  • 会话消息链:服务于读扩散模式,存储会话中的消息ID列表。
  • 用户消息链:服务于写扩散模式,存储每个用户的消息ID列表。

保证消息的有序性是即时通信的基本诉求,我们为消息链引入了递增的消息Seq来反映消息顺序,客户端本地维护消息链的接收队列,并通过查看当前收到的消息Seq在队列中是否连续来判断是否有消息丢失情况发生。如果消息Seq连续,则可以将消息展示给用 户,否则补偿拉取空洞消息。

一条消息从发送到被用户接收经过了如下过程。

  1. 客户端在为会话与服务端建立的长连接上发出消息。
  2. IM API接收消息,为消息生成全局唯一的消息ID。如果消息来自新会话,则进一步为消息生成会话ID并在会话表中创建新会话。然后,将消息按照会话ID有序投递到conversation_im_topic 消息队列中。
  3. 会话消息服务消费conversation_im_topic中的最新消息,在消息表中插入新数据,并为消息产生递增的消息Seq后,将消息插入会话消息链中。
  4. 会话消息服务更新若干缓存数据。
    • 将消息缓存到Redis中。
    • 在会话表中更新会话的最新产生消息的时间,如果消息来自群聊,则将会话消息进一步缓存到Redis中。
    • 将消息插入会话消息链的Redis最近消息缓存中。
  5. 会话消息服务获取与会话相关的用户列表,然后为每个用户按照用户ID将消息顺序转发到user_im_topic消息队列中。
  6. 用户消息服务消费user_im_topic中的最新消息,为消息产生递增的消息Seq后,将消息插入用户消息链中。
  7. 用户消息服务将消息更新到用户消息链的Redis最近消息缓存中。
  8. 将消息交给推送系统,由它把消息推送给目标用户。
  9. 接收者的客户端收到消息,在本地检查消息的Seq值是否等于接收队列中最后一条连续消息的Seq+1值,如果是,则说明消息连续,客户端将消息展示给用户;否则,说明出现消息空洞,需要补偿拉取消息。
  10. 客户端从服务端拉取消息,IM服务先从Redis缓存中读取消息ID列表,如果未读取到,则从数据库的消息链中再次读取。
  11. 在读取到消息ID列表后,IM服务将消息数据打包返回给客户端。
  12. 客户端将拉取到的消息补充到接收队列中,将连续消息展示给用户。

最后,IM服务的完整架构如图13-13所示。

image-20250503231944775