如何设计一个消息中心

article/2025/9/1 12:48:40

b57c2082e99e6df2ed7a08460579db07.jpeg

如今的内容型产品,不管提供的是什么类型的内容,在其主功能之外,不可避免的会有另一个十分重要的功能——消息中心。

f9639b445f167662e03c7f63170977d1.jpeg

而无论是信息流、论坛、信箱,还是私聊、群聊、通知,推拉模型是内容型(包括:社交型)产品架构的核心。做出正确选择的关键在于对产品形态和系统组件清晰的认识。

今天我们将重心放在消息中心上,聊一聊如何设计一个消息中心。

需求分析

消息中心通常会有两个功能(如下图所示):

  1. 用户通知(点赞、评论、关注、@等)

  2. 官方通知

76a4e0a2ba250956532fe61cedfab959.jpeg

接下来我们将会对这两类通知进行一个简单的抽象。

首先,可以确定的是,对于用户通知,每个用户都不一样(我的点赞列表和你的点赞列表肯定是不一样的),因此对于每个人我们都需要维护一个「收件箱」。

当 A 点赞了 B 的内容,后端系统在收到了这一个点赞消息后,会将点赞信息写入 B 的 「收件箱」,并标明这是 A 在 xxx 时点赞的 xxx 内容。这是一个系统将消息 推送 给 B 的过程。

而对于官方通知,每个人(几乎)都是一样的(用户有可能设置了屏蔽,系统也可能指定了发送人群),并且官方通知是由系统自然下发的,因此对于系统来说需要维护一个系统「发件箱」

发件箱维护了官方想给用户的通知,每次打开消息中心时,用户都会主动来系统「拉取」官方最新的消息,并和用户自己的「收件箱」里的官方通知进行比较,以确认是否已读该条通知。这是一个用户主动从系统「拉取」通知的过程。

推拉模型

其实到这里就已经点出了这两个场景背后的一套模型——推拉模型。而之所以在这两种场景选择不同的运行机制,其实背后牵扯到的是读写扩散的问题。

推模型

c009467c523c03f22fd7c2b06ad1277c.jpeg

先看推模型,对于任何一个内容创作者来说,最开心的事情莫过于打开软件会有一堆点赞/评论的小红点。对于大 V 来说,打开 App 查看点赞消息的频率根本比不过别人给你点赞的频率,这是一个很典型的读少写多的场景。每当有一个用户点赞该大 V 时,都会将索引信息(一般为内容 ID、类型、发表时间等索引数据)写到用户的收件箱中。

  • 优点:读很轻。仅需要读取消息列表即可。

  • 缺点:写很重。一旦用户的内容质量很高,可能会收到大量的点赞/评论,会有大量的写入操作。

拉模型

2587e9add39e4b394d0f5a5fd77e9330.jpeg

再看拉模型,以官方通知为例,一般官方通知是由运营人员发布的,一个月可能也不会有几条,但是每次用户进入 App 时都会看看是否有新的官方通知进来,这是一个很典型的读多写少的场景。

  • 优点:写很轻,节省空间。系统只需维护一个属于自己的消息列表即可。

  • 缺点:读很重,计算量大。假设可以发送官方通知的生产者较多(例如淘宝里的一系列官方业务),则每次都需要从这些消息生产者里拉取最新的内容。

流程设计

用户通知

对于用户通知,流程设计如下:

593e1b14de7b32b050171a229d386971.jpeg

对于该流程,有几点需要注意的:

异步发送

当用户出发了点赞/关注/评论行为时,被点赞/评论/关注的用户,其实不需要立即感知,因此也不需要立即将互动信息写入该用户的收件箱中,因此可以考虑以消息队列的方式通知出去,缓解系统压力。

缓存前置

写入消息时,如果直接写入用户收件箱,可能会导致用户在请求消息列表时,将请求全部打到 DB,造成系统故障,因此通常会在更新用户收件箱时双写用户缓存。

官方通知

648570d1feb0cac6b0a97cd80865d60c.jpeg

相较于用户通知,官方通知由于引入官方运营这一角色,操作上会稍微复杂一些(如上图所示),因此整个系统的设计也会稍微复杂一些。

