如何設(shè)計(jì)一個(gè)消息中心
發(fā)布日期:2022/9/9 9:47:39 瀏覽量:
如今的內(nèi)容型產(chǎn)品,不管提供的是什么類型的內(nèi)容,在其主功能之外,不可避免的會(huì)有另一個(gè)十分重要的功能——消息中心。
而無(wú)論是信息流、論壇、信箱,還是私聊、群聊、通知,推拉模型是內(nèi)容型(包括:社交型)產(chǎn)品架構(gòu)的核心。做出正確選擇的關(guān)鍵在于對(duì)產(chǎn)品形態(tài)和系統(tǒng)組件清晰的認(rèn)識(shí)。
今天我們將重心放在消息中心上,聊一聊如何設(shè)計(jì)一個(gè)消息中心。
需求分析
消息中心通常會(huì)有兩個(gè)功能(如下圖所示):
- 用戶通知(點(diǎn)贊、評(píng)論、關(guān)注、@等)
- 官方通知
接下來(lái)我們將會(huì)對(duì)這兩類通知進(jìn)行一個(gè)簡(jiǎn)單的抽象。
首先,可以確定的是,對(duì)于用戶通知,每個(gè)用戶都不一樣(我的點(diǎn)贊列表和你的點(diǎn)贊列表肯定是不一樣的),因此對(duì)于每個(gè)人我們都需要維護(hù)一個(gè)「收件箱」。
當(dāng) A 點(diǎn)贊了 B 的內(nèi)容,后端系統(tǒng)在收到了這一個(gè)點(diǎn)贊消息后,會(huì)將點(diǎn)贊信息寫入 B 的 「收件箱」,并標(biāo)明這是 A 在 xxx 時(shí)點(diǎn)贊的 xxx 內(nèi)容。這是一個(gè)系統(tǒng)將消息 推送 給 B 的過(guò)程。
而對(duì)于官方通知,每個(gè)人(幾乎)都是一樣的(用戶有可能設(shè)置了屏蔽,系統(tǒng)也可能指定了發(fā)送人群),并且官方通知是由系統(tǒng)自然下發(fā)的,因此對(duì)于系統(tǒng)來(lái)說(shuō)需要維護(hù)一個(gè)系統(tǒng)「發(fā)件箱」。
發(fā)件箱維護(hù)了官方想給用戶的通知,每次打開(kāi)消息中心時(shí),用戶都會(huì)主動(dòng)來(lái)系統(tǒng)「拉取」官方最新的消息,并和用戶自己的「收件箱」里的官方通知進(jìn)行比較,以確認(rèn)是否已讀該條通知。這是一個(gè)用戶主動(dòng)從系統(tǒng)「拉取」通知的過(guò)程。
推拉模型
其實(shí)到這里就已經(jīng)點(diǎn)出了這兩個(gè)場(chǎng)景背后的一套模型——推拉模型。而之所以在這兩種場(chǎng)景選擇不同的運(yùn)行機(jī)制,其實(shí)背后牽扯到的是讀寫擴(kuò)散的問(wèn)題。
推模型
先看推模型,對(duì)于任何一個(gè)內(nèi)容創(chuàng)作者來(lái)說(shuō),最開(kāi)心的事情莫過(guò)于打開(kāi)軟件會(huì)有一堆點(diǎn)贊/評(píng)論的小紅點(diǎn)。對(duì)于大 V 來(lái)說(shuō),打開(kāi) App 查看點(diǎn)贊消息的頻率根本比不過(guò)別人給你點(diǎn)贊的頻率,這是一個(gè)很典型的讀少寫多的場(chǎng)景。每當(dāng)有一個(gè)用戶點(diǎn)贊該大 V 時(shí),都會(huì)將索引信息(一般為內(nèi)容 ID、類型、發(fā)表時(shí)間等索引數(shù)據(jù))寫到用戶的收件箱中。
- 優(yōu)點(diǎn):讀很輕。僅需要讀取消息列表即可。
- 缺點(diǎn):寫很重。一旦用戶的內(nèi)容質(zhì)量很高,可能會(huì)收到大量的點(diǎn)贊/評(píng)論,會(huì)有大量的寫入操作。
再看拉模型,以官方通知為例,一般官方通知是由運(yùn)營(yíng)人員發(fā)布的,一個(gè)月可能也不會(huì)有幾條,但是每次用戶進(jìn)入 App 時(shí)都會(huì)看看是否有新的官方通知進(jìn)來(lái),這是一個(gè)很典型的讀多寫少的場(chǎng)景。
- 優(yōu)點(diǎn):寫很輕,節(jié)省空間。系統(tǒng)只需維護(hù)一個(gè)屬于自己的消息列表即可。
- 缺點(diǎn):讀很重,計(jì)算量大。假設(shè)可以發(fā)送官方通知的生產(chǎn)者較多(例如淘寶里的一系列官方業(yè)務(wù)),則每次都需要從這些消息生產(chǎn)者里拉取最新的內(nèi)容。
對(duì)于用戶通知,流程設(shè)計(jì)如下:
對(duì)于該流程,有幾點(diǎn)需要注意的:
異步發(fā)送
當(dāng)用戶出發(fā)了點(diǎn)贊/關(guān)注/評(píng)論行為時(shí),被點(diǎn)贊/評(píng)論/關(guān)注的用戶,其實(shí)不需要立即感知,因此也不需要立即將互動(dòng)信息寫入該用戶的收件箱中,因此可以考慮以消息隊(duì)列的方式通知出去,緩解系統(tǒng)壓力。
緩存前置
寫入消息時(shí),如果直接寫入用戶收件箱,可能會(huì)導(dǎo)致用戶在請(qǐng)求消息列表時(shí),將請(qǐng)求全部打到 DB,造成系統(tǒng)故障,因此通常會(huì)在更新用戶收件箱時(shí)雙寫用戶緩存。
官方通知
相較于用戶通知,官方通知由于引入官方運(yùn)營(yíng)這一角色,操作上會(huì)稍微復(fù)雜一些(如上圖所示),因此整個(gè)系統(tǒng)的設(shè)計(jì)也會(huì)稍微復(fù)雜一些。
官方運(yùn)營(yíng)發(fā)送通知到「發(fā)件箱」中,「發(fā)件箱」中保留所有在線的通知列表。用戶查看通知列表時(shí),從官方「發(fā)件箱」中獲取到未讀通知,從自己的「收件箱」中查詢歷史通知。即:
- 運(yùn)營(yíng)寫發(fā)件箱
- 用戶讀發(fā)件箱
- 用戶寫收件箱
流程示意圖如下
官方運(yùn)營(yíng)在運(yùn)營(yíng)后臺(tái)進(jìn)行通知的編輯和發(fā)布,發(fā)布的通知更新到數(shù)據(jù)庫(kù)中進(jìn)行持久化存儲(chǔ)。(這里選擇 mysql 數(shù)據(jù)庫(kù)進(jìn)行數(shù)據(jù)持久化,下一章節(jié)將會(huì)提到)
通知發(fā)生變更時(shí),會(huì)發(fā)送通知變更消息?;谠撓⒏聠螚l通知的緩存,并更新官方發(fā)件箱列表(供前臺(tái)查詢)。
用戶查看通知列表時(shí),若為第一頁(yè),需要從官方發(fā)件箱隊(duì)列查看是否有未讀的通知。
若有未讀通知,則和歷史通知第一頁(yè)合并,返回給用戶。同時(shí)異步寫入用戶的收件箱中。
持久化方案
說(shuō)完了核心的業(yè)務(wù)流程后,接下來(lái)要面臨的問(wèn)題就是,數(shù)據(jù)存在哪?
上文有提到會(huì)將官方通知的發(fā)件箱利用 mysql 持久化,因?yàn)楣俜酵ㄖ臄?shù)量較少,且官方通知是一個(gè)拉模型,重讀輕寫,壓力多半由緩存來(lái)扛,所以底層數(shù)據(jù)存儲(chǔ)在 mysql 中并無(wú)大礙。
重難點(diǎn)主要在用戶的「收件箱」。
之前有提過(guò),用戶收件箱的邏輯是一個(gè)重寫輕讀的推模型,一旦大 V 的內(nèi)容更新,他的收件箱可能在一瞬間涌入大量的寫流量。另外,對(duì)于幾個(gè)頭部大 V 來(lái)說(shuō),收到幾千萬(wàn)的點(diǎn)贊并不是什么難事,每一個(gè)點(diǎn)贊信息都要寫入到該用戶的收件箱中,這就要求了底層存儲(chǔ)需要能支持海量數(shù)據(jù)。
基于以上情景,MySQL 可能并不是一個(gè)合適的持久化方案。此時(shí),我們可以嘗試使用 HBase。
MySQL 與 HBase
MySQL 和 HBase 是我們?nèi)粘?yīng)用中常用的兩個(gè)數(shù)據(jù)庫(kù),分別解決應(yīng)用的在線事務(wù)問(wèn)題和大數(shù)據(jù)場(chǎng)景的海量存儲(chǔ)問(wèn)題。
綜合對(duì)比
MySQL:是常用的數(shù)據(jù)庫(kù),采用行存儲(chǔ)模式,底層是 binlog,用來(lái)存儲(chǔ)業(yè)務(wù)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)量較小。
HBase:列式數(shù)據(jù)庫(kù),底層是 hdfs,可以存儲(chǔ)海量的數(shù)據(jù),主要用來(lái)存儲(chǔ)海量的業(yè)務(wù)數(shù)據(jù)和日志數(shù)據(jù)。
從引擎結(jié)構(gòu)看差異
HBase 和 MySQL 的核心差異在于底層的數(shù)據(jù)結(jié)構(gòu),HBase 使用 LSM(Log-Structure Merge)樹,Innodb 使用 B+樹。
LSM 樹,即日志結(jié)構(gòu)合并樹(Log-Structured Merge-Tree)。 其實(shí)它并不屬于一個(gè)具體的數(shù)據(jù)結(jié)構(gòu),它更多是一種數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)思想。
它的核心思路其實(shí)非常簡(jiǎn)單,就是假定內(nèi)存足夠大,因此不需要每次有數(shù)據(jù)更新就必須將數(shù)據(jù)寫入到磁盤中,而可以先將最新的數(shù)據(jù)駐留在內(nèi)存中,等到積累到最后多之后,再使用歸并排序的方式將內(nèi)存內(nèi)的數(shù)據(jù)合并追加到磁盤隊(duì)尾 (因?yàn)樗写判虻臉涠际怯行虻?,可以通過(guò)合并排序的方式快速合并到一起)。
LSM 具有批量特性,存儲(chǔ)延遲。當(dāng)寫讀比例很大的時(shí)候(寫比讀多),LSM 樹相比于 B 樹有更好的性能。因?yàn)殡S著 insert 操作,為了維護(hù) B 樹結(jié)構(gòu),節(jié)點(diǎn)分裂。 讀磁盤的隨機(jī)讀寫概率會(huì)變大,性能會(huì)逐漸減弱。 多次單頁(yè)隨機(jī)寫,變成一次多頁(yè)隨機(jī)寫,復(fù)用了磁盤尋道時(shí)間,極大提升效率。
因此,由引擎結(jié)構(gòu)(B+Tree vs LSM Tree)看到的能力差異:
- MySQL:讀寫均衡、存在空間碎片
- HBase:側(cè)重于寫、存儲(chǔ)緊湊無(wú)浪費(fèi)、Io 放大、數(shù)據(jù)導(dǎo)入能力強(qiáng)
相比 MySQL,HBase 的架構(gòu)特點(diǎn):
- 完全分布式(數(shù)據(jù)分片、故障自恢復(fù))
- 底層使用 HDFS(存儲(chǔ)計(jì)算分離)。
由架構(gòu)看到的能力差異:
- MySQL:運(yùn)維簡(jiǎn)單(組件少)、延時(shí)低(訪問(wèn)路徑短)
- HBase:擴(kuò)展性好、內(nèi)置容錯(cuò)恢復(fù)與數(shù)據(jù)冗余
本文我們講述了如何從官方通知和用戶通知兩個(gè)方面切入,設(shè)計(jì)一個(gè) App 的常見(jiàn)功能——消息中心。但該方案仍然有很多潛在的問(wèn)題:如果官方通知的來(lái)源很多呢?如何解決寫擴(kuò)散帶來(lái)的成本問(wèn)題?這些都是值得探索的問(wèn)題。
事實(shí)上,消息中心雖然是一個(gè)十分常見(jiàn)的功能,但背后涉及到的東西非常復(fù)雜,發(fā)布/訂閱、推拉模型、讀寫擴(kuò)散等問(wèn)題都會(huì)影響到我們的架構(gòu)設(shè)計(jì)。
架構(gòu)設(shè)計(jì)的過(guò)程,就是取舍的過(guò)程,而如何取舍,則是一門學(xué)問(wèn)。對(duì)于現(xiàn)在紛繁復(fù)雜的互聯(lián)網(wǎng)業(yè)務(wù),永遠(yuǎn)沒(méi)有最好的架構(gòu),只有最適合的架構(gòu)。
最后,我們拋個(gè)問(wèn)題,朋友圈是推模型還是拉模型?
馬上咨詢: 如果您有業(yè)務(wù)方面的問(wèn)題或者需求,歡迎您咨詢!我們帶來(lái)的不僅僅是技術(shù),還有行業(yè)經(jīng)驗(yàn)積累。
QQ: 39764417/308460098 Phone: 13 9800 1 9844 / 135 6887 9550 聯(lián)系人:石先生/雷先生