當(dāng)前最流行的Feed流產(chǎn)品有微博、微信朋友圈、頭條的資訊推薦、快手抖音的視頻推薦等,還有一些變種,比如私信、通知等,這些系統(tǒng)都是Feed流系統(tǒng),接下來(lái)我們會(huì)介紹如何設(shè)計(jì)一個(gè)Feed流系統(tǒng)架構(gòu)。
Feed流系統(tǒng)特點(diǎn)
Feed流本質(zhì)上是一個(gè)數(shù)據(jù)流,是將 “N個(gè)發(fā)布者的信息單元” 通過(guò) “關(guān)注關(guān)系” 傳送給 “M個(gè)接收者”。
Feed流系統(tǒng)是一個(gè)數(shù)據(jù)流系統(tǒng),所以我們核心要看數(shù)據(jù)。從數(shù)據(jù)層面看,數(shù)據(jù)分為三類(lèi),分別是:
- 發(fā)布者的數(shù)據(jù):發(fā)布者產(chǎn)生數(shù)據(jù),然后數(shù)據(jù)需要按照發(fā)布者組織,需要根據(jù)發(fā)布者查到所有數(shù)據(jù),比如微博的個(gè)人頁(yè)面、朋友圈的個(gè)人相冊(cè)等。
- 關(guān)注關(guān)系:系統(tǒng)中個(gè)體間的關(guān)系,微博中是關(guān)注,是單向流,朋友圈是好友,是雙向流。不管是單向還是雙向,當(dāng)發(fā)布者發(fā)布一條信息時(shí),該條信息的流動(dòng)永遠(yuǎn)是單向的。
- 接收者的數(shù)據(jù):從不同發(fā)布者那里獲取到的數(shù)據(jù),然后通過(guò)某種順序(一般為時(shí)間)組織在一起,比如微博的首頁(yè)、朋友圈首頁(yè)等。這些數(shù)據(jù)具有時(shí)間熱度屬性,越新的數(shù)據(jù)越有價(jià)值,越新的數(shù)據(jù)就要排在最前面。
針對(duì)這三類(lèi)數(shù)據(jù),我們可以有如下定義:
- 存儲(chǔ)庫(kù):存儲(chǔ)發(fā)布者的數(shù)據(jù),永久保存。
- 關(guān)注表:用戶(hù)關(guān)系表,永久保存。
- 同步庫(kù):存儲(chǔ)接收者的時(shí)間熱度數(shù)據(jù),只需要保留最近一段時(shí)間的數(shù)據(jù)即可。
設(shè)計(jì)Feed流系統(tǒng)時(shí)最核心的是確定清楚產(chǎn)品層面的定義,需要考慮的因素包括:
-
產(chǎn)品用戶(hù)規(guī)模:用戶(hù)規(guī)模在十萬(wàn)、千萬(wàn)、十億級(jí)時(shí),設(shè)計(jì)難度和側(cè)重點(diǎn)會(huì)不同。
-
關(guān)注關(guān)系(單向、雙寫(xiě)):如果是雙向,那么就不會(huì)有大V,否則會(huì)有大V存在。上述是選擇數(shù)據(jù)存儲(chǔ)系統(tǒng)最核心的幾個(gè)考慮點(diǎn),除此之外,還有一些需要考慮的:
-
如何實(shí)現(xiàn)Meta和Feed內(nèi)容搜索?
- 雖然Feed流系統(tǒng)本身可以不需要搜索,但是一個(gè)Feed流產(chǎn)品必須要有搜索,否則信息發(fā)現(xiàn)難度會(huì)加大,用戶(hù)留存率會(huì)大幅下降。
-
Feed流的順序是時(shí)間還是其他分?jǐn)?shù),比如個(gè)人的喜好程度?
- 雙向關(guān)系時(shí)由于關(guān)系很緊密,一定是按時(shí)間排序,就算一個(gè)關(guān)系很緊密的人發(fā)了一條空消息或者低價(jià)值消息,那我們也會(huì)需要關(guān)注了解的。
- 單向關(guān)系時(shí),那么可能就會(huì)存在大V,大V的粉絲數(shù)量理論極限就是整個(gè)系統(tǒng)的用戶(hù)數(shù),有一些產(chǎn)品會(huì)讓所有用戶(hù)都默認(rèn)關(guān)注產(chǎn)品負(fù)責(zé)人,這種產(chǎn)品中,該負(fù)責(zé)人就是最大的大V,粉絲數(shù)就是用戶(hù)規(guī)模。接下來(lái),我們看看整個(gè)Feed流系統(tǒng)如何設(shè)計(jì)。
Feed流系統(tǒng)設(shè)計(jì)
上一節(jié),我們提前思考了Feed流系統(tǒng)的幾個(gè)關(guān)鍵點(diǎn),接下來(lái),在這一節(jié),我們自頂向下來(lái)設(shè)計(jì)一個(gè)Feed流系統(tǒng)。
1. 產(chǎn)品定義
第一步,我們首先需要定義產(chǎn)品,我們要做的產(chǎn)品是哪一種類(lèi)型,常見(jiàn)的類(lèi)型有:
- 微博類(lèi)
- 朋友圈類(lèi)
- 抖音類(lèi)
- 私信類(lèi)
接著,再詳細(xì)看一下這幾類(lèi)產(chǎn)品的異同:
類(lèi)型 | 關(guān)注關(guān)系 | 是否有大V | 時(shí)效性 | 排序 |
---|---|---|---|---|
微博類(lèi) | 單向 | 有 | 秒~分 | 時(shí)間 |
抖音類(lèi) | 單向/無(wú) | 有 | 秒~分 | 推薦 |
朋友圈類(lèi) | 雙向 | 無(wú) | 秒 | 時(shí)間 |
私信類(lèi) | 雙向 | 無(wú) | 秒 | 時(shí)間 |
上述對(duì)比中,只對(duì)比各類(lèi)產(chǎn)品最核心、或者最根本特點(diǎn),其他次要的不考慮。比如微博中互相關(guān)注后就是雙向關(guān)注了,但是這個(gè)不是微博的立命之本,只是補(bǔ)充,無(wú)法撼動(dòng)根本。
從上面表格可以看出來(lái),主要分為兩種區(qū)分:
-
關(guān)注關(guān)系是單向還是雙向:
- 如果是單向,那么可能就會(huì)存在大V效應(yīng),同時(shí)時(shí)效性可以低一些,比如到分鐘級(jí)別;
- 如果是雙向,那就是好友,好友的數(shù)量有限,那么就不會(huì)有大V,因?yàn)槊總€(gè)人的精力有限,他不可能主動(dòng)加幾千萬(wàn)的好友,這時(shí)候因?yàn)殛P(guān)系更精密,時(shí)效性要求會(huì)更高,需要都秒級(jí)別。
-
排序是時(shí)間還是推薦:
- 用戶(hù)對(duì)feed流最容易接受的就是時(shí)間,目前大部分都是時(shí)間。
- 但是有一些場(chǎng)景,是從全網(wǎng)數(shù)據(jù)里面根據(jù)用戶(hù)的喜好給用戶(hù)推薦和用戶(hù)喜好度最匹配的內(nèi)容,這個(gè)時(shí)候就需要用推薦了,這種情況一般也會(huì)省略掉關(guān)注了,相對(duì)于關(guān)注了全網(wǎng)所有用戶(hù),比如抖音、頭條等。確定了產(chǎn)品類(lèi)型后,還需要繼續(xù)確定的是系統(tǒng)設(shè)計(jì)目標(biāo):需要支持的最大用戶(hù)數(shù)是多少?十萬(wàn)、百萬(wàn)、千萬(wàn)還是億?
用戶(hù)數(shù)很少的時(shí)候,就比較簡(jiǎn)單,這里我們主要考慮 億級(jí)用戶(hù) 的情況,因?yàn)槿绻到y(tǒng)能支持億級(jí),那么其他量級(jí)也能支持。為了支持億級(jí)規(guī)模的用戶(hù),主要子系統(tǒng)選型時(shí)需要考慮水平擴(kuò)展能力以及一些子系統(tǒng)的可用性和可靠性了,因?yàn)橄到y(tǒng)大了后,任何一個(gè)子系統(tǒng)的不穩(wěn)定都很容易波及整個(gè)系統(tǒng)。
2. 存儲(chǔ)
我們先來(lái)看看最重要的存儲(chǔ),不管是哪種同步模式,在存儲(chǔ)上都是一樣的,我們定義用戶(hù)消息的存儲(chǔ)為存儲(chǔ)庫(kù)。存儲(chǔ)庫(kù)主要滿(mǎn)足三個(gè)需求:
- 可靠存儲(chǔ)用戶(hù)發(fā)送的消息,不能丟失。否則就找不到自己曾經(jīng)發(fā)布到朋友圈狀態(tài)了。
- 讀取某個(gè)人發(fā)布過(guò)的所有消息,比如個(gè)人主頁(yè)等。
- 數(shù)據(jù)永久保存。
所以,存儲(chǔ)庫(kù)最重要的特征就是兩點(diǎn):
- 數(shù)據(jù)可靠、不丟失。
- 由于數(shù)據(jù)要永久保存,數(shù)據(jù)會(huì)一直增長(zhǎng),所以要易于水平擴(kuò)展。
綜上,可以選為存儲(chǔ)庫(kù)的系統(tǒng)大概有兩類(lèi):
特點(diǎn) | 分布式NoSQL | 關(guān)系型數(shù)據(jù)庫(kù)(分庫(kù)分表) |
---|---|---|
可靠性 | 極高 | 高 |
水平擴(kuò)展能力 | 線性 | 需要改造 |
水平擴(kuò)展速度 | 毫秒 | 無(wú) |
常見(jiàn)系統(tǒng) | Tablestore、Bigtable | MySQL、PostgreSQL |
- 對(duì)于可靠性,分布式NoSQL的可靠性要高于關(guān)系型數(shù)據(jù)庫(kù),這個(gè)可能有違很多人的認(rèn)知。主要是關(guān)系型數(shù)據(jù)庫(kù)發(fā)展很長(zhǎng)時(shí)間了,且很成熟了,數(shù)據(jù)放在上面大家放心,而分布式NoSQL數(shù)據(jù)庫(kù)發(fā)展晚,使用的并不多,不太信任。但是,分布式NoSQL需要存儲(chǔ)的數(shù)據(jù)量更多,對(duì)數(shù)據(jù)可靠性的要求也加嚴(yán)格,所以一般都是存儲(chǔ)三份,可靠性會(huì)更高。目前在一些云廠商中的關(guān)系型數(shù)據(jù)庫(kù)因?yàn)椴捎昧撕头植际絅oSQL類(lèi)似的方式,所以可靠性也得到了大幅提高。
- 水平擴(kuò)展能力:對(duì)于分布式NoSQL數(shù)據(jù)庫(kù),數(shù)據(jù)天然是分布在多臺(tái)機(jī)器上,當(dāng)一臺(tái)機(jī)器上的數(shù)據(jù)量增大后,可以通過(guò)自動(dòng)分裂兩部分,然后將其中一半的數(shù)據(jù)遷移到另一臺(tái)機(jī)器上去,這樣就做到了線性擴(kuò)展。而關(guān)系型數(shù)據(jù)庫(kù)需要在擴(kuò)容時(shí)再次分庫(kù)分表。
所以,結(jié)論是:
- 如果是自建系統(tǒng),且不具備分布式NoSQL數(shù)據(jù)庫(kù)運(yùn)維能力,且數(shù)據(jù)規(guī)模不大,那么可以使用MySQL,這樣可以撐一段時(shí)間。
- 如果是基于云服務(wù),那么就用分布式NoSQL,比如Tablestore或Bigtable。
- 如果數(shù)據(jù)規(guī)模很大,那么也要用分布式NoSQL,否則就是走上一條不歸路。
如果使用Tablestore,那么存儲(chǔ)庫(kù)表設(shè)計(jì)結(jié)構(gòu)如下:
主鍵列 | 第一列主鍵 | 第二列主鍵 | 屬性列 | 屬性列 |
---|---|---|---|---|
列名 | user_id | message_id | content | other |
解釋 | 消息發(fā)送者用戶(hù)ID | 消息順序ID,可以使用timestamp。 | 內(nèi)容 | 其他內(nèi)容 |
到此,我們確定了存儲(chǔ)庫(kù)的選型,那么系統(tǒng)架構(gòu)的輪廓有了:
3. 同步
系統(tǒng)規(guī)模和產(chǎn)品類(lèi)型,以及存儲(chǔ)系統(tǒng)確定后,我們可以確定同步方式,常見(jiàn)的方式有三種:
- 推模式(也叫寫(xiě)擴(kuò)散):和名字一樣,就是一種推的方式,發(fā)送者發(fā)送了一個(gè)消息后,立即將這個(gè)消息推送給接收者,但是接收者此時(shí)不一定在線,那么就需要有一個(gè)地方存儲(chǔ)這個(gè)數(shù)據(jù),這個(gè)存儲(chǔ)的地方我們稱(chēng)為:同步庫(kù)。推模式也叫寫(xiě)擴(kuò)散的原因是,一個(gè)消息需要發(fā)送個(gè)多個(gè)粉絲,那么這條消息就會(huì)復(fù)制多份,寫(xiě)放大,所以也叫寫(xiě)擴(kuò)散。這種模式下,對(duì)同步庫(kù)的要求就是寫(xiě)入能力極強(qiáng)和穩(wěn)定。讀取的時(shí)候因?yàn)橄⒁呀?jīng)發(fā)到接收者的收件箱了,只需要讀一次自己的收件箱即可,讀請(qǐng)求的量極小,所以對(duì)讀的QPS需求不大。歸納下,推模式中對(duì)同步庫(kù)的要求只有一個(gè):寫(xiě)入能力強(qiáng)。
- 拉模式(也叫讀擴(kuò)散):這種是一種拉的方式,發(fā)送者發(fā)送了一條消息后,這條消息不會(huì)立即推送給粉絲,而是寫(xiě)入自己的發(fā)件箱,當(dāng)粉絲上線后再去自己關(guān)注者的發(fā)件箱里面去讀取,一條消息的寫(xiě)入只有一次,但是讀取最多會(huì)和粉絲數(shù)一樣,讀會(huì)放大,所以也叫讀擴(kuò)散。拉模式的讀寫(xiě)比例剛好和寫(xiě)擴(kuò)散相反,那么對(duì)系統(tǒng)的要求是:讀取能力強(qiáng)。另外這里還有一個(gè)誤區(qū),很多人在最開(kāi)始設(shè)計(jì)feed流系統(tǒng)時(shí),首先想到的是拉模式,因?yàn)檫@種和用戶(hù)的使用體感是一樣的,但是在系統(tǒng)設(shè)計(jì)上這種方式有不少痛點(diǎn),最大的是每個(gè)粉絲需要記錄自己上次讀到了關(guān)注者的哪條消息,如果有1000個(gè)關(guān)注者,那么這個(gè)人需要記錄1000個(gè)位置信息,這個(gè)量和關(guān)注量成正比的,遠(yuǎn)比用戶(hù)數(shù)要大的多,這里要特別注意,雖然在產(chǎn)品前期數(shù)據(jù)量少的時(shí)候這種方式可以應(yīng)付,但是量大了后就會(huì)事倍功半,得不償失,切記切記。
- 推拉結(jié)合模式:推模式在單向關(guān)系中,因?yàn)榇嬖诖骎,那么一條消息可能會(huì)擴(kuò)散幾百萬(wàn)次,但是這些用戶(hù)中可能有一半多是僵尸,永遠(yuǎn)不會(huì)上線,那么就存在資源浪費(fèi)。而拉模式下,在系統(tǒng)架構(gòu)上會(huì)很復(fù)雜,同時(shí)需要記錄的位置信息是天量,不好解決,尤其是用戶(hù)量多了后會(huì)成為第一個(gè)故障點(diǎn)?;诖耍杂辛送评Y(jié)合模式,大部分用戶(hù)的消息都是寫(xiě)擴(kuò)散,只有大V是讀擴(kuò)散,這樣既控制了資源浪費(fèi),又減少了系統(tǒng)設(shè)計(jì)復(fù)雜度。但是整體設(shè)計(jì)復(fù)雜度還是要比推模式復(fù)雜。
用圖表對(duì)比:
類(lèi)型 | 推模式 | 拉模式 | 推拉結(jié)合模式 |
---|---|---|---|
寫(xiě)放大 | 高 | 無(wú) | 中 |
讀放大 | 無(wú) | 高 | 中 |
用戶(hù)讀取延時(shí) | 毫秒 | 秒 | 秒 |
讀寫(xiě)比例 | 1:99 | 99:1 | ~50:50 |
系統(tǒng)要求 | 寫(xiě)能力強(qiáng) | 讀能力強(qiáng) | 讀寫(xiě)都適中 |
常見(jiàn)系統(tǒng) | Tablestore、Bigtable等LSM架構(gòu)的分布式NoSQL | Redis、memcache等緩存系統(tǒng)或搜索系統(tǒng)(推薦排序場(chǎng)景) | 兩者結(jié)合 |
架構(gòu)復(fù)雜度 | 簡(jiǎn)單 | 復(fù)雜 | 更復(fù)雜 |
介紹完同步模式中所有場(chǎng)景和模式后,我們歸納下:
- 如果產(chǎn)品中是雙向關(guān)系,那么就采用推模式。
- 如果產(chǎn)品中是單向關(guān)系,且用戶(hù)數(shù)少于1000萬(wàn),那么也采用推模式,足夠了。
- 如果產(chǎn)品是單向關(guān)系,單用戶(hù)數(shù)大于1000萬(wàn),那么采用推拉結(jié)合模式,這時(shí)候可以從推模式演進(jìn)過(guò)來(lái),不需要額外重新推翻重做。
- 永遠(yuǎn)不要只用拉模式。
- 如果是一個(gè)初創(chuàng)企業(yè),先用推模式,快速把系統(tǒng)設(shè)計(jì)出來(lái),然后讓產(chǎn)品去驗(yàn)證、迭代,等客戶(hù)數(shù)大幅上漲到1000萬(wàn)后,再考慮升級(jí)為推拉集合模式。
- 如果是按推薦排序,那么是另外的考慮了,架構(gòu)會(huì)完全不一樣,這個(gè)后面專(zhuān)門(mén)文章介紹。
如果選擇了Tablestore,那么同步庫(kù)表設(shè)計(jì)結(jié)構(gòu)如下:
主鍵列 | 第一列主鍵 | 第二列主鍵 | 屬性列 | 屬性列 | 屬性列 |
---|---|---|---|---|---|
列名 | user_id | sequence_id | sender_id | message_id | other |
解釋 | 消息接收者用戶(hù)ID | 消息順序ID,可以使用timestamp + send_user_id,也可以直接使用Tablestore的自增列。 | 發(fā)送者的用戶(hù)ID | store_table中的message_id列的值,也就是消息ID。通過(guò)sender_id和message_id可以到store_table中查詢(xún)到消息內(nèi)容 | 其他內(nèi)容,同步庫(kù)中不需要包括消息內(nèi)容。 |
確定了同步庫(kù)的架構(gòu)如下:
4. 元數(shù)據(jù)
前面介紹了同步和存儲(chǔ)后,整個(gè)Feed流系統(tǒng)的基礎(chǔ)功能完成了,但是對(duì)于一個(gè)完整Feed流產(chǎn)品而言,還缺元數(shù)據(jù)部分,接下來(lái),我們看元數(shù)據(jù)如何處理:
Feed流系統(tǒng)中的元數(shù)據(jù)主要包括:
- 用戶(hù)詳情和列表。
- 關(guān)注或好友關(guān)系。
- 推送session池。
我們接下來(lái)逐一來(lái)看。
4.1 用戶(hù)詳情和列表
主要是用戶(hù)的詳情,包括用戶(hù)的各種自定義屬性和系統(tǒng)附加的屬性,這部分的要求只需要根據(jù)用戶(hù)ID查詢(xún)到就可以了。
可以采用的分布式NoSQL系統(tǒng)或者關(guān)系型數(shù)據(jù)庫(kù)都可以。
如果使用NoSQL數(shù)據(jù)庫(kù)Tablestore,那么用戶(hù)詳情表設(shè)計(jì)結(jié)構(gòu)如下:
主鍵順序 | 第一列主鍵 | 屬性列-1 | 屬性列-2 | ...... |
---|---|---|---|---|
字段名 | user_id | nick_name | gender | other |
備注 | 主鍵列,用于唯一確定一個(gè)用戶(hù) | 用戶(hù)昵稱(chēng),用戶(hù)自定義屬性 | 用戶(hù)性別,用戶(hù)自定義屬性 | 其他屬性,包括用戶(hù)自定義屬性列和系統(tǒng)附加屬性列。Tablestore是FreeSchema類(lèi)型的,可以隨時(shí)在任何一行增加新列而不影響原有數(shù)據(jù)。 |
4.2 關(guān)注或好友關(guān)系
這部分是存儲(chǔ)關(guān)系,查詢(xún)的時(shí)候需要支持查詢(xún)關(guān)注列表或者粉絲列表,或者直接好友列表,這里就需要根據(jù)多個(gè)屬性列查詢(xún)需要索引能力,這里,存儲(chǔ)系統(tǒng)也可以采用兩類(lèi),關(guān)系型、分布式NoSQL數(shù)據(jù)庫(kù)。
-
如果已經(jīng)有了關(guān)系型數(shù)據(jù)庫(kù)了,且數(shù)據(jù)量較少,則選擇關(guān)系型數(shù)據(jù)庫(kù),比如MySQL等。
-
如果數(shù)據(jù)量比較大,這個(gè)時(shí)候就有兩種選擇:
- 使用具有索引的系統(tǒng),比如云上的Tablestore,更簡(jiǎn)單,吞吐更高,擴(kuò)容能力也一并解決了。
- 需要分布式事務(wù),可以采用支持分布式事務(wù)的系統(tǒng),比如分布式關(guān)系型數(shù)據(jù)庫(kù)。
如果使用Tablestore,那么關(guān)注關(guān)系表設(shè)計(jì)結(jié)構(gòu)如下:
Table:user_relation_table
主鍵順序 | 第一列主鍵 | 第一列主鍵 | 屬性列 | 屬性列 |
---|---|---|---|---|
Table字段名 | user_id | follow_user_id | timestamp | other |
備注 | 用戶(hù)ID | 粉絲用戶(hù)ID | 關(guān)注時(shí)間 | 其他屬性列 |
多元索引的索引結(jié)構(gòu):
Table字段名 | user_id | follow_user_id | timestamp |
---|---|---|---|
是否Index | 是 | 是 | 是 |
是否enableSortAndAgg | 是 | 是 | 是 |
是否store | 是 | 是 | 是 |
查詢(xún)的時(shí)候:
- 如果需要查詢(xún)某個(gè)人的粉絲列表:使用TermQuery查詢(xún)固定user_id,且按照timestamp排序。
- 如果需要查詢(xún)某個(gè)人的關(guān)注列表:使用TermQuery查詢(xún)固定follow_user_id,且按照timestamp排序。
- 當(dāng)前數(shù)據(jù)寫(xiě)入Table后,需要5~10秒鐘延遲后會(huì)在多元索引中查詢(xún)到,未來(lái)會(huì)優(yōu)化到2秒以?xún)?nèi)。
除了使用多元索引外,還可以使用GlobalIndex。
4.3 推送session池
思考一個(gè)問(wèn)題,發(fā)送者將消息發(fā)送后,接收者如何知道自己有新消息來(lái)了?客戶(hù)端周期性去刷新?如果是這樣子,那么系統(tǒng)的讀請(qǐng)求壓力會(huì)隨著客戶(hù)端增長(zhǎng)而增長(zhǎng),這時(shí)候就會(huì)有一個(gè)風(fēng)險(xiǎn),比如平時(shí)的設(shè)備在線率是20%~30%,突然某天平臺(tái)爆發(fā)了一個(gè)熱點(diǎn)消息,大量休眠設(shè)備登陸,這個(gè)時(shí)候就會(huì)出現(xiàn)“查詢(xún)風(fēng)暴”,一下子就把系統(tǒng)打垮了,所有的用戶(hù)都不能用了。
解決這個(gè)問(wèn)題的一個(gè)思路是,在服務(wù)端維護(hù)一個(gè)推送session池,這個(gè)里面記錄哪些用戶(hù)在線,然后當(dāng)用戶(hù)A發(fā)送了一條消息給用戶(hù)B后,服務(wù)端在寫(xiě)入存儲(chǔ)庫(kù)和同步庫(kù)后,再通知一下session池中的用戶(hù)B的session,告訴他:你有新消息了。然后session-B再去讀消息,然后有消息后將消息推送給客戶(hù)端?;蛘哂邢⒑蠼o客戶(hù)端推送一下有消息了,客戶(hù)端再去拉。
這個(gè)session池使用在同步中,但是本質(zhì)還是一個(gè)元數(shù)據(jù),一般只需要存在于內(nèi)存中即可,但是考慮到failover情況,那就需要持久化,這部分?jǐn)?shù)據(jù)由于只需要指定單Key查詢(xún),用分布式NoSQL或關(guān)系型數(shù)據(jù)庫(kù)都可以,一般復(fù)用當(dāng)前的系統(tǒng)即可。
如果使用Tablestore,那么session表設(shè)計(jì)結(jié)構(gòu)如下:
主鍵列順序 | 第一列主鍵 | 第二列主鍵 | 屬性列 |
---|---|---|---|
列名 | user_id | device_id | last_sequence_id |
備注 | 接收者用戶(hù)ID | 設(shè)備ID,同一個(gè)用戶(hù)可能會(huì)有多個(gè)設(shè)備,不同設(shè)備的讀取位置可能不一致,所以這里需要一個(gè)設(shè)備ID。如果不需要支持多終端,則這一列可以省略。 | 該接收者已經(jīng)推送給客戶(hù)端的最新的順序ID |
5. 評(píng)論
除了私信類(lèi)型外,其他的feed流類(lèi)型中,都有評(píng)論功能,評(píng)論的屬性和存儲(chǔ)庫(kù)差不多,但是多了一層關(guān)系:被評(píng)論的消息,所以只要將評(píng)論按照被被評(píng)論消息分組組織即可,然后查詢(xún)時(shí)也是一個(gè)范圍查詢(xún)就行。這種查詢(xún)方式很簡(jiǎn)單,用不到關(guān)系型數(shù)據(jù)庫(kù)中復(fù)雜的事務(wù)、join等功能,很適合用分布式NoSQL數(shù)據(jù)庫(kù)來(lái)存儲(chǔ)。
所以,一般的選擇方式就是:
- 如果系統(tǒng)中已經(jīng)有了分布式NoSQL數(shù)據(jù)庫(kù),比如Tablestore、Bigtable等,那么直接用這些即可。
- 如果沒(méi)有上述系統(tǒng),那么如果有MySQL等關(guān)系型數(shù)據(jù)庫(kù),那就選關(guān)系型數(shù)據(jù)庫(kù)即可。
- 如果選擇了Tablestore,那么“評(píng)論表”設(shè)計(jì)結(jié)構(gòu)如下:
主鍵列順序 | 第一列主鍵 | 第二列主鍵 | 屬性列 | 屬性列 | 屬性列 |
---|---|---|---|---|---|
字段名 | message_id | comment_id | comment_content | reply_to | other |
備注 | 微博ID或朋友圈ID等消息的ID | 這一條評(píng)論的ID | 評(píng)論內(nèi)容 | 回復(fù)給哪個(gè)用戶(hù) | 其他 |
如果需要搜索評(píng)論內(nèi)容,那么對(duì)這張表建立多元索引即可。
6. 贊
最近幾年,“贊”或“l(fā)ike”功能很流行,贊功能的實(shí)現(xiàn)和評(píng)論類(lèi)似,只是比評(píng)論少了一個(gè)內(nèi)容,所以選擇方式和評(píng)論一樣。
如果選擇了Tablestore,那么“贊表”設(shè)計(jì)結(jié)構(gòu)同評(píng)論表,這里就不再贅述了。
系統(tǒng)架構(gòu)中加了元數(shù)據(jù)系統(tǒng)后的架構(gòu)如下:
7. 搜索
到此,我們已經(jīng)介紹完了Feed流系統(tǒng)的主題架構(gòu),F(xiàn)eed流系統(tǒng)算是完成了。但是Feed流產(chǎn)品上還未結(jié)束,對(duì)于所有的feed流產(chǎn)品都需要有搜索能力,比如下面場(chǎng)景:
- 微博中的搜索用戶(hù)。
- 搜索微博內(nèi)容。
- 微信中搜索好友等。
這些內(nèi)容搜索只需要字符匹配到即可,不需要非常復(fù)雜的相關(guān)性算法,所以只需要有能支持分詞的檢索功能即可,所以一般有兩種做法:
使用搜索引擎,將存儲(chǔ)庫(kù)的內(nèi)容和用戶(hù)信息表內(nèi)容推送給搜索系統(tǒng),搜索的時(shí)候直接訪問(wèn)搜索系統(tǒng)。使用具備全文檢索能力的數(shù)據(jù)庫(kù),比如最新版的MySQL、MongoDB或者Tablestore。
所以,選擇的原則如下:
- 如果存儲(chǔ)庫(kù)使用了MySQL或者Tablestore,那么直接選擇這兩個(gè)系統(tǒng)就可以了。
- 如果整個(gè)系統(tǒng)都沒(méi)使用MySQL、Tablestore,且已經(jīng)使用了搜索系統(tǒng),那么可以直接復(fù)用搜索系統(tǒng),其他場(chǎng)景都不應(yīng)該再額外加一個(gè)搜索系統(tǒng)進(jìn)來(lái),徒添復(fù)雜度。
如果使用Tablestore,那么只需要在相應(yīng)表上建立多元索引即可:
- 如果需要對(duì)用戶(hù)名支持搜索,那么需要對(duì)user_table建立多元索引,其中的nick_name需要是Text類(lèi)型,且單字分詞。
- 如果需要對(duì)Feed流內(nèi)容支持搜索,那么需要對(duì)存儲(chǔ)庫(kù)表:store_table建立多元索引,這樣就能直接對(duì)Feed流內(nèi)容進(jìn)行各種復(fù)雜查詢(xún)了,包括多條件篩選、全文檢索等。
系統(tǒng)架構(gòu)中加了搜索功能后的架構(gòu)如下:
8. 排序
目前的Feed流系統(tǒng)中的排序方式有兩種,一種是時(shí)間,一種是分?jǐn)?shù)。
我們常用的微博、朋友圈、私信這些都是時(shí)間線類(lèi)型的,因?yàn)檫@些產(chǎn)品定義中,需要我們主動(dòng)關(guān)注某些人后才會(huì)看到這些人發(fā)表的內(nèi)容,這個(gè)時(shí)候,最重要的是實(shí)時(shí)性,而不是發(fā)布質(zhì)量,就算關(guān)注人發(fā)布了一條垃圾信息,我們也會(huì)被動(dòng)看到。這種類(lèi)型的產(chǎn)品適用于按照時(shí)間線排序。這一篇我們介紹的架構(gòu)都是基于時(shí)間類(lèi)型的。
另外一種是不需要關(guān)注任何人,我們能看到的都是系統(tǒng)希望我們看到的,系統(tǒng)在后臺(tái)會(huì)分析我們的每個(gè)人的愛(ài)好,然后給每個(gè)人推送差異化的、各自喜歡的內(nèi)容,這一種的架構(gòu)和基于時(shí)間的完全不一樣,我們?cè)诤罄m(xù)的推薦類(lèi)型中專(zhuān)門(mén)介紹。
9. 刪除Feed內(nèi)容
在Feed流應(yīng)用中有一個(gè)問(wèn)題,就是如果用戶(hù)刪除了之前發(fā)表的內(nèi)容,系統(tǒng)該如何處理?因?yàn)橄到y(tǒng)里面有寫(xiě)擴(kuò)散,那么刪除的時(shí)候是不是也要寫(xiě)擴(kuò)散一遍?這樣的話,刪除就不及時(shí)了,很難應(yīng)對(duì)法律法規(guī)要求的快速刪除。
針對(duì)這個(gè)問(wèn)題,我們?cè)谥霸O(shè)計(jì)的時(shí)候,同步表中只有消息ID,沒(méi)有消息內(nèi)容,在用戶(hù)讀取的時(shí)候需要到存儲(chǔ)庫(kù)中去讀消息內(nèi)容,那么我們可以直接刪除存儲(chǔ)庫(kù)中的這一條消息,這樣用戶(hù)讀取的時(shí)候使用消息ID是讀不到數(shù)據(jù)的,也就相當(dāng)于刪除的內(nèi)容,而且刪除速度會(huì)很快。除了直接刪除外,另外一種辦法是邏輯刪除,對(duì)于刪除的feed內(nèi)容,只做標(biāo)記,當(dāng)查詢(xún)到帶有標(biāo)記的數(shù)據(jù)時(shí)就認(rèn)為刪除了。
10. 更新Feed內(nèi)容
更新和刪除Feed處理邏輯一樣,如果使用了支持多版本的存儲(chǔ)系統(tǒng),比如Tablestore,那么也可以支持編輯版本,和現(xiàn)在的微博一樣。
11. 總結(jié)
上面介紹了不同子功能的特點(diǎn)和系統(tǒng)要求,能滿(mǎn)足需求的系統(tǒng)主要有兩類(lèi),一類(lèi)是阿里云的Tablestore單系統(tǒng),一類(lèi)是開(kāi)源組件組成的組合系統(tǒng)。
-
開(kāi)源組件組成的組合系統(tǒng):包括MySQL、Redis、HBase等,這些系統(tǒng)單個(gè)都不能解決Feed流系統(tǒng)中遇到的問(wèn)題,需要組合在一起,各司其職才能完成一個(gè)Feed流系統(tǒng),適用于熱衷開(kāi)源系統(tǒng),人多且喜歡運(yùn)維操作的團(tuán)隊(duì)。
-
Tablestore單系統(tǒng):只使用Tablestore單個(gè)系統(tǒng)就能解決上述的所有問(wèn)題,這時(shí)候肯定有人要問(wèn)?你是不是在吹牛?這里不是吹牛,Tablestore在三年前就已經(jīng)開(kāi)始重視Feed流類(lèi)型業(yè)務(wù),之前也發(fā)表過(guò)多篇文章介紹,功能上也在專(zhuān)門(mén)為Feed流系統(tǒng)特別定制設(shè)計(jì),所以到今天,只使用Tablestore一款產(chǎn)品,是可以滿(mǎn)足上述需求的。選擇Tablestore做Feed流系統(tǒng)的用戶(hù)具有以下一些特征:
- 產(chǎn)品設(shè)計(jì)目標(biāo)規(guī)模大,千萬(wàn)級(jí)或億級(jí)。
- 不喜歡運(yùn)維,喜歡專(zhuān)注于開(kāi)發(fā)。
- 高效率團(tuán)隊(duì),希望盡快將產(chǎn)品實(shí)現(xiàn)落地。
- 希望一勞永逸,未來(lái)系統(tǒng)隨著用戶(hù)規(guī)模增長(zhǎng)可以自動(dòng)擴(kuò)容。
- 希望能按量付費(fèi),用戶(hù)少的時(shí)候費(fèi)用低,等用戶(hù)增長(zhǎng)起來(lái)后費(fèi)用在跟隨用戶(hù)數(shù)增長(zhǎng)。如果具有上述四個(gè)特征的任何一個(gè),那么都是適合于用Tablestore。
架構(gòu)實(shí)踐
上面我們介紹了Feed流系統(tǒng)的設(shè)計(jì)理論,具體到不同的類(lèi)型中,會(huì)有不同的側(cè)重點(diǎn),下面會(huì)逐一介紹。
朋友圈
朋友圈是一種典型的Feed流系統(tǒng),關(guān)系是雙寫(xiě)關(guān)系,關(guān)系有上限,排序按照時(shí)間,如果有個(gè)人持續(xù)產(chǎn)生垃圾內(nèi)容,那就只能屏蔽掉TA,這一種類(lèi)型就是典型的寫(xiě)擴(kuò)散模型。
微博
微博也是一種非常典型的Feed流系統(tǒng),但不同于朋友圈,關(guān)系是單向的,那么也就會(huì)產(chǎn)生大V,這個(gè)時(shí)候就需要讀寫(xiě)擴(kuò)散模式,用讀擴(kuò)散解決大V問(wèn)題。同時(shí),微博也是主動(dòng)關(guān)注類(lèi)型的產(chǎn)品,所以排序也只能是時(shí)間,如果按照推薦排序,那么效果就會(huì)比較差。
頭條
頭條是最近幾年快速崛起的一款應(yīng)用,在原有微博的Feed流系統(tǒng)上產(chǎn)生了進(jìn)化,用戶(hù)不需要主動(dòng)關(guān)注其他人,只要初始瀏覽一些內(nèi)容后,系統(tǒng)就會(huì)自動(dòng)判斷出你的喜好,然后后面再根據(jù)你的喜好給你推薦你可能會(huì)喜好的內(nèi)容,訓(xùn)練時(shí)間長(zhǎng)了后,推送的內(nèi)容都會(huì)是你最喜歡看的。
私信
私信也算是一種簡(jiǎn)單的Feed流系統(tǒng),或者也可以認(rèn)為是一種變相的IM,都是單對(duì)單的,沒(méi)有群。
總結(jié)
上面我們介紹了Feed流系統(tǒng)的整體框架,主要是產(chǎn)品定義、同步、存儲(chǔ)、元數(shù)據(jù)、評(píng)論、贊、排序和搜索等內(nèi)容。
- END -本文摘自 :https://blog.51cto.com/u