官方运营发送通知到「发件箱」中,「发件箱」中保留所有在线的通知列表。用户查看通知列表时,从官方「发件箱」中获取到未读通知,从自己的「收件箱」中查询历史通知。即:

  1. 运营写发件箱

  2. 用户读发件箱

  3. 用户写收件箱

流程示意图如下

015600d9f59996f0862dd71523931722.jpeg

官方运营在运营后台进行通知的编辑和发布,发布的通知更新到数据库中进行持久化存储。(这里选择 mysql 数据库进行数据持久化,下一章节将会提到)

通知发生变更时,会发送通知变更消息。基于该消息更新单条通知的缓存,并更新官方发件箱列表(供前台查询)。

用户查看通知列表时,若为第一页,需要从官方发件箱队列查看是否有未读的通知。

若有未读通知,则和历史通知第一页合并,返回给用户。同时异步写入用户的收件箱中。

持久化方案

说完了核心的业务流程后,接下来要面临的问题就是,数据存在哪?

上文有提到会将官方通知的发件箱利用 mysql 持久化,因为官方通知的数量较少,且官方通知是一个拉模型,重读轻写,压力多半由缓存来扛,所以底层数据存储在 mysql 中并无大碍。

重难点主要在用户的「收件箱」

之前有提过,用户收件箱的逻辑是一个重写轻读的推模型,一旦大 V 的内容更新,他的收件箱可能在一瞬间涌入大量的写流量。另外,对于几个头部大 V 来说,收到几千万的点赞并不是什么难事,每一个点赞信息都要写入到该用户的收件箱中,这就要求了底层存储需要能支持海量数据。

基于以上情景,MySQL 可能并不是一个合适的持久化方案。此时,我们可以尝试使用 HBase。

40d7edd9cb7c0c9dc76bc08d2a4435b1.jpeg

MySQL 与 HBase

MySQL 和 HBase 是我们日常应用中常用的两个数据库,分别解决应用的在线事务问题和大数据场景的海量存储问题。

综合对比

MySQL:是常用的数据库,采用行存储模式,底层是 binlog,用来存储业务数据,数据存储量较小。

HBase:列式数据库,底层是 hdfs,可以存储海量的数据,主要用来存储海量的业务数据和日志数据。

从引擎结构看差异

5f0c98520a5f8e401132e2367d89b5e5.jpeg

HBase 和 MySQL 的核心差异在于底层的数据结构,HBase 使用 LSM(Log-Structure Merge)树,Innodb 使用 B+树。

LSM 树,即日志结构合并树(Log-Structured Merge-Tree)。其实它并不属于一个具体的数据结构,它更多是一种数据结构的设计思想。

e85e8600d9dc98f6f70840be7d8976cd.jpeg

它的核心思路其实非常简单,就是假定内存足够大,因此不需要每次有数据更新就必须将数据写入到磁盘中,而可以先将最新的数据驻留在内存中,等到积累到最后多之后,再使用归并排序的方式将内存内的数据合并追加到磁盘队尾 (因为所有待排序的树都是有序的,可以通过合并排序的方式快速合并到一起)。

LSM 具有批量特性,存储延迟。当写读比例很大的时候(写比读多),LSM 树相比于 B 树有更好的性能。因为随着 insert 操作,为了维护 B 树结构,节点分裂。读磁盘的随机读写概率会变大,性能会逐渐减弱。多次单页随机写,变成一次多页随机写,复用了磁盘寻道时间,极大提升效率。

因此,由引擎结构(B+Tree vs LSM Tree)看到的能力差异:

  1. MySQL:读写均衡、存在空间碎片

  2. HBase:侧重于写、存储紧凑无浪费、Io 放大、数据导入能力强

从架构对比看差异

相比 MySQL,HBase 的架构特点:

  1. 完全分布式(数据分片、故障自恢复)

  2. 底层使用 HDFS(存储计算分离)。

由架构看到的能力差异:

  1. MySQL:运维简单(组件少)、延时低(访问路径短)

  2. HBase:扩展性好、内置容错恢复与数据冗余

总结

本文我们讲述了如何从官方通知和用户通知两个方面切入,设计一个 App 的常见功能——消息中心。但该方案仍然有很多潜在的问题:如果官方通知的来源很多呢?如何解决写扩散带来的成本问题?这些都是值得探索的问题。

事实上,消息中心虽然是一个十分常见的功能,但背后涉及到的东西非常复杂,发布/订阅、推拉模型、读写扩散等问题都会影响到我们的架构设计。

架构设计的过程,就是取舍的过程,而如何取舍,则是一门学问。对于现在纷繁复杂的互联网业务,永远没有最好的架构,只有最适合的架构。

最后,我们抛个问题,朋友圈是推模型还是拉模型?


http://chatgpt.dhexx.cn/article/q2ZFUWNV.shtml

相关文章

聊聊消息中心的设计与实现逻辑

厌烦被消息打扰,又怕突然间的安静; 一、业务背景 微服务的架构体系中,会存在很多基础服务,提供一些大部分服务都可能需要的能力,比如文件管理、MQ队列、缓存机制、消息中心等等,这些服务需要提供各种可以复…

4. 消息中心的设计与实现

消息中心的设计与实现 一、引言 运用场景: 1、消息的主动提醒(客户端被动接收) 2、客户模块(及时通讯) 3、单一登录(一个账号只能在一个设备登录) 消息中心的实现方案: 1、客户端轮…

mysql格式化数字去掉千分位

前言 使用format格式化数字时,超过1000就会有千分位,但有时我们不希望有这个千分位,那怎么去掉呢? 1. select format(11111.123,2) 2. select convert(11111.123,decimal(12,2))

upper mysql_MySQL函数

MySQL函数 Lower 转换小写 upper 转换大写 substr 取子串(substr(被截取的字符串,起始下标,截取的长度)) length 取长度 trim 去空格 str_to_date 将字符串转换成日期 date_format 格式化日期 format 设置千分位 round 四舍五入 rand() 生成随机数 Ifnull 可以将null转换成一个…

MySql FORMAT 去掉千位分隔符,

加上墨西哥的地区参数即可 SELECT FORMAT(35555566.8, 2, es_MX) AS value; 结果

MySQL知识总结

目录 知识点条件查询排序常见单行处理函数(可嵌套)多行处理函数分组查询distinct去重 连接查询⭐⭐⭐⭐⭐内连接外连接多表连接(两张表以上) 子查询where子句中的子查询from 子句中的子查询select后面出现的子查询 union合并查询结…

MySQL知识点

