消息通知系統模型設計

簡介

幾乎每一個站點都有消息通知系統,可見通知系統的重要性不言而喻。通知系統看似簡單,實際上比較複雜。那麼本篇主要講解常見的消息通知系統的設計和具體實現,包括數據庫設計、邏輯關係分析等。算法

常見的站內通知類別:數據庫

  • 公告 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}

參考

消息系統設計與實現
通知系統設計

相關文章
相關標籤/搜索