消息已读未读的模型设计_阿里云技术专家分享:现代 IM 系统中消息推送和存储架构的实现...

前言

IM 全称是“Instant Messaging”,中文名是即时通讯。在这个高度信息化的移动互联网时代,生活中 IM 类产品已经成为必备品,比较有名的如钉钉、微信、QQ 等以 IM 为核心功能的产品。当然目前微信已经成长为一个生态型产品,但其核心功能还是 IM。还有一些非以 IM 系统为核心的应用,最典型的如一些在线游戏、社交应用,IM 也是其重要的功能模块。可以说,带有社交属性的应用,IM 功能一定是必不可少的。

IM 系统在互联网初期即存在,其基础技术架构在这十几年的发展中更新迭代多次,从早期的 CS、P2P 架构,到现在后台已经演变为一个复杂的分布式系统,涉及移动端、网络、安全和存储等技术的方方面面。其支撑的规模也从早期的少量日活,到现在微信这个巨头最新公布的达到 9 亿日活的体量。

IM 系统中最核心的部分是消息系统,消息系统中最核心的功能是消息的同步和存储:

  • 消息的同步:将消息完整、快速地从发送方传递到接收方,就是消息的同步。消息同步系统最重要的衡量指标就是消息传递的实时性、完整性以及能支撑的消息规模。从功能上来说,一般至少要支持在线和离线推送,高级的 IM 系统还支持“多端同步”。

  • 消息的存储:消息存储即消息的持久化保存,这里不是指消息在客户端本地的保存,而是指云端的保存,功能上对应的就是“消息漫游”。“消息漫游”的好处是可以实现账号在任意端登陆查看所有历史消息,这也是高级 IM 系统特有的功能之一。

本文内容主要涉及 IM 系统中的消息系统架构,会介绍一种基于 TableStore 构建的消息同步以及存储系统的架构实现,能够支持消息系统中的高级特性“多端同步”以及“消息漫游”。在性能和规模上,能够做到全量消息云端存储,百万 TPS 以及毫秒级延迟的消息同步能力。

架构设计

本章主要会介绍基于 TableStore 的现代 IM 消息系统的架构设计,在详细介绍架构设计之前,会先介绍一种 Timeline 逻辑模型,来抽象和简化对 IM 消息同步和存储模型的理解。理解了 Timeline 模型后,会介绍如何基于此模型对消息的同步以及存储进行建模。基于 Timeline 模型,在实现消息同步和存储时还会有各方面的技术权衡,例如如何对消息同步常见的读扩散和写扩散两种模型进行对比和选择,以及针对 Timeline 模型的特征如何来选择底层数据库。

传统架构 vs 现代架构

2f0d3b11220bb79e738ddfa53ad55d6d.png

上图是消息系统传统架构与现代架构的简单对比。

传统架构下,消息是先同步后存储。对于在线的用户,消息会直接实时同步到在线的接收方,消息同步成功后,并不会进行持久化。而对于离线的用户或者消息无法实时同步成功时,消息会持久化到离线库,当接收方重新连接后,会从离线库拉取所有未读消息。当离线库中的消息成功同步到接收方后,消息会从离线库中删除。传统的消息系统,服务端的主要工作是维护发送方和接收方的连接状态,并提供在线消息同步和离线消息缓存的能力,保证消息一定能够从发送方传递到接收方。服务端不会对消息进行持久化,所以也无法支持消息漫游。

现代架构下,消息是先存储后同步。先存储后同步的好处是,如果接收方确认接收到了消息,那这条消息一定是已经在云端保存了。并且消息会有两个库来保存,一个是消息存储库,用于全量保存所有会话的消息,主要用于支持消息漫游。另一个是消息同步库,主要用于接收方的多端同步。消息从发送方发出后,经过服务端转发,服务端会先将消息保存到消息存储库,后保存到消息同步库。完成消息的持久化保存后,对于在线的接收方,会直接选择在线推送。但在线推送并不是一个必须路径,只是一个更优的消息传递路径。对于在线推送失败或者离线的接收方,会有另外一个统一的消息同步方式。接收方会主动的向服务端拉取所有未同步消息,但接收方何时来同步以及会在哪些端来同步消息对服务端来说是未知的,所以要求服务端必须保存所有需要同步到接收方的消息,这是消息同步库的主要作用。对于新的同步设备,会有消息漫游的需求,这是消息存储库的主要作用,在消息存储库中,可以拉取任意会话的全量历史消息。

