IM系统海量消息数据是怎么存储的?

Wesley13
• 阅读 1006

一、与消息相关的主要场景

1、存储和离线消息。

现在的IM系统,消息都要落地存储。这样如果接收消息的用户不在线,等他下次上线时,能获取到消息数据。

2、消息漫游

消息漫游包括主要两种场景,

(1)用户新安装IM软件,要能看到以前的聊天记录

(2)聊天软件有PC版和App版,在App上聊的天,打开PC版要能够看到

二、不同场景读取 消息关键点

1、拉取离线消息

每个用户打开App就需要拉取离线,网络中断重连后要拉取离线,收到消息序列号不连续也要拉取离线,拉取离线消息是一个高频操作 。离线消息包括单聊、群聊、控制类等消息,消息类型类型众多。因此离线消息需要以用户ID(多端情况下需要以端)为检索维度。说的直白一点,就是每个人(端)都需要一个收件箱,拉离线消息就是把个人(端)收件箱里的消息取到客户端。

2、消息漫游

消息漫游的典型使用场景是,打开某个会话(单聊、群聊、公众号),下拉界面,客户端向服务端请求这个会话的聊天数据。消息漫游需要以会话为检索维度。消息漫游拉取数据的频率相对较低。我们把这类获取消息的方式成为拉取历史消息。

三、存储消息关键点

1、离线消息

离线消息读取频繁(写也有一定压力),但是检索逻辑简单(参看《一个海量在线用户即时通讯系统(IM)的完整设计》拉取离线消息章节)。我们采用内存数据库(Redis)存储,主要结构使用SortedSet(可以有更高效的存储结构,但Redis不支持)。对于群消息,采用扩散写方式(一条群消息给每个群成员都写一份)。按照消息接受者ID水平分库。

2、历史消息

历史消息的访问频率低,但是每条消息都需要存储,我们采用关系型数据库(MySQL)存储,重点考虑写入效率。对于群消息,采用扩散读方式(每条群消息只写一条记录)。按照消息发送者ID(单聊),或群ID(群聊)进行水平分库。

四、消息存取方案

1、离线消息

存储离线消息。按照消息接收者ID(toID),取模Hash分库(也可以用一致性Hash)。每个用户创建一个SortedSet结构的Key,用于存储离线消息。离线消息按照时间先后顺序排列即可。SortedSet添加一个元素时间复杂度是O(log(N)),N 是有序集的基数,由于离线消息的msgid是有序的,所以实际插入时间复杂度很可能退化为O(1)。

IM系统海量消息数据是怎么存储的?

读取离线消息。离线消息读取策略参看《一个海量在线用户即时通讯系统(IM)的完整设计》拉取离线消息章节。理论上读取离线消息的时间复杂度为O(log(N)+M), N 为离线消息的条数, M 为一次读取消息的条数。实际上,由于离线消息从有序集的头部开始读取,实际时间复杂度比这个值低。

2、历史消息

历史消息分为两大类,单聊消息、群聊消息。

单聊消息按照发送者ID(fromId)水平(取模Hash)分库,存到一张数据表(例如叫msg_user_send)中。核心字段包括msgid(消息ID),fromId(发送者Id),toId(接收者Id),content(消息内容)。

拉取单聊历史消息时(假设拉取userId1跟userId2的聊天),分别读取两人给对方发送的消息(因为分库原因,两人发送的消息可能分布在不同数据库中),然后进行Merge。

群聊消息按照群ID(groupId)水平(取模Hash)分库,存到一张表中(例如叫:msg_group)。核心字段包括msgid(消息ID),fromId(发送者Id),groupId(群Id),content(消息内容)。

另外一张msg_group_user表记录用户加入群的时间,如下图。某个人(如张三)加入群的时间,相当于一个游标,群消息表中,这个游标之后的聊天消息,是这个人(张三)能够查看的数据(当然,也可以做查看加入群之前若干条消息)。

IM系统海量消息数据是怎么存储的?

拉取群历史消息,直接倒序读取这个群消息表数据即可。

由于MySQL和Redis都采用了水平分库,存储能力几乎可以线性扩展!是不是这样就足够了呢?答案是否定的,优化永远没有尽头。如果我在非洲某个国家登录系统,从北京的机房读取消息数据显然不太合适!如何让数据靠近用户,是一个更加有挑战的问题。

本文分享自微信公众号 - 普通程序员(farmerbrag)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的
1、引言好久没写技术文章了,今天这篇不是原理性文章,而是为大家分享一下由笔者主导开发实施的IM即时通讯聊天系统,针对大量离线消息(包括消息漫游)导致的用户体验问题的升级改造全过程。文章中,我将从如下几个方面进行介绍:1)这款IM产品的主要业务及特点;2)IM系统业务现状和痛点;3)升级改造之路;
Wesley13 Wesley13
2年前
IM开发干货分享:如何优雅的实现大量离线消息的可靠投递
1、点评IM聊天消息的可靠投递,是每个线上产品都要考虑的IM热点技术问题。IM聊天消息能保证可靠送达,对于用户来说,就好比把钱存在银行不怕被偷一样,是信任的问题。试想,如果用户能明显感知到聊天消息无法保证送达,谁还愿意来用你的APP?谁也不希望自已的话就像浮云一样随风飘逝。必竟用IM聊天,虽然很多时候是费话,但总有关键时刻存在——比如向
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
2年前
IM消息ID技术专题(六):深度解密滴滴的高性能ID生成器(Tinyid)
1、引言在中大型IM系统中,聊天消息的唯一ID生成策略是个很重要的技术点。不夸张的说,聊天消息ID贯穿了整个聊天生命周期的几乎每一个算法、逻辑和过程,ID生成策略的好坏有可能直接决定系统在某些技术点上的设计难易度。有中小型IM场景下,消息ID可以简单处理,反正只要唯一就行,而中大型场景下,因为要考虑到分布式的性能、一致性等,所以要考虑的问题
Wesley13 Wesley13
2年前
IM 消息服务架构
IM消息架构主要有1、消息redis缓存队列及用户信息memcache2、消息的数据落地(入库mysql)3、消息的发送4、离线消息服务5、过期消息服务消息redis缓存队列服务端落地队列1.客户端通过HTTPS
Stella981 Stella981
2年前
IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?
1、前言在IM这种讲究高并发、高消息吞吐的互联网场景下,MQ消息中间件是个很重要的基础设施,它在IM系统的服务端架构中担当消息中转、消息削峰、消息交换异步化等等角色,当然MQ消息中间件的作用远不止于此,它的价值不仅仅存在于技术上,更重要的是改变了以往同步处理消息的思路(比如进行IM消息历史存储时,传统的信息系统作法可能是收到一条消息就马上同步存
京东云开发者 京东云开发者
5个月前
线上SQL超时场景分析-MySQL超时之间隙锁 | 京东物流技术团队
前言之前遇到过一个由MySQL间隙锁引发线上sql执行超时的场景,记录一下。背景说明分布式事务消息表:业务上使用消息表的方式,依赖本地事务,实现了一套分布式事务方案消息表名:mqmessages数据量:3000多万索引:createtime和statuss
郑天寿 郑天寿
5个月前
消息丢失排查方法?
遇到丢消息问题,如果是单聊,群聊,聊天室,系统消息可以在开发者后台北极星自助查询一下消息是否发送成功。根据您实际发送的相关信息(发送者、接收者、时间、消息ID……)看是否可以查到消息如果消息查不到一般有几种可能:信息有误(获取token的用户id跟您系统中