IM 顶层设计

文章目录

  • [IM 顶层设计解析](#IM 顶层设计解析)
    • [1. 完善IM系统需要考虑的方面](#1. 完善IM系统需要考虑的方面)
    • [2. 消息架构组件定义](#2. 消息架构组件定义)
    • [3. 消息交互大致流程 (推拉结合设计)](#3. 消息交互大致流程 (推拉结合设计))
    • [4. 集群推送方案设计](#4. 集群推送方案设计)
      • [4.1 WebSocket 与 HTTP 区别](#4.1 WebSocket 与 HTTP 区别)
      • [4.2 单机连接管理(失效方案)](#4.2 单机连接管理(失效方案))
      • [4.3 方案一:Redis 存储 Channel (不可行)](#4.3 方案一:Redis 存储 Channel (不可行))
      • [4.4 方案二:精准投递消息 (Precise Message Delivery)](#4.4 方案二:精准投递消息 (Precise Message Delivery))
      • [4.5 方案三:分层路由 (解决连接爆炸)](#4.5 方案三:分层路由 (解决连接爆炸))
    • [5. 消息时序性与唯一性](#5. 消息时序性与唯一性)
    • [6. 推送策略总结](#6. 推送策略总结)
    • [7. 百万直播间推送方案 (广播推送)](#7. 百万直播间推送方案 (广播推送))
      • [7.1 为什么需要广播推送?](#7.1 为什么需要广播推送?)
      • [7.2 广播推送的实现](#7.2 广播推送的实现)
      • [7.3 几个功能的细节:](#7.3 几个功能的细节:)
      • [7.4 RocketMQ Tag 过滤 (实现指定推送)](#7.4 RocketMQ Tag 过滤 (实现指定推送))
    • [8. 消息可靠性保证](#8. 消息可靠性保证)
      • [8.1 消息发送的可靠性 (客户端 -> 服务端)](#8.1 消息发送的可靠性 (客户端 -> 服务端))
      • [8.2 消息推送的可靠性 (服务端 -> 客户端)](#8.2 消息推送的可靠性 (服务端 -> 客户端))
  • 表结构设计
    • 核心表关系
      • 关系图结构化说明
      • [1. 房间表(room)](#1. 房间表(room))
      • [2. 单聊扩展表(single_chat)](#2. 单聊扩展表(single_chat))
      • [3. 群聊扩展表(group_chat)](#3. 群聊扩展表(group_chat))
      • [4. 群成员表(group_member)](#4. 群成员表(group_member))
      • [5. 消息表(message)](#5. 消息表(message))
      • [6. 会话表(contact,即优化后的用户收信箱)](#6. 会话表(contact,即优化后的用户收信箱))
      • [7. 热点群聊收信箱(hot_room_inbox,可选,独立存储热点群聊会话)](#7. 热点群聊收信箱(hot_room_inbox,可选,独立存储热点群聊会话))
      • 核心设计说明

IM 顶层设计解析

1. 完善IM系统需要考虑的方面

在设计一个完善的IM系统时,需要考虑以下关键方面:

以下是转换后的 Markdown 格式(按原结构整理,保持层级清晰):

  • 集群消息路由
    • 用户不在同一个websocket服务上,怎么通信
    • 点对点/广播
  • 消息时序性
    • 跟随时间戳/d
    • 全局时序
    • 会话时序
    • 设备端时序
  • 消息id生成
    • 唯一保证
    • 顺序保证
      • 单调递增
      • 趋势递增
  • 消息可靠ACK
    • 发送可靠/推送可靠
    • 在线推送可靠/离线推送可靠
  • 消息重复
    • 分布式特性:可靠就会重复,重试就会重复,重发就会重复等
    • 前端重复发
    • 后端重复推
  • 推拉结合
    • 推拉结合,平衡时效性和可靠性
  • 多端同步
  • 单聊群聊
    • 怎么兼容单聊和群聊的消息存储和推送
  • 消息已读未读
    • 已读未读,同时关联到用户读消息的ack设计,如果是全量写,每一条消息又是指数上升
  • 热点扩散风暴
    • 全员消息一条,一条消息写入所有成员的信箱,就是造成的扩散系数增长
  • 表结构设计
    • 多类型消息表结构设计
    • 用户收信箱表结构设计
    • 已读未读表结构设计
    • 单聊群聊表结构设计

要不要我帮你整理一份IM系统设计各模块的核心方案总结?方便你快速了解每个模块的解决思路。

2. 消息架构组件定义

以下是 MallChat 架构中考虑的几个关键服务组件及其职责:

  • WebSocket 服务 (有状态):维护和用户的连接通道,可以接收消息,也可以推送消息。
  • IM 服务 (无状态):负责消息的发送逻辑,处理单聊/群聊的消息。
  • Logic 服务:处理用户的心跳、上下线、联系人、加好友、创群组等逻辑。
  • Auth 服务:处理用户认证、权限等需求。
  • Router:推送消息时,负责确保消息被正确、可靠地推送到用户所在的 WebSocket 服务上。

3. 消息交互大致流程 (推拉结合设计)

以下是一个群消息发送到推送的流程:

  1. 连接建立:用户A和 WebSocket 服务建立连接。之后都通过该连接发送消息,接受消息。
  2. 消息发送:用户A发送群消息,WebSocket 服务将消息通过 Dubbo 转发给 IM 服务 (无状态,通过负载均衡随机发送)。
  3. 持久化与投递 :IM 服务将消息持久化,然后将消息投递到 消息队列 MQ。MQ 快速响应前端,消费者根据负载进行后续的推送、写扩散等操作。
  4. 写扩散/热点信箱
    • 热点群聊 :只写入 热点信箱
    • 单聊/普通群聊:写扩散到每个群成员信箱 (如写入B和C的信箱)。
  5. 消息推送 :将消息投递信箱后,进行推送。
    • 在线:进行 WebSocket 推送。
    • 离线:进行 push 通知。
    • 路由 :由于用户连接在不同的 WebSocket 上,需要 Router 服务 推送到 B 和 C 所在的不同 WebSocket。
  6. 可靠性保证:推送时需确保消息可靠性,可能需要做应用层的 ACK,类似 TCP 的滑动窗口确认。
  7. 会话查询 :用户查询会话列表时,需要一个 聚合层 聚合用户信箱和热点信箱,并严格排序后返回给用户(即 推拉结合)。

4. 集群推送方案设计

4.1 WebSocket 与 HTTP 区别

特性 HTTP WebSocket
状态 无状态 有状态
连接 每次请求重新握手建立 TCP 连接 建立连接后,一直复用该连接
负载均衡 可随意进行负载均衡 收发信息在同一个机器,重连后才更换连接

4.2 单机连接管理(失效方案)

MallChat 使用 Netty 实现 WebSocket,连接即为一个 Channel。在单机环境下,登录时将 UIDChannel 关联缓存在 JVM 的 Map 中,推送时通过 UID 取出 Channel 进行推送。

问题:在集群场景下,要推送的用户连接在别的机器上,IM服务不知道对应的 WebSocket 机器在哪。连接管理在 JVM 层面,导致 Router 无法定位。

4.3 方案一:Redis 存储 Channel (不可行)

设想 :使用中心化的 Redis 存储 UIDChannel 的映射关系。
问题Channel 是本地的 Socket 连接,无法进行存储和反序列化。

4.4 方案二:精准投递消息 (Precise Message Delivery)

核心思路 :本地依然维护 UIDChannel 的关系,而 Redis 维护用户的连接状态(即用户在哪台机器上连接,如 IP)

流程描述

  1. 消息发送 :A 通过其连接的 channel 发送消息给 WebSocket,该 WebSocket 通过 Dubbo 转发给 IM 服务。
  2. 消息持久化 :IM 服务持久化消息后,调用 Router,推送消息给 C。
  3. 路由定位Router 通过 Redis 查到 C 目前连接的 WebSocket IP,然后通过 TCP 连接对指定的 WebSocket 服务器进行消息推送。
  4. 本地投递 :目标 WebSocket 服务收到请求后,通过本地连接管理查出用户具体的 channel,然后进行推送。

方案问题

  1. Redis 更新频繁:需要频繁更新 Redis 维护用户和 WebSocket 服务的映射 (可复用上下线功能解决)。
  2. 连接数爆炸 :Router 必须连接所有的 WebSocket。如果用户体量大,WebSocket 需要水平扩容,Router 数量也会增加,导致 Router 与 WebSocket 之间的连接数爆炸(达到上千连接),占用 WebSocket 资源。

4.5 方案三:分层路由 (解决连接爆炸)

核心思路:将 Router 与 WebSocket 的连接分组管理,避免 Router 连接所有 WebSocket,在中间增加一层路由,并设定路由规则。

优缺点

  • 优点:有效减少连接数。
  • 缺点:增加消息的推送链路。
  • 适用场景 :适用于真的很大型的集群场景。

5. 消息时序性与唯一性

5.1 消息ID的重要性

消息ID需满足 唯一有序(递增) 两个需求。

5.2 有序性保证

有序性类型 描述 实现挑战
全局递增 消息在整个IM系统都是唯一且递增。 分库分表后,分布式ID通常保证 趋势递增 ,而非 单调递增。实现单调递增存在单点竞争问题。
会话递增 保证同一个会话内的消息递增。 -
收信箱递增 适用于写扩散场景,每个人都有自己的收信箱,维护自己的时间线。 -

方案

  • MallChat 方案:MallChat 使用表ID自增。
  • 行业方案:微信是典型的写扩散场景,群聊人数上限为 500 人,其序列号生成器方案可作为参考。

https://cloud.tencent.com/document/product/269/2282

  • 单聊消息 MsgSeq 字段的作用及说明:该字段在发送消息时由用户自行指定,该值可以重复,非后台生成,非全局唯一。单聊消息历史记录对同一个会话的消息先以时间戳排序,同秒内的消息再以 MsgSeq 排序。

  • 与群聊消息的 MsgSeq 字段不同,群聊消息的 MsgSeq 由后台生成,每个群都维护一个 MsgSeq,从1开始严格递增。

总结一下消息顺序性的实现:目前最有效的方法就是,维护单独的消息序号,然后对于维护的序号,他一定要做到全局单调递增。这是很困难的,所以我们可以退而求其次,模仿微信的方式,做到群聊表内的单点递增,将消息id存在Redis中,做到唯一递增。

这里可能会遇到Redis突然挂了,所以要设计一个备用流程,提前预估好短时间内会进来多少条消息,例如1000条,在Redis挂的时候,本地对这些消息,从最新的消息id开始进行递增,直到Redis重新上线。

客户端排序

为了解决用户快速发两三条消息时间内的局部排序而已。可以参考腾讯sdk的实现。

给消息设置一个本地的自增id,发送消息的时候带上。排序整体以服务器的时间为准, 相同秒内的排序以自增id为准。

腾讯IM方案

单聊消息 MsgSeq字段的作用及说明:该字段在发送消息时由用户自行指定,该值可以重复,非后台生成,非全局唯一。

与群聊消息的MsgSeq字段不同,群聊消息的MsgSeq由后台生成,每个群都维护一个MsgSeq,从1开始严格递增。单聊消息历史记录对同一个会话的消息先以时间戳排序,同秒内的消息再以MsgSeq排序。

https://cloud.tencent.com/document/product/269/2282

这样赌的就是,你发的消息再快,哪怕存储顺序变了,但是也都在1s内,对于b来说,1s内的消息,按照自增id额外排序就好了。

这样发送者只需要保证指定时间内的消息自增就好。如果哪天seq丢失,或者在其他端发消息seq不一致,都没关系。

但是在群聊的场景下,每个群成员的客户端时间也是不一样的,没法作为排序的统一基准时间。只能采用服务端时间。并且seq也是不同的,相同秒内也没法排序,不适用该方案

服务端排序

对于群聊的场景,我们需要采用服务端排序。服务端排序其实本应该很简单。

对于单表来说,我们可以采用主键id来排序, 也可以通过消息的时间戳来排序。id肯定是严格自增了,时间戳要考虑精度问题, 一般设置的精度是毫秒, 也基本足够进行消息的排序了。因为毫秒内的消息,本身就没有上下文的关系, 对顺序要求不高。

除了消息的顺序性 外,还有一个很重要的点,就是消息的唯一性 。在游标翻页的场景下。翻页的游标字段,需要同时保证顺序性与唯一性的作用。如果单纯用时间戳,毫秒内的消息,就没办法区分与排序,得额外再拼接其他唯一字段一同排序,不如就直接用唯一且有序的id作为游标。

因此消息的时序性,我们通常都是用一个唯一id来保证。微信sdk也是用一个id来进行翻页。

6. 推送策略总结

  • 单聊消息多 :用 精准投递
  • 群聊消息多 :用 集群广播推送
  • MallChat 倾向 :MallChat 倾向于使用 集群广播推送 方案(因全员群发言频率高)。

万人群聊推送策略

对于万人群聊,一般系统的压力就在于消息的扇出(写扩散)。如果按照精准投递的话,我们的消息需要查询redis中心路由,然后将消息投递1w次。而如果用消息广播的形式消息只需要投递一次。由websocket自己进行广播消息的拉取与过滤。

  • 1.mq的消息消费模式为集群消费,确保每台websocket都能消消费到所有需要投递的消息。
  • 2.对比推送的uid在不在本地连接管理的列表,如果不在,直接丢弃消息,也叫过滤消息。
  • 3.如果在本地连接管理,根据uid取出channel,就可以进行消息推送了。

单聊消息多的,用精准投递。

群聊消息多的,用广播消息。

对于抹茶,我们是有个全员群聊,很多个小群聊,和很多的单聊。发言频率最多的场景是全员群。所以抹茶最应该使用集群广播推送的方案。

7. 百万直播间推送方案 (广播推送)

7.1 为什么需要广播推送?

  • 场景:直播间、全员群等高并发场景,需向海量用户推送同样的消息。
  • 问题 :如果采用 精准投递 方案,Router 需要连接所有 WebSocket 机器,且需要多次 RPC 推送消息,开销大。
  • 目标:没有消息的扇出(写扩散)的压力,只需要写一次。

假设在抖音直播的场景下,一个热门的直播间100w人同时在线,大量的礼物,互动消息充斥在直播间,如何通知到每个人。保证消息的即时性,可靠性

首先这明显更倾向于大群聊的一种场景,如果用精准投递,那么消息的扩散系数就是100w级。如果采用的是集群推送,假设100w的用户需要500台websocket进行连接,那么扩数系数只是500的级别。

但是这个假设是整个平台只有这个直播间,如果平台有更多的直播间。websocket会更多,mq的扩散系数也会更大。

每个方案又都优缺点,而应对极端场景,通常都是方案的组合,找场长避短。很类似于我们后面会提到的推拉结合。

这个场景的方案,我们可以设置一个热门阈值,比如1w。超过1w的直播间,我们会进行直播间升级,升级成热门直播间。热门直播间的websocket单独管理。把直播间用户的websocket连接都统一路由到固定的几百台websocket上。由于目标用户都集中了,也就不需要精准投递了,可以采用广播投递消息到这指定500台机器上。再对应的推送给直播间的观众。

这里其实是精准投递和集群推送的一个结合(你会发现,很多方案都是有优劣的,最后都是结合起来使用扬长避短)

这个方案的核心,就是要能将直播间所有用户通过网关路由到相同同的500台websocket上,有了这个基础,才能用广播消息,那500台websocket都监听同一个topic的广播mq消息。能省下很大的带宽开销。而消息的发送端,需要知道消息究竟是发送到热门直播间的topic进行集群广播 还是普通直播间的精准推送。还是得依赖router服务进行路由推送。

7.2 广播推送的实现

  1. MQ 实现广播 :使用 消息队列(MQ)的广播模式(如 RocketMQ 的 Topic 广播消费模式),将消息投递到 Topic,所有 WebSocket 服务都订阅该 Topic。
  2. WebSocket 广播:WebSocket 服务消费 MQ 消息后,对本地所有连接的 Channel 进行遍历推送。

7.3 几个功能的细节:

热门直播间升级

一开始的普通直播间,用户都分散在不同的websocket机器上。等到直播间人数突破阈值1w,就需要开始直播间的热点升级 。这时候服务器检测到直播间需要升级,动态扩缩容 ,启动一系列配套措施(k8s现在已经使用的比较多了)。一系列措施准备好后,相应的配置推送到网关路由机器上。指定以后该直播间的连接路由到我们新启动的50台websocket上。然后对当前在线的所有用户发送断连替换指令。所有在线用户都断开连接,重连的时候会被网关路由到新的websocket上。

优化:对于经常突破1w人的直播间,可以打个标。以后该直播间上线,默认就是热点,省略升级过程。

消息合并

直播间的点赞操作,一般发生在主播求赞的时候,大量人在同一时间段点赞。并且单人在同一时间也快速点赞。可以在客户端对每个人的多次点赞首先进行合并一次(用户a点赞20)。请求到后端后,由于路由已经做好,在每个点赞服务器,可以对多人的点赞再合并一次(用户a+b总点赞40),进行入库。给前端推送的时候,也可以合并推送,不需要每条点赞都推送。每隔1s推送一次直播间点赞总量达到(100w)。

不了解请求合并思想的,可查看《架构之路》的文章请求折叠工具类。

优先级隔离

在100w直播间里,推送的消息会有很多。会导致部分消息到达产生延迟。这就类似push系统,消息应该区分优先级,不要被互相影响。大礼物,和主播发言消息,这些应该独立在一个广播topic里,其他的不重要的消息,可以设置另一个topic,区分优先级,不要影响重要消息。

7.4 RocketMQ Tag 过滤 (实现指定推送)

  • 问题:如何实现给特定分组(如年龄、性别)推送?
  • 方案 :使用 MQ 的 Tag 过滤
    • 消息携带 Tag(如 age>=20)。
    • WebSocket 服务本地维护 用户 Tag 到 Channel 的映射。
    • WebSocket 服务接收到消息后,根据消息的 Tag 找到对应的 Channel 列表进行推送。

8. 消息可靠性保证

8.1 消息发送的可靠性 (客户端 -> 服务端)

  • 发送方 :发送消息后,需要等待接收方(服务端)的 ACK(确认) 消息。
  • 应用层 ACK:由于 WebSocket 底层是 TCP 协议,收发报文不关联,需要通过一个唯一标识,标识推出去的消息是用来响应上一个接收的消息的。发送方客户端也需要等待 ACK 的到来。

MallChat 方案:MallChat 使用 HTTP 来发送消息,通过返回的标识,判断是否发送成功即可。

  • 明确失败(ACK 失败):可能是业务校验问题,提示用户即可。
  • 超时:底层帮助自动重发,确保发送可靠。

8.2 消息推送的可靠性 (服务端 -> 客户端)

推送可靠性旨在保证服务端消息入库成功后,能够到达对应的消息接收方。

  • 持久化 :为了保证严格的可靠性,推送给每个人的 ACK 都需要入库,写到每个人的 消息表/信箱 持久化,接收到 ACK 后,修改消息状态。
  • 定时重试:如果信箱没有收到 ACK,说明消息没有到达接收端,需要进行重新推送。可靠的前提是信箱是持久化的,且支持定时任务不断重试。

根据文档内容,提取的 MySQL 表结构如下(含字段定义、注释及核心索引设计):

表结构设计

核心表关系

核心表共 7 张,分为「基础会话层」「消息层」「成员层」三类,通过 room_id(房间唯一标识)作为核心关联键,屏蔽单聊/群聊差异,实现统一会话管理。

关系图结构化说明

c 复制代码
erDiagram
    %% 1. 房间表(核心抽象层,屏蔽单聊/群聊差异,统一会话标识)
    ROOM {
        bigint(20) room_id PK "房间唯一标识(主键,全局唯一)"
        int(11) type "房间类型:1=单聊/2=群聊/3=热点群聊"
        tinyint(1) is_hot "是否为热点群聊:1=是/0=否(用于区分读写扩散策略)"
        json ext_info "扩展信息(群聊存头像/名称,单聊无额外信息)"
        bigint(20) last_msg_id "房间最新消息ID(用于会话排序与聚合)"
        datetime(3) create_time "房间创建时间(毫秒精度,确保时序性)"
        datetime(3) update_time "房间更新时间(热点状态/最新消息变更触发)"
    }
    note for ROOM "核心抽象表,解耦单聊/群聊逻辑,所有会话相关表通过room_id关联"

    %% 2. 单聊扩展表(与ROOM一对一,仅存单聊专属信息)
    SINGLE_CHAT {
        bigint(20) id PK "单聊扩展表主键"
        bigint(20) room_id FK "关联ROOM.room_id(一对一,绑定单聊对应的房间)"
        bigint(20) uid1 "单聊双方中UID较小者(用于生成唯一room_key)"
        bigint(20) uid2 "单聊双方中UID较大者"
        varchar(64) room_key UK "单聊房间唯一标识(格式:uid1_uid2,避免重复创建)"
        datetime(3) create_time "单聊房间创建时间"
    }
    note for SINGLE_CHAT "单聊专属扩展表,不存储消息核心逻辑,仅维护单聊双方关系"

    %% 3. 群聊扩展表(与ROOM一对一,仅存群聊专属信息)
    GROUP_CHAT {
        bigint(20) id PK "群聊扩展表主键"
        bigint(20) room_id FK "关联ROOM.room_id(一对一,绑定群聊对应的房间)"
        varchar(64) group_name "群聊名称"
        varchar(255) avatar "群聊头像URL(存储于OSS等对象存储)"
        bigint(20) owner_uid "群主UID(冗余字段,加速群主查询,避免关联群成员表)"
        datetime(3) create_time "群聊创建时间"
        datetime(3) update_time "群聊信息更新时间(名称/头像变更触发)"
    }
    note for GROUP_CHAT "群聊专属扩展表,维护群聊基础信息,群成员关系单独存储于GROUP_MEMBER"

    %% 4. 群成员表(与GROUP_CHAT一对多,管理群成员角色与关联)
    GROUP_MEMBER {
        bigint(20) id PK "群成员表主键"
        bigint(20) group_id FK "关联GROUP_CHAT.id(即群聊对应的扩展表记录,绑定群聊)"
        bigint(20) uid "群成员UID(关联用户表,文档未明确定义用户表,此处为隐含关联)"
        tinyint(1) role "成员角色:0=普通成员/1=管理员/2=群主(用于权限控制)"
        datetime(3) join_time "成员加入群聊的时间"
    }
    note for GROUP_MEMBER "群成员关系表,支持多角色管理,通过联合索引优化群主/管理员查询"
    note over GROUP_MEMBER "联合唯一索引:(group_id, uid) 避免重复入群;联合索引:(group_id, role) 快速筛选群主/管理员"

    %% 5. 消息表(与ROOM一对多,存储所有会话的消息内容,兼容多类型消息)
    MESSAGE {
        bigint(20) id PK "消息唯一ID(全局单调递增,用于时序性排序与游标翻页)"
        bigint(20) room_id FK "关联ROOM.room_id(绑定消息所属的会话房间)"
        bigint(20) from_uid "消息发送者UID(关联用户表,隐含)"
        int(11) type "消息类型:1=文本/2=图片/3=文件/4=语音(用于客户端解析展示)"
        varchar(5000) content "文本消息内容(非文本类型可空,详情存于extra)"
        json extra "扩展信息(图片/文件URL、语音时长等非文本消息详情)"
        bigint(20) reply_msg_id "回复的目标消息ID(可选,用于消息回复功能)"
        int(11) status "消息状态:0=正常/1=撤回/2=删除(用于消息生命周期管理)"
        datetime(3) create_time "消息发送时间(毫秒精度,辅助时序排序)"
        datetime(3) update_time "消息状态更新时间(撤回/删除操作触发)"
    }
    note for MESSAGE "核心消息存储表,通过type+extra字段兼容多类型消息,无需拆分表结构"
    note over MESSAGE "联合索引:(room_id, create_time) 优化会话内消息按时间查询性能"

    %% 6. 会话表(用户收信箱,与ROOM一对多,记录用户-会话交互状态,替代传统收信箱)
    CONTACT {
        bigint(20) id PK "会话表主键"
        bigint(20) uid "收信人UID(关联用户表,隐含,标识会话所属用户)"
        bigint(20) room_id FK "关联ROOM.room_id(绑定用户参与的会话房间)"
        bigint(20) last_msg_id "该会话的最新消息ID(关联MESSAGE.id,用于会话列表排序)"
        datetime(3) read_time "用户最后阅读时间(用于已读未读统计,替代每条消息的ack记录)"
        datetime(3) active_time "会话活跃时间(用于会话列表倒序排序,新消息触发更新)"
        datetime(3) create_time "会话创建时间(用户首次加入房间/单聊初始化触发)"
        datetime(3) update_time "会话状态更新时间(阅读/新消息/房间信息变更触发)"
    }
    note for CONTACT "优化后的用户收信箱,通过read_time记录阅读时间线,避免写扩散导致的存储爆炸"
    note over CONTACT "联合唯一索引:(uid, room_id) 避免同一用户重复创建同一会话;联合索引:(uid, active_time) 优化会话列表排序;联合索引:(room_id, read_time) 加速已读未读统计"

    %% 7. 热点群聊信箱(可选,独立存储热点群聊会话,减少写扩散开销)
    HOT_ROOM_INBOX {
        bigint(20) id PK "热点群聊信箱主键"
        bigint(20) room_id FK "关联ROOM.room_id(绑定热点群聊房间)"
        bigint(20) last_msg_id "热点群聊最新消息ID(关联MESSAGE.id,用于聚合查询)"
        datetime(3) update_time "最新消息更新时间(热点群聊新消息触发)"
    }
    note for HOT_ROOM_INBOX "热点群聊专属会话存储,避免写扩散到每个用户的CONTACT表,通过聚合层与普通会话合并"

    %% 表间关联关系(解耦设计,核心表独立,扩展表按需关联)
    ROOM ||--|| SINGLE_CHAT : "一对一(单聊扩展,不影响ROOM核心逻辑)"
    ROOM ||--|| GROUP_CHAT : "一对一(群聊扩展,不影响ROOM核心逻辑)"
    GROUP_CHAT ||--o{ GROUP_MEMBER : "一对多(一个群聊包含多个成员,成员仅属于一个群聊)"
    ROOM ||--o{ MESSAGE : "一对多(一个房间包含多条消息,消息仅属于一个房间)"
    ROOM ||--o{ CONTACT : "一对多(一个房间关联多个用户的会话,用户会话仅对应一个房间)"
    ROOM ||--o{ HOT_ROOM_INBOX : "一对多(一个热点房间对应一条热点会话记录,仅热点群聊使用)"
    MESSAGE ||--o{ CONTACT : "间接关联(通过last_msg_id同步会话最新消息,无强耦合)"

1. 房间表(room)

字段名 类型 说明
room_id bigint(20) 房间唯一标识(主键),屏蔽单聊/群聊差异,作为会话唯一标识
type int(11) 房间类型(单聊/群聊/热点群聊)
ext_info varchar/JSON 扩展信息(如群聊头像、群名称;单聊无额外信息)
is_hot tinyint(1) 是否为热点群聊(1=是,0=否)
last_msg_id bigint(20) 房间最新消息 ID(用于排序和聚合)
create_time datetime(3) 房间创建时间
update_time datetime(3) 房间更新时间(如热点状态变更、最新消息更新)
索引 - 联合索引:type + is_hot(快速筛选热点群聊);room_id(主键索引)

2. 单聊扩展表(single_chat)

字段名 类型 说明
id bigint(20) 主键
room_id bigint(20) 关联房间表的 room_id(唯一关联,一对一)
uid1 bigint(20) 单聊双方中 UID 较小的一方(用于生成唯一房间标识)
uid2 bigint(20) 单聊双方中 UID 较大的一方
room_key varchar(64) 唯一标识(格式:uid1_uid2,避免重复创建房间)
create_time datetime(3) 单聊房间创建时间
索引 - 联合唯一索引:room_key;关联索引:room_id

3. 群聊扩展表(group_chat)

字段名 类型 说明
id bigint(20) 主键
room_id bigint(20) 关联房间表的 room_id(唯一关联,一对一)
group_name varchar(64) 群名称
avatar varchar(255) 群头像 URL
owner_uid bigint(20) 群主 UID(冗余字段,加速查询)
create_time datetime(3) 群创建时间
update_time datetime(3) 群信息更新时间
索引 - 关联索引:room_id;普通索引:owner_uid

4. 群成员表(group_member)

字段名 类型 说明
id bigint(20) 主键
group_id bigint(20) 关联群聊扩展表的 id(即群聊对应的 room_id 关联)
uid bigint(20) 群成员 UID
role tinyint(1) 成员角色(0=普通成员,1=管理员,2=群主)
join_time datetime(3) 加入群聊时间
索引 - 联合索引:group_id + role(快速筛选群主/管理员);联合唯一索引:group_id + uid(避免重复加入)

5. 消息表(message)

字段名 类型 说明
id bigint(20) 消息唯一标识(主键,全局单调递增,用于时序性和游标翻页)
room_id bigint(20) 消息所属房间 ID
from_uid bigint(20) 发送者 UID
type int(11) 消息类型(文本/图片/文件/视频/语音等)
content varchar(5000) 文本消息内容(非文本类型可存空,详情放 extra)
extra JSON 扩展信息(如图片/文件的 OSS URL、语音时长等)
reply_msg_id bigint(20) 回复的消息 ID(文本消息专用,可冗余到 extra)
gap_count int(11) 消息间隔计数(文本消息专用,可冗余到 extra)
status int(11) 消息状态(0=正常,1=撤回,2=删除)
create_time datetime(3) 消息发送时间(毫秒精度)
update_time datetime(3) 消息状态更新时间
索引 - 联合索引:room_id + create_time(会话消息查询);主键索引:id

6. 会话表(contact,即优化后的用户收信箱)

字段名 类型 说明
id bigint(20) 主键
uid bigint(20) 收信人 UID
room_id bigint(20) 关联房间表的 room_id(非热点群聊的会话关联)
last_msg_id bigint(20) 该房间最新消息 ID(用于排序)
read_time datetime(3) 最后阅读时间(用于已读未读统计)
active_time datetime(3) 会话活跃时间(用于会话列表排序)
create_time datetime(3) 会话创建时间(首次加入房间/单聊时生成)
update_time datetime(3) 会话更新时间(如最新消息接收、阅读状态变更)
索引 - 联合索引:uid + active_time(会话列表查询);联合索引:room_id + read_time(已读未读统计);联合唯一索引:uid + room_id(避免重复会话)

7. 热点群聊收信箱(hot_room_inbox,可选,独立存储热点群聊会话)

字段名 类型 说明
id bigint(20) 主键
uid bigint(20) 群成员 UID
room_id bigint(20) 热点群聊房间 ID
last_msg_id bigint(20) 热点群聊最新消息 ID(用于聚合排序)
read_time datetime(3) 最后阅读时间(用于已读未读统计)
active_time datetime(3) 会话活跃时间(用于聚合排序)
create_time datetime(3) 加入热点群聊时间
update_time datetime(3) 会话更新时间
索引 - 联合索引:uid + active_time(聚合查询);联合唯一索引:uid + room_id

核心设计说明

  1. 抽象房间表 :通过 room 表屏蔽单聊/群聊差异,single_chatgroup_chat 作为扩展表,降低表结构冗余。
  2. 会话表优化contact 表替代传统收信箱,仅记录用户-房间的阅读时间线,避免写扩散导致的存储爆炸。
  3. 热点群聊隔离 :热点群聊通过 room.is_hot 标识,可独立存储到 hot_room_inbox 或缓存到 Redis,减少写扩散开销。
  4. 索引设计:所有核心查询场景(会话列表、消息查询、已读未读统计)均设计联合索引,保证查询性能。
相关推荐
CesareCheung1 小时前
用python写一个websocket接口,并用jmeter压测websocket接口
python·websocket·jmeter
500842 小时前
鸿蒙 Flutter 权限管理进阶:动态权限、权限组、兼容处理与用户引导
flutter·华为·架构·wpf·开源鸿蒙
真上帝的左手2 小时前
4. 关系型数据库-MySQL-架构
数据库·mysql·架构
weixin_472183542 小时前
微信小程序使用websocket
websocket·微信小程序·小程序
猫猫的小茶馆2 小时前
【ARM】BootLoader(Uboot)介绍
linux·汇编·arm开发·单片机·嵌入式硬件·mcu·架构
Stirner2 小时前
React 史诗级漏洞: SSR Server Action 协议导致服务器远程代码执行
react.js·架构·next.js
通信小呆呆2 小时前
面向万物互联的通信感知一体化用户端感知与云端通信人工智能体训练研究
人工智能·信息与通信·万物互联·通信感知一体化
是店小二呀3 小时前
本地 Websocket 调试总碰壁?cpolar一招让远程访问变简单
网络·websocket·网络协议
shaohaoyongchuang3 小时前
01-分布式基础-创建微服务项目
分布式·微服务·架构