以上是传统架构和现代架构的一个简单的对比,现代架构上整个消息的同步和存储流程,并没有变复杂太多,但是其能实现多端同步以及消息漫游。现代架构中最核心的就是两个消息库“消息同步库”和“消息存储库”,是消息同步和存储最核心的基础。而本文接下来的部分,都是围绕这两个库的设计和实现来展开。

Timeline 模型

在分析“消息同步库”和“消息存储库”的设计和实现之前,在本章会先介绍一个逻辑模型——Timeline。Timeline 模型会帮助我们简化对消息同步和存储模型的理解,而消息库的设计和实现也是围绕 Timeline 的特性和需求来展开。

4507fa6d229cb5dd1dd7e7b149bc28d0.png

如图是 Timeline 模型的一个抽象表述,Timeline 可以简单理解为是一个消息队列,但这个消息队列有如下特性:

  • 每个消息拥有一个顺序 ID(SeqId),在队列后面的消息的 SeqId 一定比前面的消息的 SeqId 大,也就是保证 SeqId 一定是增长的,但是不要求严格递增。

  • 新的消息永远在尾部添加,保证新的消息的 SeqId 永远比已经存在队列中的消息都大。

  • 可根据 SeqId 随机定位到具体的某条消息进行读取,也可以任意读取某个给定范围内的所有消息。

有了这些特性后,消息的同步可以拿 Timeline 来很简单的实现。图中的例子中,消息发送方是 A,消息接收方是 B,同时 B 存在多个接收端,分别是 B1、B2 和 B3。A 向 B 发送消息,消息需要同步到 B 的多个端,待同步的消息通过一个 Timeline 来进行交换。A 向 B 发送的所有消息,都会保存在这个 Timeline 中,B 的每个接收端都是独立的从这个 Timeline 中拉取消息。每个接收端同步完毕后,都会在本地记录下最新同步到的消息的 SeqId,即最新的一个位点,作为下次消息同步的起始位点。服务端不会保存各个端的同步状态,各个端均可以在任意时间从任意点开始拉取消息。

消息漫游也是基于 Timeline,和消息同步唯一的区别是,消息漫游要求服务端能够对 Timeline 内的所有数据进行持久化。

基于 Timeline,从逻辑模型上能够很简单的理解在服务端如何去实现消息同步和存储,并支持多端同步和消息漫游这些高级功能。落地到实现的难点主要在如何将逻辑模型映射到物理模型,Timeline 的实现对数据库会有哪些要求?我们应该选择何种数据库去实现?这些是接下来会讨论到的问题。

消息存储模型

7ce97a90e027b4cdf052f43efcfa8cfb.png

如图是基于Timeline的消息存储模型,消息存储要求每个会话都对应一个独立的 Timeline。如图例子所示,A 与 B/C/D/E/F 均发生了会话,每个会话对应一个独立的 Timeline,每个 Timeline 内存有这个会话中的所有消息,服务端会对每个 Timeline 进行持久化。服务端能够对所有会话 Timeline 中的全量消息进行持久化,也就拥有了消息漫游的能力。

消息同步模型

消息同步模型会比消息存储模型稍复杂一些,消息的同步一般有读扩散和写扩散两种不同的方式,分别对应不同的 Timeline 物理模型。

d74cb4dc756cc8a08802144fbc22e62c.png

如图是读扩散和写扩散两种不同同步模式下对应的不同的 Timeline 模型,按图中的示例,A 作为消息接收者,其与 B/C/D/E/F 发生了会话,每个会话中的新的消息都需要同步到 A 的某个端,看下读扩散和写扩散两种模式下消息如何做同步。

  • 读扩散:消息存储模型中,每个会话的 Timeline 中保存了这个会话的全量消息。读扩散的消息同步模式下,每个会话中产生的新的消息,只需要写一次到其用于存储的 Timeline 中,接收端从这个 Timeline 中拉取新的消息。优点是消息只需要写一次,相比写扩散的模式,能够大大降低消息写入次数,特别是在群消息这种场景下。但其缺点也比较明显,接收端去同步消息的逻辑会相对复杂和低效。接收端需要对每个会话都拉取一次才能获取全部消息,读被大大的放大,并且会产生很多无效的读,因为并不是每个会话都会有新消息产生。

  • 写扩散:写扩散的消息同步模式,需要有一个额外的 Timeline 来专门用于消息同步,通常是每个接收端都会拥有一个独立的同步 Timeline,用于存放需要向这个接收端同步的所有消息。每个会话中的消息,会产生多次写,除了写入用于消息存储的会话 Timeline,还需要写入需要同步到的接收端的同步 Timeline。在个人与个人的会话中,消息会被额外写两次,除了写入这个会话的存储 Timeline,还需要写入参与这个会话的两个接收者的同步 Timeline。而在群这个场景下,写入会被更加的放大,如果这个群拥有 N 个参与者,那每条消息都需要额外的写 N 次。写扩散同步模式的优点是,在接收端消息同步逻辑会非常简单,只需要从其同步 Timeline 中读取一次即可,大大降低了消息同步所需的读的压力。其缺点就是消息写入会被放大,特别是针对群这种场景。

