消息系統設計與實現

文/JC_Huang(簡書做者)
原文連接:http://www.jianshu.com/p/f4d7827821f1
著做權歸做者全部,轉載請聯繫做者得到受權,並標註「簡書做者」。

產品分析

首先咱們來看一下市場上關於消息的實現是怎麼樣的。javascript

簡書

簡書的消息系統主要分了兩種java

  • 簡信
  • 提醒

簡信
簡信的性質其實跟私信是同樣的,是用戶發送給用戶的一則消息,有具體的信息內容。ios


簡書簡信

提醒
而提醒,則是系統發送的一則消息,其文案格式是固定的,而且對特殊對象通常擁有超連接。json


簡書提醒

知乎

知乎跟簡書同樣,主要分了兩種:數組

  • 私信
  • 消息

私信
跟簡書同樣,使用戶發送給用戶的一則消息,也能夠是管理員發送給用戶的消息。post


知乎私信

消息
知乎的消息比簡書的提醒有過之而無不及,知乎會對多條類似的消息進行聚會,以達到減輕用戶閱讀壓力的體驗。單元測試


知乎消息

消息的三種分類

經過兩種產品的簡單分析,得出他們的消息有兩種分類,在這基礎上,咱們再加上一種:公告。
公告的主要性質是系統發送一則含有具體內容的消息,站內全部用戶都能讀取到這條消息。
因此,消息有三種分類:測試

  1. 公告 Announce
  2. 提醒 Remind
  3. 私信 Message

提醒的語言分析

咱們從簡書取一組提醒樣本:網站

  • 3dbe1bd90774 關注了你
  • magicdawn 喜歡了你的文章 《單點登陸的三種實現方式》
  • 無良程序 喜歡了你的文章 《基於RESTful API 怎麼設計用戶權限控制?》
  • alexcc4 喜歡了你的文章 《在Nodejs中貫徹單元測試》
  • 你在《基於RESTful API 怎麼設計用戶權限控制?》中收到一條 cnlinjie 的評論
  • 你的文章《Session原理》已被加入專題 《ios開發》

分析句子結構,提醒的內容無非就是ui

「誰對同樣屬於誰的事物作了什麼操做」
「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的提醒設置


Knewone提醒設置

因此咱們須要再維護一個表:SubscriptionConfig,來存放用戶的提醒設置。
而且,當用戶沒有提醒設置的時候,可使用系統提供的一套默認設置:defaultSubscriptionConfig

聚合

若是我發佈了一篇文章《XXX》,在我不在線的時候,被評論了10遍,當我一上線的時候,應該是收到十條信息相似於:「誰誰誰評論了你的文章《XXX》」?
仍是應該收到一條信息:「甲、乙、丙、丁...評論了你的文章《XXX》」?

知乎在聚合上作的很優秀,要知道他們要實現這個仍是挺有技術的:
知乎的消息機制,在技術上如何設計與規劃?
網站的消息(通知)系統通常是如何實現的?

關於這部分功能,咱們尚未具體的實現方法,暫時也沒法講得更加詳細。⊙﹏⊙

五個實體

經過上面的分析,大概知道作這個消息系統,須要哪些實體類:

  1. 用戶消息隊列 UserNotify
  2. 用戶 User
  3. 訂閱 Subscription
  4. 訂閱設置 SubscriptionConfig
  5. 消息 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

id            : {type: 'integer', primaryKey: true}, // 主鍵 content : {type: 'text'}, // 消息的內容 type : {type: 'integer', required: true, enum: [1, 2, 3]}, // 消息的類型,1: 公告 Announce,2: 提醒 Remind,3:信息 Message target : {type: 'integer'}, // 目標的ID targetType : {type: 'string'}, // 目標的類型 action : {type: 'string'}, // 提醒信息的動做類型 sender : {type: 'integer'}, // 發送者的ID createdAt : {type: 'datetime', required: true}

Save Remind
消息表,咱們須要targettargetType字段,來記錄該條提醒所關聯的對象。而action字段,則記錄該條提醒所關聯的動做。
好比消息:「小明喜歡了文章」
則:

target = 123, // 文章ID targetType = 'post', // 指明target所屬類型是文章 sender = 123456 // 小明ID

Save Announce and Message
固然,Notify還支持存儲公告和信息。它們會用到content字段,而不會用到targettargetTypeaction字段。

UserNotify

id            : {type: 'integer', primaryKey: true}, // 主鍵 isRead : {type: 'boolean', required: true}, user : {type: 'integer', required: true}, // 用戶消息所屬者 notify : {type: 'integer', required: true} // 關聯的Notify createdAt : {type: 'datetime', required: true}

咱們用UserNotify來存儲用戶的消息隊列,它關聯一則提醒(Notify)的具體內容。
UserNotify的建立,主要經過兩個途徑:

  1. 遍歷訂閱(Subscription)表拉取公告(Announce)和提醒(Remind)的時候建立
  2. 新建信息(Message)以後,馬上建立。

