提问: 如何更加优雅地查询大量聊天数据

Viewed 151

背景

如题,最近在实践中需要存储大量聊天数据,但是在处理数据分页的时候遇到了一些阻力

环境与当前的逻辑

环境

部分技术选型如下:

版本
Java 21
SpringBoot 3.4.3
mybatis 3.0.4

使用MySQL 8.0.41为主数据库,Redis做缓存,java+服务端+redis统一封装进docker部署

插入/查询逻辑

要求是做群的聊天记录存储就可以了

当前的群号为一串数字如:87324986948

由于是大量聊天记录,于是分表,分为了N张表,表名为group_message_N

每个群的消息通过简单的取模平均分配在各个表中,部分代码如下:

public GroupMessage selectGroupMessageById(Long groupId, Long groupMessageId, Integer groupType) {
        String tableSuffix = String.valueOf(groupId % 64);
        return groupMessageMapper.selectMessageById(
                tableSuffix,
                groupType,
                groupMessageId,
                groupId
        );

    }

表的Column如下:

Key 类型 描述
message_id BIGINT 消息ID (出于历史原因,message_id是无序的)
group_id BIGINT 群组ID
group_type INT 群组类型
user_id BIGINT 用户ID(发送者ID)
type INT 消息类型
content JSON 消息内容
create_time BIGINT 发送时间

索引如下:
image.png

问题

后端返回数据肯定是要分页的

所有群挤在一起不能用primary key

没有针对某个群的自增message_id,message_id是无序的也不能用来做游标

于是现在使用的是时间游标分页,具体查询sql如下:

<select id="getMessageCountAfterDate" resultType="Long">
        <![CDATA[
        SELECT COUNT(*)
        FROM group_message_${tableSuffix} FORCE INDEX (idx_group_create_time)
        WHERE group_id = #{groupId} AND group_type = #{groupType} AND create_time >= #{startDate};
        ]]>
    </select>

在这种情况下,如何快速查询某页的消息

每页消息数pageSize页数page都是不固定的,第一页始终是最新的消息,所以也不能直接缓存每页的游标

如果直接使用SELECT LIMIT,在大量数据的情况下效率会很低

请问各位在不修改表的情况下有没有更优解?

1 Answers

我理解你的问题核心在于如何优雅的实现im历史消息查询?我觉得这个问题有几个需要明确的地方:

  1. 你要做的im面向谁?对于历史消息的查询需求怎么样?如果是qq微信这些toc im,那问题非常好解决:存储近几天热点数据就可以了,其他的数据可以直接不支持查询。消息存储在客户端本地,你只负责推消息就行了。
  2. 如果你要做的是针对企业级需要支持任意时间消息记录查询的系统的话,我理解也要分场景:对于用户访问较多的热点数据,比如近30天,可以直接存缓存里,zset/list 等,可以想一下怎么设计,如果是30天后的数据,就直接查db吧,缓存也没有性价比,用游标已经可以了。如果需要支持复杂一些的查询,就上 es 。

还有,不是很理解为什么消息id不是有序的...先不考虑查询可能带来的复杂性,插入db的时候可能也会有不小的性能损耗。然后建议可以再去看看正经 im ,比如飞书/qq之类的实现,有一些设计我理解还可以优化一些,比如单/混链等,不过我也没实际做过 im 的经验,这方面就不能提供太多的帮助了。

十分感谢,询问过后发现冷数据查询确实是一个“可以不用但不能没有”的少用功能,缓存热数据确实是更优解,消息id不是有序的我也很头疼,拿到文档之后一开始我以为消息id是某种类snowflake算法生成的唯一id,但是后面发现后面的消息id不一定就比前面的大....然后就这样了XD