在 IM 这种应用场景下,通常会选择写扩散这种消息同步模式。IM 场景下,一条消息只会产生一次,但是会被读取多次,是典型的读多写少的场景,消息的读写比例大概是 10:1。若使用读扩散同步模式,整个系统的读写比例会被放大到 100:1。一个优化的好的系统,必须从设计上去平衡这种读写压力,避免读或写任意一维触碰到天花板。所以 IM 系统这类场景下,通常会应用写扩散这种同步模式,来平衡读和写,将 100:1 的读写比例平衡到 30:30。当然写扩散这种同步模式,还需要处理一些极端场景,例如万人大群。针对这种极端写扩散的场景,会退化到使用读扩散。一个简单的 IM 系统,通常会在产品层面限制这种大群的存在,而对于一个高级的 IM 系统,会采用读写扩散混合的同步模式,来满足这类产品的需求。

消息库设计

基于 Timeline 模型,以及 Timeline 模型在消息存储和消息同步的应用,我们看下消息同步库和消息存储库的设计。

183e118b8dd132eb3b9626fa49246b9e.png

如图是基于 Timeline 的消息库设计。

  • 消息同步库:消息同步库用于存储所有用于消息同步的 Timeline,每个 Timeline 对应一个接收端,主要用作写扩散模式的消息同步。这个库不需要永久保留所有需要同步的消息,因为消息在同步到所有端后其生命周期就可以结束,就可以被回收。但是如前面所介绍的,一个实现简单的多端同步消息系统,在服务端不会保存有所有端的同步状态,而是依赖端自己主动来做同步。所以服务端不知道消息何时可以回收,通常的做法是为这个库里的消息设定一个固定的生命周期,例如一周或者一个月,生命周期结束可被淘汰。

  • 消息存储库:消息存储库用于存储所有会话的 Timeline,每个 Timeline 包含了一个会话中的所有消息。这个库主要用于消息漫游时拉取某个会话的所有历史消息,也用于读扩散模式的消息同步。

消息同步库和消息存储库,对数据库有不同的要求,如何对数据库做选型,在下面会讨论。

数据库选型

消息系统最核心的两个库是消息同步库和消息存储库,两个库对数据库有不同的要求:

d6d17a5070887f98278ab90b9d1f0323.png

总结下来,对数据库的要求有如下几点:

1. 表结构设计能够满足 Timeline 模型的功能要求:不要求关系模型,能够实现队列模型,并能够支持生成自增的 SeqId。

2. 能够支持高并发写和范围读,规模在十万级 TPS。

3. 能够保存海量数据,百 TB 级。

4. 能够为数据定义生命周期。

阿里云表格存储(TableStore)是基于 LSM 存储引擎的分布式 NoSQL 数据库,支持百万 TPS 高并发读写,PB 级数据存储,数据支持 TTL,能够很好的满足以上需求,并且支持自增列,能够非常完美的设计和实现 Timeline 的物理模型。

架构实现

本章会以一段非常精简的代码,来展示如何基于 TableStore 实现 Timeline 模型,并基于 Timeline 模型进行消息存储和推送。本文中给出的代码,主要目的是为了演示如何能够实现一个精简 Timeline 的最基本功能。马上我们会推出一个完整的 Timeline Library,来将基于 Timeline 进行消息存储和推送的代码的开发变得无比简单。

所有示例代码基于如下 SDK 版本:

0baf24adb2148acd82c777c2812cb07b.png

表结构设计

e9b407a34604e9521f88803dfcd36aca.png

59f9951dc3f9888444aa4a751817e3cc.png

以上是创建 Timeline 表的示例代码,总共需要创建两张表,一张表作为消息同步库,名称为“PushTable”,另一张表作为消息存储库,名称为“StoreTable”。

推送和存储实现

2a2a7aedc916e7070909a96300357755.png

以上是模拟一个群内消息同步和存储的示例代码。群名称为『TableStore(钉钉号:11789671)』,群内成员有『A, B, C, D, E』。群内新的消息,需要先存储到群的存储 Timeline(Timeline ID 为群名称),之后需要以写扩散的模式推送到群内每个成员的同步 Timeline(以群成员名称作为 Timeline ID)。

