消息通知系统模型设计

简介

几乎每一个站点都有消息通知系统,可见通知系统的重要性不言而喻。通知系统看似简单,实际上比较复杂。那么本篇主要讲解常见的消息通知系统的设计和具体实现,包括数据库设计、逻辑关系分析等。算法

常见的站内通知类别:数据库

  • 公告 Announcement
  • 提醒 Remindapp

    • 资源订阅提醒「我关注的资源有更新、评论等事件时通知我」
    • 资源发布提醒「我发布的资源有评论、收藏等事件时通知我」
    • 系统提醒「平台会根据一些算法、规则等可能会对你的资源作一些事情,这时你会收到系统通知」
  • 私信 Mailbox

以上三种消息有各自特色,实现也各不相同,其中「提醒」类通知是最复杂的,下面会详细讲。异步

设计与实现

公告

公告是指平台发送一条含有具体内容的消息,站内全部用户都能收到这条消息。数据库设计

方案一:【适合活跃用户在5万左右】 ide

公告表「notify_announce」
表结构以下:网站

id: {type: 'integer', primaryKey: true, autoIncrement:true} //公告编号;
senderID: {type: 'string', required: true} //发送者编号,一般为系统管理员;
title: {type: 'string', required: true} //公告标题;
content: {type: ’text', required: true} //公告内容;
createdAt: {type: 'timestamp', required: true} //发送时间;

用户公告表「notify_announce_user」
表结构以下:ui

id: {type: 'integer', primaryKey: true, autoIncrement:true} //用户公告编号;
announceID: {type: 'integer'} //公告编号;
recipientID: {type: 'string', required: true} //接收用户编号;
createdAt:{type: 'timestamp', required: true} //拉取公告时间;
state: {type: 'integer', required: true} //状态,已读|未读;
readAt:{type: 'timestamp', required: true} //阅读时间;

平台发布一则公告以后,当用户登陆的时候去拉取站内公告并插入notify_announce_user表,这样那些好久都没登录的用户就不必插入了。「首次拉取,根据用户的注册时间;不然根据notify_announce_user.createdAt即上一次拉取的时间节点获取公告」spa

方案二:【适合活跃用户在百万-千万左右】 设计

和方案一雷同,只是须要把notify_announce_user表进行哈希分表,需事先生成表:notify_announce_<hash(uid)>。

用户公告表「notify_announce_<hash(uid)>」
表结构以下:

id: {type: 'integer', primaryKey: true, autoIncrement:true} //用户公告编号;
announceID: {type: 'integer'} //公告编号;
recipientID: {type: 'string', required: true} //接收用户编号;
createdAt:{type: 'timestamp', required: true} //拉取公告时间;
state: {type: 'integer', required: true} //状态,已读|未读;
readAt:{type: 'timestamp', required: true} //阅读时间;

通知提醒

提醒是指「个人资源」或「我关注的资源」有新的动态产生时通知我。提醒的内容无非就是:
「someone do something in someone's something」
「谁对同样属于谁的事物作了什么操做」

常见的提醒消息例子,如:

XXX 关注了你  - 「这则属于资源发布提醒」   
XXX 喜欢了你的文章 《消息通知系统模型设计》  - 「这则属于资源发布提醒」   
你喜欢的文章《消息通知系统模型设计》有新的评论  - 「这则属于资源订阅提醒」   
你的文章《消息通知系统模型设计》已被加入专题 《系统设计》 - 「这则属于系统提醒」  
小明赞同了你的回答 XXXXXXXXX  -「这则属于资源发布提醒」
最后一个例子中包含了消息的生产者(小明),消息记录的行为(赞同),行为的对象(你的回答内容)

分析提醒类消息的句子结构:

someone = 动做发起者,标记为「sender」  
do something = 对资源的操做,如:评论、喜欢、关注都属于一个动做,标记为「action」  
something = 被做用对象,如:一篇文章,文章的评论等,标记为「object」  
someone's = 动做的目标对象或目标资源的全部者,标记为「objectOwner」
sender 和 objectOwner 就是网站的用户,object 就是网站资源,多是一篇文章,一条文章的评论等等。action 就是动做,能够是赞、评论、收藏、关注、捐款等等。

为了可以更加通俗易懂一点,接下来我会按照通知事件发送的时间顺序开始讲解。

关键点

  • 通知事件
  • 通知设置
  • 通知提醒
  • 消息推送.方式

    • 一对一推送
    • 一对多推送
  • 通知推送.渠道

    • 站内
    • 站外

      • 安卓系统应用
      • IOS系统应用
      • 邮箱
      • 短信
      • WhatsAPP
      • 其它
  • 通知推送.技术

    • WebSocket
    • 第三方SDK开发或接入
  • 通知接收.渠道

    • 站内
    • 站外

      • 安卓系统应用
      • IOS系统应用
      • 邮箱
      • 短信
      • WhatsAPP
      • 其它
  • 消息聚合

通知事件

什么是通知事件?

通知事件就是当用户在网站或应用上产生了支付行为以后,若是你想给用户一个通知,告诉她系统已收到她的付款,那么你就要把这个「支付行为」定义为一个通知事件,而且保存这个通知事件到「通知事件表」里,以便通知系统做异步处理。通知系统会不断的处理「通知事件表」里的数据,分析每个事件应该通知和不通知哪些人。

通知事件表「notify_event」

记录每个用户行为产生的通知事件信息

表结构以下:

id: {type: 'integer', primaryKey: true, autoIncrement:true} 
userID: {type: 'string', required: true} //用户ID
action: {type: 'string', required: true} //动做,如:捐款/更新/评论/收藏
objectID: {type: 'string', required: true}, //对象ID,如:文章ID;
objectType: {type: 'string', required: true} //对象所属类型,如:人、文章、活动、视频等;
createdAt:{type: 'timestamp', required: true} //建立时间;
用户行为定义

「action」即用户行为,如:赞了、评论了、喜欢了、捐款了、收藏了;通常来说,咱们把一个用户行为定义为一个通知类型,那么用户行为必须是须要提早定义好的。

由消息系统内部定义,为后台提供接口,用于通知设置。以下:

notify_action_type := ["donated","conllected","commented","updated"]
对象类型定义

「objectType」即用户行为做用的对象的所属类型,简单的说就是资源类型,如:项目、文章、评论、商品、视频、图片、用户。

由消息系统内部定义,为后台提供接口,用于通知设置。以下:

notify_object_type := ["project","comment"]

通知设置

什么是通知设置?

通知设置是指用户在通知设置页面上能够对某类通知进行屏蔽的设置选项,如:

通知提醒

  • [x] {我关注的}{项目}有动态{更新}时通知我
  • [x] 有人{收藏}{我发布的}{项目}时通知我

通知设置页面上的通知选项信息来源于「后台通知设置管理」,后台须要将通知设置选项的内容描述,转化为逻辑关系保存在「通知设置配置表」。"{}"括号内部就是须要提取的变量,保存到对应的字段中。

通知设置配置表「notify_setting_config」

表结构以下:

id: {type: 'integer', primaryKey: true, autoIncrement:true} //通知设置编号;
objectType: {type: 'string', required: true} //资源对象类型,如:项目、文章、评论、商品、视频、图片、用户;
action: {type: 'string', required: true} //动做,也即通知类型,如:捐款、更新、评论、收藏
objectRelationship: {type: 'string', required: true} //用户与资源的关系,如:用户发布的published,用户关注的followed;
messageTemplate: {type: 'string', required: true} //为某个通知类型设置对应的消息模版
notifyChannel: {type: 'string', required: true} //为某个通知类型设置一个或多个推送渠道
description: {type: 'string', required: true}  //设置选项的内容描述
settingType: {type: 'string', required: true} //remind、privateLetters
消息模版

每个通知类型都有一个特定的消息模版,由消息系统内部定义,为后台提供接口,用于通知设置。以下:

  • XXX 喜欢了你的文章 《消息通知系统模型设计》
  • 你喜欢的文章《消息通知系统模型设计》有新的评论
message_templates := ["donated","conllected","updated"]

message.SetString(language.AmericanEnglish, "donated", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX.")
message.SetString(language.AmericanEnglish, "conllected", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX.")
message.SetString(language.AmericanEnglish, "updated", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX.")
通知渠道

什么是通知渠道?
通知渠道是指消息以什么途径推送给用户。每一条特定通知类型的通知能够被发送到一个或多个渠道,每个通知类型对应几个通知渠道须要在「后台通知设置管理」中配置好。

通知渠道由消息系统内部定义,为后台提供接口,用于通知设置。以下:
站内,短信,邮件,设备,WhatsAPP

notify_channel := ["inside","device","sms","email","whatsapp"]
用户通知设置

用户的通知设置都保存在这张表里。 当无用户的任何记录时,即用户没有设置时,默认表明用户是开启了接收此类通知的;不然若是有用户的设置记录,则表明用户关闭了此类通知的接收。

系统在给用户推送消息的时候必须查询「用户通知设置」,以获取某一通知事件的提醒消息应该推送到哪些用户。也就是说「事件」和「用户」之间有一个订阅关系。

让咱们分析下「订阅」有哪些关键元素:

好比我发布了一篇文章,那么我会订阅文章《XXX》的评论动做,因此文章《XXX》每被人评论了,就须要发送一则提醒告知我。

分析得出如下关键元素:

  • 订阅者「subscriber」
  • 订阅的对象「object」
  • 订阅的动做「action」
  • 订阅对象和订阅者的关系「objectRelationship」

什么是订阅的目标关系呢?

拿知乎来讲,好比我喜欢了一篇文章,我但愿我订阅这篇文章的更新、评论动做。那这篇文章和我什么关系?不是所属关系,只是喜欢。

  • objectRelationship = 我发布的,对应着 actions = [评论,收藏]
  • objectRelationship = 我喜欢的,对应着 actions = [更新,评论]

通常系统都默认用户设置/订阅了全部通知的。

通知设置表「notify_setting」

表结构以下:

id: {type: 'integer', primaryKey: true, autoIncrement:true} //用户通知设置ID;
userId: {type: 'string', required: true},//用户ID,对应 notify_remind 中的 recipientId;
settingId: {type: 'string', required: true},//通知设置表ID;
createdAt:{type: 'timestamp', required: true} //建立时间;
通知设置接口

用户行为、对象类型、消息模版、通知渠道 这些都在消息系统内部定义,为后台提供接口,用于通知设置。

type NotifySetting struct {
    NotifyActionType []string
    NotifyObjectType []string
    MessageTemplates []string
    NotifyChannel []string
}

用户提醒

每个通知事件产生的通知消息将被保存在这里,经过提供接口最终展现在页面上。

通知提醒表「notify_remind」

表结构以下:

id: {type: 'integer', primaryKey: true, autoIncrement:true} //主键;
remindID: {type: 'string', required: true} //通知提醒编号;
senderID: {type: 'string', required: true} //操做者的ID,三个0表明是系统发送的;
senderName: {type: 'string’, required: true} //操做者用户名;
senderAction: {type: 'string', required: true} //操做者的动做,如:捐款、更新、评论、收藏;
objectID: {type: 'string', required: true}, //目标对象ID;
object: {type: 'string', required: false}, //目标对象内容或简介,好比:文章标题;
objectType: {type: 'string', required: true} //被操做对象类型,如:人、文章、活动、视频等;
recipientID: {type: 'string’} //消息接收者;多是对象的全部者或订阅者;
message: {type: 'text', required: true} //消息内容,由提醒模版生成,须要提早定义;
createdAt:{type: 'timestamp', required: true} //建立时间;
status:{type: 'integer', required: false} //是否阅读,默认未读;
readAt:{type: 'timestamp', required: false} //阅读时间;

消息聚合

假如我在抖音上发布了一个短视频,在我不在线的时候,被评论了1000遍,当我一上线的时候,应该是收到一千条消息,相似于:「* 评论了你的文章《XXX》」? 仍是应该收到一条信息:「有1000我的评论了你的文章《XXX》」?

固然是后者更好些,要尽量少的骚扰用户。

消息推送

是否是感受有点晕了,仍是先上一张消息通知的推送流程图吧:
clipboard.png

订阅表一共有两张噢,一张是「通知订阅表」、另外一张是用户对资源的「对象订阅表」。
具体实现就很少讲了,配合这张图,理解上面讲的应该不会有问题了。

私信

一般私信有这么几种需求:

  • 点到点:用户发给用户的站内信,系统发给用户的站内信。「1:1」
  • 点到多:系统发给多个用户的站内信,接收对象较少,并且接收对象无特殊共性。「1:N」
  • 点到面:系统发给用户组的站内信,接收对象同属于某用户组之类的共同属性。「1:N」
  • 点到所有:系统发给全站用户的站内信,接收对象为所有用户,一般为系统通知。「1:N」

这里主要讲「点到点」的站内信。

私信表「notify_mailbox」
表结构以下:

id: {type: 'integer', primaryKey: true, autoIncrement:true} //编号;
dialogueID: {type: 'string', required: true} //对话编号; 
senderID: {type: 'string', required: true} //发送者编号;
recipientID: {type: 'string', required: true} //接收者编号;
messageID: {type: 'integer', required: true} //私信内容ID;
createdAt:{type: 'timestamp', required: true} //发送时间;
state: {type: 'integer', required: true} //状态,已读|未读;
readAt:{type: 'timestamp', required: true} //阅读时间;

Inbox

私信列表
select * from notify_inbox where recipientID="uid" order by createdAt desc

对话列表
select * from notify_inbox where dialogueID=“XXXXXXXXXXXX” and (recipientID=“uid” or senderID="uid") order by createdAt asc

私信回复时,回复的是dialogueID

Outbox

私信列表
select * from notify_inbox where senderID="uid" order by createdAt desc

对话列表
select * from notify_inbox where dialogueID=“XXXXXXXXXXXX” and (senderID=“uid” or recipientID="uid") order by createdAt asc

私信内容表「notify_inbox_message」
表结构以下:

id: {type: 'integer', primaryKey: true, autoIncrement:true} //编号;
senderID: {type: 'string', required: true} //发送者编号;
content: {type: 'string', required: true} //私信内容; 
createdAt:{type: 'timestamp', required: true}

参考

消息系统设计与实现
通知系统设计

相关文章
相关标签/搜索