Subscription

target      : {type: 'integer', required: true}, // 目標的ID targetType : {type: 'string', required: true}, // 目標的類型 action : {type: 'string'}, // 訂閱動做,如: comment/like/post/update etc. user : {type: 'integer'}, createdAt : {type: 'datetime', required: true}

訂閱,是從Notify表拉取消息到UserNotify的前提,用戶首先訂閱了某一個目標的某一個動做,在此以後產生這個目標的這個動做的消息,纔會被通知到該用戶。
如:「小明關注了產品A的評論」,數據表現爲:

target: 123, // 產品A的ID targetType: 'product', action: 'comment', user: 123 // 小明的ID

這樣,產品A下產生的每一條評論,都會產生通知給小明瞭。

SubscriptionConfig

action: {type: 'json', required: true}, // 用戶的設置 user: {type: 'integer'}

不一樣用戶可能會有不同的訂閱習慣,在這個表中,用戶能夠統一針對某種動做進行是否訂閱的設置。而默認是使用系統提供的默認配置:

defaultSubscriptionConfig: {
  'comment' : true, // 評論 'like' : true, // 喜歡 }

在這套模型中,targetTypeaction是能夠根據需求來擴展的,例如咱們還能夠增長多幾個動做的提醒:hate被踩、update被更新....諸如此類。

配置文件 NotifyConfig

// 提醒關聯的目標類型 targetType: { PRODUCT : 'product', // 產品 POST : 'post' // 文章 }, // 提醒關聯的動做 action: { COMMENT : 'comment', // 評論 LIKE : 'like', // 喜歡 }, // 訂閱緣由對應訂閱事件 reasonAction: { 'create_product' : ['comment', 'like'] 'like_product' : ['comment'], 'like_post' : ['comment'], }, // 默認訂閱配置 defaultSubscriptionConfig: { 'comment' : true, // 評論 'like' : true, // 喜歡 }

服務層 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)

  1. 往Notify表中插入一條公告記錄

createRemind(target, targetType, action, sender, content)

  1. 往Notify表中插入一條提醒記錄

createMessage(content, sender, receiver)

  1. 往Notify表中插入一條信息記錄
  2. 往UserNotify表中插入一條記錄,並關聯新建的Notify

pullAnnounce(user)

  1. 從UserNotify中獲取最近的一條公告信息的建立時間: lastTime
  2. lastTime做爲過濾條件,查詢Notify的公告信息
  3. 新建UserNotify並關聯查詢出來的公告信息

pullRemind(user)

  1. 查詢用戶的訂閱表,獲得用戶的一系列訂閱記錄
  2. 經過每一條的訂閱記錄的targettargetTypeactioncreatedAt去查詢Notify表,獲取訂閱的Notify記錄。(注意訂閱時間必須早於提醒建立時間)
  3. 查詢用戶的配置文件SubscriptionConfig,若是沒有則使用默認的配置DefaultSubscriptionConfig
  4. 使用訂閱配置,過濾查詢出來的Notify
  5. 使用過濾好的Notify做爲關聯新建UserNotify

subscribe(user, target, targetType, reason)

  1. 經過reason,查詢NotifyConfig,獲取對應的動做組:actions
  2. 遍歷動做組,每個動做新建一則Subscription記錄

cancelSubscription(user, target ,targetType)

  1. 刪除usertargettargetType對應的一則或多則記錄

getSubscriptionConfig(userID)

  1. 查詢SubscriptionConfig表,獲取用戶的訂閱配置

updateSubscriptionConfig(userID)

  1. 更新用戶的SubscriptionConfig記錄

getUserNotify(userID)

  1. 獲取用戶的消息列表

read(user, notifyIDs)

  1. 更新指定的notify,把isRead屬性設置爲true

時序圖

提醒的訂閱、建立、拉取


提醒的訂閱、建立、拉取


咱們能夠在產品建立以後,調用NotifyService.subscribe方法,
而後在產品被評論以後調用NotifyService.createRemind方法,
再就是用戶登陸系統或者其餘的某一個時刻調用NotifyService.pullRemind方法,
最後在用戶查詢消息隊列的時候調用NotifyService.getUserNotify方法。

公告的建立、拉取


公告的建立、拉取


在管理員發送了一則公告的時候,調用NotifyService.createAnnounce方法,
而後在用戶登陸系統或者其餘的某一個時刻調用NotifyService.pullAnnounce方法,
最後在用戶查詢消息隊列的時候調用NotifyService.getUserNotify方法。

信息的建立


信息的建立


信息的建立,只須要直接調用NotifyService.createMessage方法就能夠了,在下一次用戶查詢消息隊列的時候,就會查詢這條信息。

相關文章
相關標籤/搜索