天天看點

如何設計一個消息中心

作者:插猹的閏土
如何設計一個消息中心

如今的内容型産品,不管提供的是什麼類型的内容,在其主功能之外,不可避免的會有另一個十分重要的功能——消息中心。

如何設計一個消息中心

而無論是資訊流、論壇、信箱,還是私聊、群聊、通知,推拉模型是内容型(包括:社交型)産品架構的核心。做出正确選擇的關鍵在于對産品形态和系統元件清晰的認識。

今天我們将重心放在消息中心上,聊一聊如何設計一個消息中心。

需求分析

消息中心通常會有兩個功能(如下圖所示):

  1. 使用者通知(點贊、評論、關注、@等)
  2. 官方通知
如何設計一個消息中心

接下來我們将會對這兩類通知進行一個簡單的抽象。

首先,可以确定的是,對于使用者通知,每個使用者都不一樣(我的點贊清單和你的點贊清單肯定是不一樣的),是以對于每個人我們都需要維護一個「收件箱」。

當 A 點贊了 B 的内容,後端系統在收到了這一個點贊消息後,會将點贊資訊寫入 B 的 「收件箱」,并标明這是 A 在 xxx 時點贊的 xxx 内容。這是一個系統将消息 推送 給 B 的過程。

而對于官方通知,每個人(幾乎)都是一樣的(使用者有可能設定了屏蔽,系統也可能指定了發送人群),并且官方通知是由系統自然下發的,是以對于系統來說需要維護一個系統「發件箱」。

發件箱維護了官方想給使用者的通知,每次打開消息中心時,使用者都會主動來系統「拉取」官方最新的消息,并和使用者自己的「收件箱」裡的官方通知進行比較,以确認是否已讀該條通知。這是一個使用者主動從系統「拉取」通知的過程。

推拉模型

其實到這裡就已經點出了這兩個場景背後的一套模型——推拉模型。而之是以在這兩種場景選擇不同的運作機制,其實背後牽扯到的是讀寫擴散的問題。

推模型

如何設計一個消息中心

先看推模型,對于任何一個内容創作者來說,最開心的事情莫過于打開軟體會有一堆點贊/評論的小紅點。對于大 V 來說,打開 App 檢視點贊消息的頻率根本比不過别人給你點贊的頻率,這是一個很典型的讀少寫多的場景。每當有一個使用者點贊該大 V 時,都會将索引資訊(一般為内容 ID、類型、發表時間等索引資料)寫到使用者的收件箱中。

  • 優點:讀很輕。僅需要讀取消息清單即可。
  • 缺點:寫很重。一旦使用者的内容品質很高,可能會收到大量的點贊/評論,會有大量的寫入操作。

拉模型

如何設計一個消息中心

再看拉模型,以官方通知為例,一般官方通知是由營運人員釋出的,一個月可能也不會有幾條,但是每次使用者進入 App 時都會看看是否有新的官方通知進來,這是一個很典型的讀多寫少的場景。

  • 優點:寫很輕,節省空間。系統隻需維護一個屬于自己的消息清單即可。
  • 缺點:讀很重,計算量大。假設可以發送官方通知的生産者較多(例如淘寶裡的一系列官方業務),則每次都需要從這些消息生産者裡拉取最新的内容。

流程設計

使用者通知

對于使用者通知,流程設計如下:

如何設計一個消息中心

對于該流程,有幾點需要注意的:

異步發送

當使用者出發了點贊/關注/評論行為時,被點贊/評論/關注的使用者,其實不需要立即感覺,是以也不需要立即将互動資訊寫入該使用者的收件箱中,是以可以考慮以消息隊列的方式通知出去,緩解系統壓力。

緩存前置

寫入消息時,如果直接寫入使用者收件箱,可能會導緻使用者在請求消息清單時,将請求全部打到 DB,造成系統故障,是以通常會在更新使用者收件箱時雙寫使用者緩存。

官方通知

如何設計一個消息中心

相較于使用者通知,官方通知由于引入官方營運這一角色,操作上會稍微複雜一些(如上圖所示),是以整個系統的設計也會稍微複雜一些。

官方營運發送通知到「發件箱」中,「發件箱」中保留所有線上的通知清單。使用者檢視通知清單時,從官方「發件箱」中擷取到未讀通知,從自己的「收件箱」中查詢曆史通知。即:

  1. 營運寫發件箱
  2. 使用者讀發件箱
  3. 使用者寫收件箱

流程示意圖如下

如何設計一個消息中心

