消息系统设计与实现
文/JC_Huang(简书作者)
原文链接:http://www.jianshu.com/p/f4d7827821f1
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
产品分析
首先我们来看一下市场上关于消息的实现是怎么样的。
简书
简书的消息系统主要分了两种
- 简信
- 提醒
简信
简信的性质其实跟私信是一样的,是用户发送给用户的一则消息,有具体的信息内容。

提醒
而提醒,则是系统发送的一则消息,其文案格式是固定的,并且对特殊对象一般拥有超链接。

知乎
知乎跟简书一样,主要分了两种:
- 私信
- 消息
私信
跟简书一样,使用户发送给用户的一则消息,也可以是管理员发送给用户的消息。

消息
知乎的消息比简书的提醒有过之而无不及,知乎会对多条相似的消息进行聚会,以达到减轻用户阅读压力的体验。

消息的三种分类
通过两种产品的简单分析,得出他们的消息有两种分类,在这基础上,我们再加上一种:公告。
公告的主要性质是系统发送一则含有具体内容的消息,站内所有用户都能读取到这条消息。
所以,消息有三种分类:
- 公告 Announce
- 提醒 Remind
- 私信 Message
提醒的语言分析
我们从简书取一组提醒样本:
- 3dbe1bd90774 关注了你
- magicdawn 喜欢了你的文章 《单点登录的三种实现方式》
- 无良程序 喜欢了你的文章 《基于RESTful API 怎么设计用户权限控制?》
- alexcc4 喜欢了你的文章 《在Nodejs中贯彻单元测试》
- 你在《基于RESTful API 怎么设计用户权限控制?》中收到一条 cnlinjie 的评论
- 你的文章《Session原理》已被加入专题 《ios开发》
分析句子结构,提醒的内容无非就是
「谁对一样属于谁的事物做了什么操作」
「someone do something in someone's something」
someone = 提醒的触发者,或者发送者,标记为sender
do something = 提醒的动作,评论、喜欢、关注都属于一个动作,标记为action
something = 提醒的动作作用对象,这就具体到是哪一篇文章,标记为target
someone's = 提醒的动作作用对象的所有者,标记为targetOwner
这就清楚了,sender和targetOwner就是网站的用户,而target是具体到哪一篇文章,如果提醒的对象不仅仅局限于文章,还有其他的话,就需要增加一项targetType,来标记目标是文章还是其他的什么。而action,则是固定的,整个网站会触发提醒的动作可能就只有那几样:评论、喜欢、关注.....(或者其他业务需要提醒的动作)
消息的两种获取方式
- 推 Push
- 拉 Pull
以知乎为例
推的比较常见,需要针对某一个问题维护着一张关注者的列表,每当触发这个问题推送的条件时(例如有人回答问题),就把这个通知发送给每个关注者。
拉的相对麻烦一点,就是推的反向,例如每个用户都有一张关注问题的列表,每当用户上线的时候,对每个问题进行轮询,当问题的事件列表出现了比我原本时间戳大的信息就进行拉取。
而我们则根据消息的不同分类采用不同的获取方式:
通告和提醒,适合使用拉取的方式,消息产生之后,会存在消息表中,用户在某一特定的时间根据自己关注问题的表进行消息的拉取,然后添加到自己的消息队列中,
信息,适合使用推的方式,在发送者建立一条信息之后,同时指定接收者,把消息添加到接收者的消息队列中。
订阅
根据提醒使用拉取的方式,需要维护一个关注某一事物的列表。
这种行为,我们称之为:「订阅」Subscribe
一则订阅有以下三个核心属性:
- 订阅的目标 target
- 订阅的目标类型 targetType
- 订阅的动作 action
比如我发布了一篇文章,那么我会订阅文章《XXX》的评论动作,所以文章《XXX》每被人评论了,就需要发送一则提醒告知我。
订阅的规则还可以扩展
我喜欢了一篇文章,和我发布了一篇文章,订阅的动作可能不一样。
喜欢了一篇文章,我希望我订阅这篇文章更新、评论的动作。
而发布了一篇文章,我希望我只是订阅这篇文章的评论动作。
这时候就需要多一个参数:subscribReason
不同的subscribReason,对应着一个动作数组,
subscribReason = 喜欢,对应着 actions = [更新,评论]
subscribReason = 发布,对应着 actions = [评论]
订阅的规则还还可以扩展
用户可能会有一个自己的订阅设置,比如对于所有的喜欢的动作,我都不希望接收。
比如Knewone的提醒设置