总结汇总MySQL数据库面试题(2020最新版)_ThinkWon的博客-CSDN博客_mysql数据库面试题 1. 索引 (1)主键索引 唯一非空,属于聚簇索引 (2)唯一索引 unique 可为空(多个null也可) (3&…

mysql 处理金额_MYSQL处理金额相关函数

1.FORMAT()数字千分位分割 FORMAT(X,D) 1.X需要格式化的数字 2.D保留小数位数 例:SELECT FORMAT(12334555.213,2) 2.ABS() 求绝对值 ABS(X) SELECT ABS(-23); SELECT ABS(21-23); SELECT ABS(23); 3.四舍五入保留小数 ROUND(X,D) 1.X需要格式化的数字 2.D保留小数位数(不写时默…

C语言字符数组的输入和输出

字符数组的输入输出有两种方法&#xff1a; &#xff08;1&#xff09;逐个字符输入输出。用格式符“%c”输入或输出一个字符。例如 int main() {char c[6]; //定义一个字符串for (int i 0; i < 5; i){scanf("%c", &c[i]); //输入字符串}for (int i 0; …

C语言 | 字符数组

C语言字符数组的定义 字符数组是用来存放字符数据的数组&#xff0c;字符数组中的一个元素存放一个字符&#xff0c;定义字符数组的方法和定义数值型数组的方法类似。 //例子&#xff1a;char character[10];C语言字符数组的初始化 C语言对字符数组初始化&#xff0c;最容易理解…

C语言,字符数组与字符串

文章目录 字符数组基本介绍字符串注意事项字符串的访问和遍历字符串的表示形式用字符数组存放一个字符串&#xff1a; 用字符数组存放一个字符串,用字符指针指向一个字符串使用字符指针变量和字符数组两种方法表示字符串的讨论 字符串相关函数常用字符串函数一览字符串函数应用…

浅谈字符数组

文章目录 一、什么是字符数组二、字符数组的定义和赋值1. 先定义一个数组&#xff0c;再为挨个元素进行赋值2. 在定义的同时给该字符数组进行初始化2.1 错误的初始化方式2.2 正确的初始化方式 3. 特别注意&#xff01;&#xff01;&#xff01;3.1 字符数组和整型数组一样不支持…

字符数组

字符数组 用来存放字符数据的数组是字符数组。字符数组中的每一个元素存放一个字符&#xff0c;其定义和使用方法与其他类型的数据基本相似。 1.1字符数组的定义和使用 字符数组的定义与其他类型的数组类似&#xff0c;标准形式如下&#xff1a; Char 数组名 【常量表达式】 …

Stata:嵌套Logit模型(NestedLogit)

原文链接&#xff1a;https://www.lianxh.cn/news/d5e00bfb17a7c.html 致谢&#xff1a; 这篇推文的核心内容主要来自陈强老师编著的《高级计量经济学及 Stata 应用》一书&#xff0c;特此致谢。 1. 简介 此前&#xff0c;连享会发布了一系列离散选择模型相关的推文&#xff0…

MNL——多项Logit模型学习笔记(二)

本节将会通过案例举例&#xff0c;介绍Logit模型的建模思路和过程 内容为摘抄他人学习资料的个人学习笔记&#xff0c;如有侵权则删 1.正确打开/解读Logit模型系数的方式 本节的具体内容在笔记里不详细表示了&#xff0c;大家在软件里拟合Logit模型时&#xff0c;对于其中的参…

logit回归模型的参数估计过程_【DCM07】Random Parameter(随机参数)Logit模型及其Nlogit实现...

本文是离散选择模型系列的第7篇原创文章,将详细介绍随机参数Logit模型,并利用Nlogit软件进行实操演示。【关注本公众号,可以获取数据和代码】本文公式较多,建议电脑端享用。 目录 1、随机参数Logit模型介绍 2、数据描述 3、随机参数Logit模型的参数估计 1 随机参数Logit模型…

MNL——多项Logit模型学习笔记(三)二项Logit模型、Gumble分布以及Logistic分布

上一节最后一部分&#xff0c;介绍了Provit模型&#xff0c;从建模的角度来说&#xff0c;Probit模型假设随机项服从正态分布&#xff0c;这是具有一定的合理性的——也是其优点&#xff1b;但是Probit模型没有闭合解——每次算P(n)i 的值的时候都需要求积分&#xff0c;这就给…

模型与logit_互助问答第33期:条件logit模型相关问题

问题: 尊敬的老师,您好!我最近在做一个条件logit模型的实证研究,因为是非线性的二元响应模型,查了很久的文献和Stata资料也没找到检查这类模型异方差的方法以及处理异方差的办法,请您们帮忙分析下,谢谢!如果可以的话,希望您能给出Stata命令,再次感谢! 答案: 第一,…

logit模型应用实例_互助问答第240期:面板Logit模型

您好老师,我是暨南大学国际商务专业的一名应届毕业生,有一个问题思考了很久都没办法解决,所以想要向你们求助。具体情况如下:我的论文采用的是面板logit模型,在判断使用固定效应和随机效应的过程中,我分别用命令xtlogit y x1 x2 x3,fe和xtlogit y x1 x2 x3,re得到了固定…

Logit模型和Logistic模型

一、离散选择模型&#xff08;Discrete Choice Model, DCM&#xff09; 常见的DCM模型&#xff1a;二项Logit&#xff08;Binary Logit&#xff09;、多项Logit&#xff08;Multi-nominal Logit&#xff09;、广义Logit&#xff08;Generalized Logit&#xff09;、条件Logit&a…