官方營運在營運背景進行通知的編輯和釋出,釋出的通知更新到資料庫中進行持久化存儲。(這裡選擇 mysql 資料庫進行資料持久化,下一章節将會提到)

通知發生變更時,會發送通知變更消息。基于該消息更新單條通知的緩存,并更新官方發件箱清單(供前台查詢)。

使用者檢視通知清單時,若為第一頁,需要從官方發件箱隊列檢視是否有未讀的通知。

若有未讀通知,則和曆史通知第一頁合并,傳回給使用者。同時異步寫入使用者的收件箱中。

持久化方案

說完了核心的業務流程後,接下來要面臨的問題就是,資料存在哪?

上文有提到會将官方通知的發件箱利用 mysql 持久化,因為官方通知的數量較少,且官方通知是一個拉模型,重讀輕寫,壓力多半由緩存來扛,是以底層資料存儲在 mysql 中并無大礙。

重難點主要在使用者的「收件箱」。

之前有提過,使用者收件箱的邏輯是一個重寫輕讀的推模型,一旦大 V 的内容更新,他的收件箱可能在一瞬間湧入大量的寫流量。另外,對于幾個頭部大 V 來說,收到幾千萬的點贊并不是什麼難事,每一個點贊資訊都要寫入到該使用者的收件箱中,這就要求了底層存儲需要能支援海量資料。

基于以上情景,MySQL 可能并不是一個合适的持久化方案。此時,我們可以嘗試使用 HBase。

如何設計一個消息中心

MySQL 與 HBase

MySQL 和 HBase 是我們日常應用中常用的兩個資料庫,分别解決應用的線上事務問題和大資料場景的海量存儲問題。

綜合對比

MySQL:是常用的資料庫,采用行存儲模式,底層是 binlog,用來存儲業務資料,資料存儲量較小。

HBase:列式資料庫,底層是 hdfs,可以存儲海量的資料,主要用來存儲海量的業務資料和日志資料。

從引擎結構看差異

如何設計一個消息中心

HBase 和 MySQL 的核心差異在于底層的資料結構,HBase 使用 LSM(Log-Structure Merge)樹,Innodb 使用 B+樹。

LSM 樹,即日志結構合并樹(Log-Structured Merge-Tree)。 其實它并不屬于一個具體的資料結構,它更多是一種資料結構的設計思想。

如何設計一個消息中心

它的核心思路其實非常簡單,就是假定記憶體足夠大,是以不需要每次有資料更新就必須将資料寫入到磁盤中,而可以先将最新的資料駐留在記憶體中,等到積累到最後多之後,再使用歸并排序的方式将記憶體内的資料合并追加到磁盤隊尾 (因為所有待排序的樹都是有序的,可以通過合并排序的方式快速合并到一起)。

LSM 具有批量特性,存儲延遲。當寫讀比例很大的時候(寫比讀多),LSM 樹相比于 B 樹有更好的性能。因為随着 insert 操作,為了維護 B 樹結構,節點分裂。 讀磁盤的随機讀寫機率會變大,性能會逐漸減弱。 多次單頁随機寫,變成一次多頁随機寫,複用了磁盤尋道時間,極大提升效率。

是以,由引擎結構(B+Tree vs LSM Tree)看到的能力差異:

  1. MySQL:讀寫均衡、存在空間碎片
  2. HBase:側重于寫、存儲緊湊無浪費、Io 放大、資料導入能力強

從架構對比看差異

相比 MySQL,HBase 的架構特點:

  1. 完全分布式(資料分片、故障自恢複)
  2. 底層使用 HDFS(存儲計算分離)。

由架構看到的能力差異:

  1. MySQL:運維簡單(元件少)、延時低(通路路徑短)
  2. HBase:擴充性好、内置容錯恢複與資料備援

總結

本文我們講述了如何從官方通知和使用者通知兩個方面切入,設計一個 App 的常見功能——消息中心。但該方案仍然有很多潛在的問題:如果官方通知的來源很多呢?如何解決寫擴散帶來的成本問題?這些都是值得探索的問題。

事實上,消息中心雖然是一個十分常見的功能,但背後涉及到的東西非常複雜,釋出/訂閱、推拉模型、讀寫擴散等問題都會影響到我們的架構設計。

架構設計的過程,就是取舍的過程,而如何取舍,則是一門學問。對于現在紛繁複雜的網際網路業務,永遠沒有最好的架構,隻有最适合的架構。

最後,我們抛個問題,朋友圈是推模型還是拉模型?

繼續閱讀