76cc1d6ee15eecbd1042f51874a0e5e9.png
6dc41eb38a118e551fdf12ec1a25270b.png

以上是拉取群内历史消息以及某个群成员进行消息同步的示例代码,主要逻辑在 syncMessages 函数内。示例代码中,拉取消息都是从 seq_id 为 0 开始,0 为 TableStore 自增列中最小值,所以代表了从最小的一个位点开始拉取消息,即拉取全量消息。

后记

这篇文章主要介绍了现代IM系统中消息推送和存储架构的实现,基于逻辑的 Timeline 模型,我们可以很清晰明了的理解整个消息推送和存储的架构。基于 TableStore,可以非常简单的实现 Timeline 模型,其中自增列功能,完美的匹配了 Timeline 模型中所需要的最关键的 SeqId 自增。

TableStore(表格存储)是阿里云自主研发的专业级分布式 NoSQL 数据库,是基于共享存储的高性能、低成本、易扩展、全托管的半结构化数据存储平台,支撑互联网和物联网数据的高效计算与分析。IM 系统的消息推送和存储场景,是 TableStore 在社交领域的重要应用之一。

基于 Timeline 的消息存储和推送模型,将不光应用在 IM 消息系统中,还可应用在例如 Feeds 流、实时消息同步、直播弹幕等场景。

作者:木洛,阿里云高级技术专家
来源:https://yq.aliyun.com/articles/253242?utm_content=m_35131

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

d6efc891ae02bc8e6f58546560daba6c.gif

看到这里,说明你喜欢本文

你的转发好看的人都点了好看↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/559575.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

移动端html5广告的优势,h5手机端开发的优势都有哪些呢

原标题:h5手机端开发的优势都有哪些呢现在是手机不离手的时代,可以说每个人都有一部甚至两部手机来打发日常的空余时间,那么你知道h5手机端开发的优势都有哪些吗?下面原创先锋小编给大家详细介绍下,想要了解的朋友一起来看看吧。…

园林景观cad_自学CAD太难?送你550张练习图纸,七天时间小白蜕变成大神

自学CAD太难?送你550张练习图纸,七天时间小白蜕变成大神俗话说:实践是检验真理的唯一标准。对于想要熟练CAD的朋友来说,最重要的就是练习!大量的练习!CAD画图是个熟能生巧的事情,练多了&#xf…

面条html5,使用 babel 全家桶模块化古老的面条代码

在最近的工作中,接手了一个古老的项目,其中的 JS 代码是一整坨的面条代码,约 3000 行的代码全写在一个文件里,维护起来着实让人头疼。想不通为啥之前维护项目的同学能够忍受这么难以维护的代码……既然现在这个锅被我拿下了&#…

cad批量打印快捷键_批量打印CAD图(无删减版)

前面两期小编出的PDF教程想必用了的人都觉得还不错吧?(此处应有掌声)上一期提到的CAD批量打印今天放出来了,擦亮眼睛往下看很多时候大批量的一堆图纸要输出,比如下面这个当然这批图纸并不多,也只是局部的,通常一个项目…

docker 容器之间通信_四、Docker 网络原理、分类及容器互联配置

本文是《Docker必知必会系列》第四篇,原文发布于个人博客:悟尘纪。上一篇:Docker必知必会系列(三):基于 Docker-registry/Nexus3 搭建本地仓库Docker 网络配置Docker 网络基本原理要实现网络通信&#xff0…

键盘与鼠标器是微型计算机上最常用的,2016年职称计算机考试WindowsXP考前预测试题5...

填空题1.3.5英寸磁盘的滑块小孔打开时,该盘只能(读),不能(写),称为(写保护)。2.软盘上的HD标记表示(双面高密度)。3.常用的双面高密度3.5英寸盘的容量为(1.44MB)。4.硬盘与软盘相比,具有(容量大)、(价格低)的特点。5.常见的光盘驱…

2020idea插件怎么同步_没有用过这些插件,别说你在用vscode

vscode 插件Rainbow Brackets编码过程中,尤其在我们使用js进行函数式编程时,代码里会有很多的花括号,想要保证它们对称十分困难,所以就出现了上面小粉同学的尴尬局面,相信很多人都遇到过类似的情况。Rainbow Brackets&…

python 删除特定行数据_怎么用 Python 做数据分析实例

01 生成数据表第一部分是生成数据表,常见的生成方法有两种,第一种是导入外部数据,第二种是直接写入数据。 Excel 中的文件菜单中提供了获取外部数据的功能,支持数据库和文本文件和页面的多种数据源导入。获取外部数据python 支持从…

