消息通知系統模型設計


本篇主要明確消息通知系統的概念和具體實現,包括數據庫設計、技術方案、邏輯關系分析等。消息通知系統是一個比較復雜的系統,這里主要分析站內消息如何設計和實現。

我們常見的消息推送渠道有以下幾種:
  • 設備推送
  • 站內推送
  • 短信推送
  • 郵箱推送
我們常見的站內通知有以下幾種類別:
  • 公告 Announcement
  • 提醒 Remind

    • 資源訂閱提醒「我關注的資源有更新、評論等事件時通知我」
    • 資源發布提醒「我發布的資源有評論、收藏等事件時通知我」
    • 系統提醒「平台會根據一些算法、規則等可能會對你的資源做一些事情,這時你會收到系統通知」
  • 私信 Mailbox

以上三種消息有各自特點,實現也各不相同,其中「提醒」類通知是最復雜的,下面會詳細講。

數據模型設計

公告

公告是指平台發送一條含有具體內容的消息,站內所有用戶都能收到這條消息。

方案一:【適合活躍用戶在5萬左右】

公告表「notify_announce」 
表結構如下:

  1.  
    id: { type: 'integer', primaryKey: true, autoIncrement:true} //公告編號;
  2.  
    senderID: { type: 'string', required: true} //發送者編號,通常為系統管理員;
  3.  
    title: { type: 'string', required: true} //公告標題;
  4.  
    content: { type: ’text', required: true} //公告內容;
  5.  
    createdAt: {type: 'timestamp', required: true} //發送時間;

用戶公告表「notify_announce_user」 
表結構如下:

  1.  
    id: { type: 'integer', primaryKey: true, autoIncrement:true} //用戶公告編號;
  2.  
    announceID: { type: 'integer'} //公告編號;
  3.  
    recipientID: { type: 'string', required: true} //接收用戶編號;
  4.  
    createdAt:{ type: 'timestamp', required: true} //拉取公告時間;
  5.  
    state: { type: 'integer', required: true} //狀態,已讀|未讀;
  6.  
    readAt:{ type: 'timestamp', required: true} //閱讀時間;

平台發布一則公告之后,當用戶登錄的時候去拉取站內公告並插入notify_announce_user表,這樣那些很久都沒登陸的用戶就沒必要插入了。「首次拉取,根據用戶的注冊時間;否則根據notify_announce_user.createdAt即上一次拉取的時間節點獲取公告」

方案二:【適合活躍用戶在百萬-千萬左右】

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

用戶公告表「notify_announce_<hash(uid)>」 
表結構如下:

  1.  
    id: { type: 'integer', primaryKey: true, autoIncrement:true} //用戶公告編號;
  2.  
    announceID: { type: 'integer'} //公告編號;
  3.  
    recipientID: { type: 'string', required: true} //接收用戶編號;
  4.  
    createdAt:{ type: 'timestamp', required: true} //拉取公告時間;
  5.  
    state: { type: 'integer', required: true} //狀態,已讀|未讀;
  6.  
    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 就是動作,可以是贊、評論、收藏、關注、捐款等等。

提醒設置

提醒通常是可以在「設置-通知」里自定義配置的,用戶可以選擇性地「訂閱」接收和不接收某類通知。

呈現在界面上是這樣的:

  1.  
    通知設置
  2.  
     
  3.  
    我發布的 publish
  4.  
    文章
  5.  
    被 評論 是/否 通知我
  6.  
    被 收藏 是/否 通知我
  7.  
    被 點贊 是/否 通知我
  8.  
    被 喜歡 是/否 通知我
  9.  
    被 捐款 是/否 通知我
  10.  
     
  11.  
    我訂閱的 follow
  12.  
    文章
  13.  
    有 更新 是/否 通知我
  14.  
    被 評論 是/否 通知我

訂閱

一般系統默認是訂閱了所有通知的。系統在給用戶推送消息的時候必須查詢通知「訂閱」模塊,以獲取某一事件提醒消息應該推送到哪些用戶。
也就是說「事件」和「用戶」之間有一個訂閱關系。

那么接下來我們分析下「訂閱」有哪些關鍵元素:

比如我發布了一篇文章,那么我會訂閱文章《XXX》的評論動作,所以文章《XXX》每被人評論了,就需要發送一則提醒告知我。

分析得出以下關鍵元素:

  • 訂閱者「subscriber」
  • 訂閱的對象「object」
  • 訂閱的動作「action」
  • 訂閱對象和訂閱者的關系「objectRelationship」

什么是訂閱的目標關系呢?

拿知乎來說,比如我喜歡了一篇文章,我希望我訂閱這篇文章的更新、評論動作。那這篇文章和我什么關系?不是所屬關系,只是喜歡。

  • objectRelationship = 我發布的,對應着 actions = [評論,收藏]
  • objectRelationship = 我喜歡的,對應着 actions = [更新,評論]

講了那么多,現在來構建「提醒」的數據結構該吧!

提醒表「notify_remind」 
表結構如下:

  1.  
    id: { type: 'integer', primaryKey: true, autoIncrement:true} //主鍵;
  2.  
    remindID: { type: 'string', required: true} //通知提醒編號;
  3.  
    senderID: { type: 'string', required: true} //操作者的ID,三個0代表是系統發送的;
  4.  
    senderName: { type: 'string’, required: true} //操作者用戶名;
  5.  
    senderAction: {type: 'string', required: true} //操作者的動作,如:贊了、評論了、喜歡了、捐款了、收藏了;
  6.  
    objectID: {type: 'string', required: true}, //目標對象ID;
  7.  
    object: {type: 'string', required: false}, //目標對象內容或簡介,比如:文章標題;
  8.  
    objectType: {type: 'string', required: true} //被操作對象類型,如:人、文章、活動、視頻等;
  9.  
    recipientID: {type: 'string’} //消息接收者;可能是對象的所有者或訂閱者;
  10.  
    message: { type: 'text', required: true} //消息內容,由提醒模版生成,需要提前定義;
  11.  
    createdAt:{ type: 'timestamp', required: true} //創建時間;
  12.  
    status:{ type: 'integer', required: false} //是否閱讀,默認未讀;
  13.  
    readAt:{ type: 'timestamp', required: false} //閱讀時間;

假如:特朗普關注了金正恩,以下字段的值是這樣的

  1.  
    senderID = 特朗普的ID
  2.  
    senderName = 特朗普
  3.  
    senderAction = 關注
  4.  
    objectID = 金正恩的ID
  5.  
    object = 金正恩
  6.  
    objectType = 人
  7.  
    recipientID = 金正恩的ID
  8.  
    message = 特朗普關注了金正恩
  9.  
     
  10.  
    這種情況objectID 和 recipientID是一樣的。
這里需要特別說下消息模版,模版由「對象」、「動作」和「對象關系」構成唯一性。

通知提醒訂閱表「notify_remind_subscribe」 
表結構如下:

  1.  
    id: { type: 'integer', primaryKey: true, autoIncrement:true} //訂閱ID;
  2.  
    userID: { type: 'string', required: true},//用戶ID,對應 notify_remind 中的 recipientID;
  3.  
    objectType: { type: 'string', required: true} //資源對象類型,如:文章、評論、視頻、活動、用戶;
  4.  
    action: { type: 'string', required: true} //資源訂閱動作,多個動作逗號分隔如: comment,like,post,update etc.
  5.  
    objectRelationship: { type: 'string', required: true} //用戶與資源的關系,用戶發布的published,用戶關注的followed;
  6.  
    createdAt:{ type: 'timestamp', required: true} //創建時間;
特別說下「objectRelationship」字段的作用,這個字段用來區分「消息模版」,為什么呢?因為同一個「資源對象」和「動作」會有兩類訂閱者,一類是該資源的Owner,另一類是該資源的Subscriber,這兩類人收到的通知消息內容應該是不一樣的。

聚合

假如我在抖音上發布了一個短視頻,在我不在線的時候,被評論了1000遍,當我一上線的時候,應該是收到一千條消息,類似於:「* 評論了你的文章《XXX》」? 還是應該收到一條信息:「有1000個人評論了你的文章《XXX》」?

當然是后者更好些,要盡可能少的騷擾用戶。

消息推送

是不是感覺有點暈了,還是先上一張消息通知的推送流程圖吧: 
clipboard.png

訂閱表一共有兩張噢,一張是「通知訂閱表」、另一張是用戶對資源的「對象訂閱表」。 
具體實現就不多講了,配合這張圖,理解上面講的應該不會有問題了。

私信

通常私信有這么幾種需求:

  • 點到點:用戶發給用戶的站內信,系統發給用戶的站內信。「1:1」
  • 點到多:系統發給多個用戶的站內信,接收對象較少,而且接收對象無特殊共性。「1:N」
  • 點到面:系統發給用戶組的站內信,接收對象同屬於某用戶組之類的共同屬性。「1:N」
  • 點到全部:系統發給全站用戶的站內信,接收對象為全部用戶,通常為系統通知。「1:N」

這里主要講「點到點」的站內信。

私信表「notify_mailbox」
表結構如下:

  1.  
    id: { type: 'integer', primaryKey: true, autoIncrement:true} //編號;
  2.  
    dialogueID: { type: 'string', required: true} //對話編號;
  3.  
    senderID: { type: 'string', required: true} //發送者編號;
  4.  
    recipientID: { type: 'string', required: true} //接收者編號;
  5.  
    messageID: { type: 'integer', required: true} //私信內容ID;
  6.  
    createdAt:{ type: 'timestamp', required: true} //發送時間;
  7.  
    state: { type: 'integer', required: true} //狀態,已讀|未讀;
  8.  
    readAt:{ type: 'timestamp', required: true} //閱讀時間;

Inbox

  1.  
    私信列表
  2.  
    select * from notify_inbox where recipientID="uid" order by createdAt desc
  3.  
     
  4.  
    對話列表
  5.  
    select * from notify_inbox where dialogueID=“XXXXXXXXXXXX” and (recipientID=“uid” or senderID="uid") order by createdAt asc

私信回復時,回復的是dialogueID

Outbox

  1.  
    私信列表
  2.  
    select * from notify_inbox where senderID="uid" order by createdAt desc
  3.  
     
  4.  
    對話列表
  5.  
    select * from notify_inbox where dialogueID=“XXXXXXXXXXXX” and (senderID=“uid” or recipientID="uid") order by createdAt asc

私信內容表「notify_inbox_message」
表結構如下:

  1.  
    id: { type: 'integer', primaryKey: true, autoIncrement:true} //編號;
  2.  
    senderID: { type: 'string', required: true} //發送者編號;
  3.  
    content: { type: 'string', required: true} //私信內容;
  4.  
    createdAt:{ type: 'timestamp', required: true}

參考

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM