由于文章篇幅较长,而作者精力有限,不希望这么早就精尽人亡,故分成上下篇来写消息系统的设计与实现。上篇主要讲的是一些概念,搞清楚我们要做的这个消息系统的主要内容。而下篇主要讲具体的实现,会包括架构设计,数据库设计,业务流程详细的实现等。
整个系统的设计与实现,并非我一人之力就可以完成的。这其中是同事们大家一起讨论与商讨的结果,而我只是把它细化,呈现出来。
我只是一个会思考的idea搬运工。
简书连接:「消息系统设计与实现「上篇」」
产品分析
首先我们来看一下市场上关于消息的实现是怎么样的。
简书
简书的消息系统主要分了两种
- 简信
- 提醒
简信 简信的性质其实跟私信是一样的,是用户发送给用户的一则消息,有具体的信息内容。
提醒 而提醒,则是系统发送的一则消息,其文案格式是固定的,并且对特殊对象一般拥有超链接。
知乎
知乎跟简书一样,主要分了两种:
- 私信
- 消息
私信 跟简书一样,使用户发送给用户的一则消息,也可以是管理员发送给用户的消息。
消息 知乎的消息比简书的提醒有过之而无不及,知乎会对多条相似的消息进行聚会,以达到减轻用户阅读压力的体验。
消息的三种分类
通过两种产品的简单分析,得出他们的消息有两种分类,在这基础上,我们再加上一种:公告。 公告的主要性质是系统发送一则含有具体内容的消息,站内所有用户都能读取到这条消息。 所以,消息有三种分类:
- 公告 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的提醒设置 <img src=“http://xia-dev.b0.upaiyun.com/80cc4aaf-0568-478c-8513-b8821e57520f.jpg” alt=“Knewone提醒设置” width=“500”>
所以我们需要再维护一个表: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
在本文的「下篇」我们来探讨一下:模型怎么做、数据库怎么设计、代码结构怎么来、一些逻辑上的时序图应该是怎么样的。
---------------------- 更新于 2015/11/15 -----------------------
关联文章:消息系统设计与实现「下篇」
👍
谢谢楼主,学习了
mark
系统性的整理 ,棒~!
mark
👊🏻👌🏻
mark
楼主很棒,期待下文
赞 期待后续报道
赞
期待下文
期待后续报道!
试试CNode的消息系统 :)
占座。
mark,很有用。最近刚好需要,谢谢楼主
mark,学习
mark学习
mark
期待下文
好腻害
如果是多台服务器聊天, 聊天室 应该分布存储在不同的机器上,还是统一存储在指定的服务器上?
mark
楼主,我想了一种办法,可以实现“聚合”的效果,想和你讨论下。
mark
来自炫酷的 CNodeMD
mark
收藏
mark
有没有 开源项目给大家参考下?
@pangguoming 我有过想法整理一套代码放到Github,已加到个人Todo,待有时间就弄一下😏
评论一下,get一下
赞。
mark
分析的很详细,可上手。
很好,正好最近在研究这个
收
好帖子,收藏了
来自酷炫的 CNodeMD
mark 就喜欢这个社区,分享的都是核心技术。。哈哈
很棒的文章啊,干货 对于已阅读或早期的提醒跟私信,又是怎么处理呢?
mark
最近正想看下消息系统,赞
来自酷炫的 CNodeMD
attr
mark
真的是一个很棒的帖子!自己也在造一个消息系统的轮子,但是从来没有像这样系统总结过!受教了!
怎么收藏这文章,请教?
学习
mark
mark 一下
楼主逻辑清晰啊, 我觉得简聊可以优化下Mac的客户端, 实在是太不好看了。
mark
mark
很好!!
Mark 自豪地采用 CNodeJS ionic
啊…那篇也是你写的啊…
@magicdawn 是啊
mark
mark
mark
你猜我来自哪?
mark
我也来mark
给力,支持一下!ฏ๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎ฏ๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎ฏ๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎๎
mark