html 地址 点击召唤高德,高德地图api 点聚合+海量点+点击事件(根据地区或坐标进行定位)...

javascript区划聚合海量点展现html,body,#container {width: 100%;height: 100%;margin: 0px;}#loadingTip {position: absolute;z-index: 9999;top: 0;left: 0;padding: 3px 10px;background: red;color: #fff;font-size: 14px;}#right {position: absolute;z-index: 9999;top…

python中集合运算_入门 | 一文带你了解Python集合与基本的集合运算

原标题:入门 | 一文带你了解Python集合与基本的集合运算 选自DataCamp 作者:Michael Galarnyk 参与:Geek Ai、思源 一般我们熟悉 Python 中列表、元组及字典等数据结构,但集合可能用得稍微少一点。但集合独特的元素唯一性与 O(1) …

python中文本文件r_Python如何读写文本文件

展开全部 1.open使用open打开文件后一定要记2113得调5261用4102文件对象的close()方法。比如可以用try/finally语句来确保最后1653能关闭文件。 file_object open(thefile.txt) try: all_the_text file_object.read( ) finally: file_object.close( ) 注:不能把op…

台式计算机性能清单是强制的吗,教你识别良心商家和奸商电脑配置清单区别以及如何选购台式电脑机箱...

我们想要组装一台电脑,由于隔行如隔山,无疑对硬件品牌型号都不太了解,会将自己的预算和大致的要求和商家说,而商家会根据预算与要求写具体的电脑配置清单,不同商家写出来的配置或多或少存在不同,含糊不清写…

mysql显示表已存在_MySQL数据库与数据表的相关操作

数据库相关操作:显示数据库:show databases;如果是0.00秒并不代表没有花费时间,而是时间非常短,小于0.01秒。创建数据库:Query OK表示创建成功,1行受到影响,处理时间为0、05秒。使用下面的命令查…

怎么用计算机算成250,万能计算器

彩票彩宝贝体彩排列五March 29, 2016彩票计划网站导航Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ultrices egestas nunc, quis venenatis orci tincidunt id. Fusce commodo blandit eleifend. Nullam viverra tincidunt dolor, at pulvinar dui.…

android怎样判断插入数据是否成功_MySQL一个表的自增id用完了,背井大佬让我用这些姿势再往里插数据...

点击上方"码之初"关注,选择"设为星标"与精品技术文章不期而遇在之前有篇文章中,和大家探讨了在MySOL数据库中,一个表的自增id用完,再插入数据有什么问题?评论处 背井 公众号的大佬建议我另开一篇再…

计算机硬件系统教具,计算机硬件系统 (2)

计算机硬件系统 (2) (3页)本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦!9.9 积分计算机硬件系统克井一中杨致远教学目标:1、了解计算机的发展概况、特点以及种类2、理解计算机的工作原理3…

经典计算机实现量子逻辑门,量子计算机:对量子逻辑门的探讨

在分析了经典比特和量子比特的异同点之后,阐述了量子逻辑门的特点;然后具体介绍了几种常见的量子逻辑门:基本量子逻辑门,量子异或门,量子与门。最后又给出了更复杂的量子逻辑门的构建方法。维普资讯 http://doc.wendoc.com信息科学}J宋纳红侯丽敏科量子计算机&#…

命名空间中不存在名称_原木定制中不开裂的木材真的存在吗?

广大的读者朋友们大家好,之前壹信缅甸柚木高端全屋定制小编和大家讲解了为什么那么多人喜欢原木实木全屋定制护墙板,本文壹信小编将给大家讲讲原木整装中不开裂的木材真的存在吗。原木整装行业的从业人员都知道,最麻烦最让人担心的是木头的开…

go 字符串替换_Go语言爱好者周刊:第 64 期 — goup 这个工具了解下

这里记录每周值得分享的 Go 语言相关内容,周日发布。本周刊开源(GitHub:polaris1119/golangweekly),欢迎投稿,推荐或自荐文章/软件/资源等,请提交 issue 。鉴于大部分人可能没法坚持把英文文章看…

opencore0.6.3_Ubuntu 18.04 源码编译安装 PHP 7.3

记录在Ubuntu 18.04下源码编译安装 PHP 7.3的过程步骤。0.下载PHP源代码首先需要从PHP官网下载PHP7.3.1的源代码,保存为php-7.3.1.tar.xz。http://cn2.php.net/distributions/php-7.3.1.tar.xz在上述文件保存的目录中打开终端,使用命令将其解压&#xff…