所以我们需要再维护一个表:SubscriptionConfig,来存放用户的提醒设置。
并且,当用户没有提醒设置的时候,可以使用系统提供的一套默认设置:defaultSubscriptionConfig
聚合
如果我发布了一篇文章《XXX》,在我不在线的时候,被评论了10遍,当我一上线的时候,应该是收到十条信息类似于:「谁谁谁评论了你的文章《XXX》」?
还是应该收到一条信息:「甲、乙、丙、丁...评论了你的文章《XXX》」?
知乎在聚合上做的很优秀,要知道他们要实现这个还是挺有技术的:
知乎的消息机制,在技术上如何设计与规划?
网站的消息(通知)系统一般是如何实现的?
关于这部分功能,我们还没有具体的实现方法,暂时也无法讲得更加详细。⊙﹏⊙
五个实体
通过上面的分析,大概知道做这个消息系统,需要哪些实体类:
- 用户消息队列 UserNotify
- 用户 User
- 订阅 Subscription
- 订阅设置 SubscriptionConfig
- 消息 Notify
- 通告 Announce
- 提醒 Remind
- 信息 Message
行为分解
说了这么多,整理一下整个消息流程的一些行为:
- 系统或者管理员,创建消息
- createNotify (make announce | remind | message)
- 用户,订阅消息,取消订阅
- subscribe, cancelSubscription
- 用户管理订阅设置
- getSubscriptionConfig, updateSubscriptionConfig
- 用户,拉取消息
- pullNotify (pull announce | remind | message | all)
- 用户,查询消息队列
- getUserNotify(get announce | remind | message | all)
- 用户阅读消息
- read
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
模型设计
Notify
<code class="javascript">id : {type: <span class="hljs-string">'integer', primaryKey: <span class="hljs-literal">true}, <span class="hljs-comment">// 主键content : {type: <span class="hljs-string">'text'}, <span class="hljs-comment">// 消息的内容type : {type: <span class="hljs-string">'integer', required: <span class="hljs-literal">true, enum: [<span class="hljs-number">1, <span class="hljs-number">2, <span class="hljs-number">3]}, <span class="hljs-comment">// 消息的类型,1: 公告 Announce,2: 提醒 Remind,3:信息 Messagetarget : {type: <span class="hljs-string">'integer'}, <span class="hljs-comment">// 目标的IDtargetType : {type: <span class="hljs-string">'string'}, <span class="hljs-comment">// 目标的类型action : {type: <span class="hljs-string">'string'}, <span class="hljs-comment">// 提醒信息的动作类型sender : {type: <span class="hljs-string">'integer'}, <span class="hljs-comment">// 发送者的IDcreatedAt : {type: <span class="hljs-string">'datetime', required: <span class="hljs-literal">true}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code>
Save Remind
消息表,我们需要target
、targetType
字段,来记录该条提醒所关联的对象。而action
字段,则记录该条提醒所关联的动作。
比如消息:「小明喜欢了文章」
则:
<code class="javascript">target = <span class="hljs-number">123, <span class="hljs-comment">// 文章IDtargetType = <span class="hljs-string">'post', <span class="hljs-comment">// 指明target所属类型是文章sender = <span class="hljs-number">123456 <span class="hljs-comment">// 小明ID</span></span></span></span></span></span></code>
Save Announce and Message
当然,Notify还支持存储公告和信息。它们会用到content
字段,而不会用到target
、targetType
、action
字段。
UserNotify
<code class="javascript">id : {type: <span class="hljs-string">'integer', primaryKey: <span class="hljs-literal">true}, <span class="hljs-comment">// 主键isRead : {type: <span class="hljs-string">'boolean', required: <span class="hljs-literal">true}, user : {type: <span class="hljs-string">'integer', required: <span class="hljs-literal">true}, <span class="hljs-comment">// 用户消息所属者notify : {type: <span class="hljs-string">'integer', required: <span class="hljs-literal">true} <span class="hljs-comment">// 关联的NotifycreatedAt : {type: <span class="hljs-string">'datetime', required: <span class="hljs-literal">true}</span></span></span></span></span></span></span></span></span></span></span></span></span></code>
我们用UserNotify来存储用户的消息队列,它关联一则提醒(Notify)的具体内容。
UserNotify的创建,主要通过两个途径:
- 遍历订阅(Subscription)表拉取公告(Announce)和提醒(Remind)的时候创建
- 新建信息(Message)之后,立刻创建。
Subscription
<code class="javascript">target : {type: <span class="hljs-string">'integer', required: <span class="hljs-literal">true}, <span class="hljs-comment">// 目标的IDtargetType : {type: <span class="hljs-string">'string', required: <span class="hljs-literal">true}, <span class="hljs-comment">// 目标的类型action : {type: <span class="hljs-string">'string'}, <span class="hljs-comment">// 订阅动作,如: comment/like/post/update etc.user : {type: <span class="hljs-string">'integer'},createdAt : {type: <span class="hljs-string">'datetime', required: <span class="hljs-literal">true}</span></span></span></span></span></span></span></span></span></span></span></code>
订阅,是从Notify表拉取消息到UserNotify的前提,用户首先订阅了某一个目标的某一个动作,在此之后产生这个目标的这个动作的消息,才会被通知到该用户。
如:「小明关注了产品A的评论」,数据表现为:
<code class="javascript">target: <span class="hljs-number">123, <span class="hljs-comment">// 产品A的IDtargetType: <span class="hljs-string">'product',action: <span class="hljs-string">'comment',user: <span class="hljs-number">123 <span class="hljs-comment">// 小明的ID</span></span></span></span></span></span></code>
这样,产品A下产生的每一条评论,都会产生通知给小明了。
SubscriptionConfig
<code class="javascript">action: {type: <span class="hljs-string">'json', required: <span class="hljs-literal">true}, <span class="hljs-comment">// 用户的设置user: {type: <span class="hljs-string">'integer'}</span></span></span></span></code>
不同用户可能会有不一样的订阅习惯,在这个表中,用户可以统一针对某种动作进行是否订阅的设置。而默认是使用系统提供的默认配置:
<code class="javascript">defaultSubscriptionConfig: { <span class="hljs-string">'comment' : <span class="hljs-literal">true, <span class="hljs-comment">// 评论 <span class="hljs-string">'like' : <span class="hljs-literal">true, <span class="hljs-comment">// 喜欢}</span></span></span></span></span></span></code>
在这套模型中,
targetType
、action
是可以根据需求来扩展的,例如我们还可以增加多几个动作的提醒:hate
被踩、update
被更新....诸如此类。
配置文件 NotifyConfig
<code class="javascript"><span class="hljs-comment">// 提醒关联的目标类型targetType: { PRODUCT : <span class="hljs-string">'product', <span class="hljs-comment">// 产品 POST : <span class="hljs-string">'post' <span class="hljs-comment">// 文章},<span class="hljs-comment">// 提醒关联的动作action: { COMMENT : <span class="hljs-string">'comment', <span class="hljs-comment">// 评论 LIKE : <span class="hljs-string">'like', <span class="hljs-comment">// 喜欢},<span class="hljs-comment">// 订阅原因对应订阅事件reasonAction: { <span class="hljs-string">'create_product' : [<span class="hljs-string">'comment', <span class="hljs-string">'like'] <span class="hljs-string">'like_product' : [<span class="hljs-string">'comment'], <span class="hljs-string">'like_post' : [<span class="hljs-string">'comment'],},<span class="hljs-comment">// 默认订阅配置defaultSubscriptionConfig: { <span class="hljs-string">'comment' : <span class="hljs-literal">true, <span class="hljs-comment">// 评论 <span class="hljs-string">'like' : <span class="hljs-literal">true, <span class="hljs-comment">// 喜欢}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code>
服务层 NotifyService
NotifyService拥有以下方法:
- createAnnounce(content, sender)
- createRemind(target, targetType, action, sender, content)
- createMessage(content, sender, receiver)
- pullAnnounce(user)
- pullRemind(user)
- subscribe(user, target, targetType, reason)
- cancelSubscription(user, target ,targetType)
- getSubscriptionConfig(userID)
- updateSubscriptionConfig(userID)
- getUserNotify(userID)
- read(user, notifyIDs)
各方法的处理逻辑如下:
createAnnounce(content, sender)
- 往Notify表中插入一条公告记录
createRemind(target, targetType, action, sender, content)
- 往Notify表中插入一条提醒记录
createMessage(content, sender, receiver)
- 往Notify表中插入一条信息记录
- 往UserNotify表中插入一条记录,并关联新建的Notify
pullAnnounce(user)
- 从UserNotify中获取最近的一条公告信息的创建时间:
lastTime
- 用
lastTime
作为过滤条件,查询Notify的公告信息 - 新建UserNotify并关联查询出来的公告信息
pullRemind(user)
- 查询用户的订阅表,得到用户的一系列订阅记录
- 通过每一条的订阅记录的
target
、targetType
、action
、createdAt
去查询Notify表,获取订阅的Notify记录。(注意订阅时间必须早于提醒创建时间) - 查询用户的配置文件SubscriptionConfig,如果没有则使用默认的配置DefaultSubscriptionConfig
- 使用订阅配置,过滤查询出来的Notify
- 使用过滤好的Notify作为关联新建UserNotify
subscribe(user, target, targetType, reason)
- 通过reason,查询NotifyConfig,获取对应的动作组:
actions
- 遍历动作组,每一个动作新建一则Subscription记录
cancelSubscription(user, target ,targetType)
- 删除
user
、target
、targetType
对应的一则或多则记录
getSubscriptionConfig(userID)
- 查询SubscriptionConfig表,获取用户的订阅配置
updateSubscriptionConfig(userID)
- 更新用户的SubscriptionConfig记录
getUserNotify(userID)
- 获取用户的消息列表
read(user, notifyIDs)
- 更新指定的notify,把isRead属性设置为true
时序图
提醒的订阅、创建、拉取

我们可以在产品创建之后,调用NotifyService.subscribe
方法,
然后在产品被评论之后调用NotifyService.createRemind
方法,
再就是用户登录系统或者其他的某一个时刻调用NotifyService.pullRemind
方法,
最后在用户查询消息队列的时候调用NotifyService.getUserNotify
方法。
公告的创建、拉取

在管理员发送了一则公告的时候,调用NotifyService.createAnnounce
方法,
然后在用户登录系统或者其他的某一个时刻调用NotifyService.pullAnnounce
方法,
最后在用户查询消息队列的时候调用NotifyService.getUserNotify
方法。
信息的创建

信息的创建,只需要直接调用NotifyService.createMessage
方法就可以了,
在下一次用户查询消息队列的时候,就会查询这条信息。

许多用户在选择智能手表的时候都会选择的华为的品牌,其中华为GT3pro和GT4都是非常热门的选择,不少用户都很好奇华为GT3pro和GT4有什么区别,下面就就给大家介绍一下二者。华为GT3pro和GT4有什么区别一、外观GT4:46mm和41mm,材质是玻璃表镜+不锈钢机身+高分纤维后壳。GT3pro:46.6mm和42.9mm,材质是蓝宝石玻璃表镜+钛金属机身/陶瓷机身+陶瓷后壳二、健康GT4:采用最新的华为Truseen5.5+算法,结果会更加的精准。GT3pro:多了ECG心电图和血管及安

为什么截图工具在Windows11上不起作用了解问题的根本原因有助于找到正确的解决方案。以下是截图工具可能无法正常工作的主要原因:对焦助手已打开:这可以防止截图工具打开。应用程序损坏:如果截图工具在启动时崩溃,则可能已损坏。过时的图形驱动程序:不兼容的驱动程序可能会干扰截图工具。来自其他应用程序的干扰:其他正在运行的应用程序可能与截图工具冲突。证书已过期:升级过程中的错误可能会导致此issu简单的解决方案这些适合大多数用户,不需要任何特殊的技术知识。1.更新窗口和Microsoft应用商店应用程

Go中Type关键字的用法有定义新的类型别名或者创建新的结构体类型。详细介绍:1、类型别名,使用“type”关键字可以为已有的类型创建别名,这种别名不会创建新的类型,只是为已有的类型提供一个新的名称,类型别名可以提高代码的可读性,使代码更加清晰;2、结构体类型,使用“type”关键字可以创建新的结构体类型,结构体是一种复合类型,可以用于定义包含多个字段的自定义类型等等。

ubuntu挂载移动硬盘出现错误:mount:unknownfilesystemtype'exfat'处理方法如下:Ubuntu13.10或安装exfat-fuse:sudoapt-getinstallexfat-fuseUbuntu13.04或以下sudoapt-add-repositoryppa:relan/exfatsudoapt-getupdatesudoapt-getinstallfuse-exfatCentOSLinux挂载exfat格式u盘错误的解决方法CentOS中加载extfa

第1部分:初始故障排除步骤检查苹果的系统状态:在深入研究复杂的解决方案之前,让我们从基础知识开始。问题可能不在于您的设备;苹果的服务器可能会关闭。访问Apple的系统状态页面,查看AppStore是否正常工作。如果有问题,您所能做的就是等待Apple修复它。检查您的互联网连接:确保您拥有稳定的互联网连接,因为“无法连接到AppStore”问题有时可归因于连接不良。尝试在Wi-Fi和移动数据之间切换或重置网络设置(“常规”>“重置”>“重置网络设置”>设置)。更新您的iOS版本:

php提交表单通过后,弹出的对话框怎样在当前页弹出php提交表单通过后,弹出的对话框怎样在当前页弹出而不是在空白页弹出?想实现这样的效果:而不是空白页弹出:------解决方案--------------------如果你的验证用PHP在后端,那么就用Ajax;仅供参考:HTML code<form name="myform"

watch4pro和gt各自具有不用的特点和适用场景,如果注重功能的全面性、高性能和时尚外观,同时愿意承担较高的价格,那么Watch 4 Pro可能更适合。如果对功能要求不高,更注重电池续航和价格的合理性,那么GT系列可能更适合。最终的选择应根据个人需求、预算和喜好来决定,建议在购买前仔细考虑自己的需求,并参考各种产品的评测和比较,以做出更明智的选择。

在本指南中,我们将学习更多关于Linux中的”type”命令。前提条件:要执行本指南中演示的步骤,您需要以下组件:正确配置的Linux系统。查看如何创建LinuxVM用于测试和学习目的。对命令行界面的基本理解Linux中的Type命令与其他Linux专用命令(例如:ls、chmod、shut、vi、grep、pwd等)不同,”type”命令是一个内置的Bash函数,它显示作为参数提供的命令类型的信息。$type除了Bash,其他炮弹(Zsh、Ksh等)还附带


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Atom editor mac version download
The most popular open source editor

Dreamweaver Mac version
Visual web development tools

Safe Exam Browser
Safe Exam Browser is a secure browser environment for taking online exams securely. This software turns any computer into a secure workstation. It controls access to any utility and prevents students from using unauthorized resources.

DVWA
Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),