如何設計一個微博 feed 流
一. 背景
微博,微信朋友圈,抖音等都是典型的 feed 流產品,也就是我們的瀏覽內容都是由他人發的 feed 組成。
本篇文章嘗試進行微博 feed 流的設計解析,如有問題歡迎大家指正。
二. 如何設計一個微博 feed 流
1. 存儲設計
在數據存儲上主要分三個部分
1)feed 存儲
是用戶發佈的內容存儲,這部分內容需要永久存儲,用戶在查看個人主頁的時候不論多久的都要可以看到
數據結構簡化如下,根據 userId 進行水平分表
create table `t_feed`(
`feedId` bigint not null PRIMARY KEY,
`userId` bigint not null COMMENT '創建人ID'
`content` text,
`recordStatus` tinyint not null default 0 comment '記錄狀態'
)ENGINE=InnoDB;
2)關注關係存儲
是用戶之間關係的一個存儲,也是控制用戶能夠看到 feed 範圍的依賴,同樣需要永久存儲。
數據結構簡化如下(待優化)根據 userId 進行水平分表:
CREATE TABLE `t_like`(
`id` int(11) NOT NULL PRIMARY KEY,
`userId` int(11) NOT NULL,
`likerId` int(11) NOT NULL,
KEY `userId` (`userId`),
KEY `userId` (`likerId`),
)ENGINE=InnoDB;
3)feed 同步存儲
用於 feed 流展示,可以理解爲是一個收件箱,關注的人發佈了 feed,就要向其中投遞。
可以根據業務場景保存一段時間內的內容,冷的數據可以進行歸檔也可以直接刪除。
數據結構簡化如下,根據 userId 進行水平分表:
create table `t_inbox`(
`id` bigint not null PRIMARY KEY,
`userId` bigint not null comment '收件人ID',
`feedId` bigint not null comment '內容ID',
`createTime` datetime not null
)ENGINE=InnoDB;
2. 場景特點
1) 讀多寫少
讀寫比例差距巨大,典型的讀多寫少場景。
2) 有序展示
需要根據 timeline 或者 feed 的打分值來進行排序處理展示。
3. 使用推模式實現
推模式也稱寫擴散模式,當被關注人發佈內容後,主動將內容推送給關注,寫入關注人的收件箱中。
1)方案
- 當被關注人發佈一條內容以後,獲取所有關注該人的用戶,然後進行遍歷數據,將內容插入這些用戶的收件箱中,示例如下:
/** 插入一條feed數據 **/
insert into t_feed (`feedId`,`userId`,`content`,`createTime`) values (10001,4,'內容','2021-10-31 17:00:00');
/** 查詢所有粉絲 **/
select userId from t_like where liker = 4;
/** 將feed插入粉絲的收件箱中 **/
insert into t_inbox (`userId`,`feedId`,`createTime`) values (1,10001,'2021-10-31 17:00:00');
insert into t_inbox (`userId`,`feedId`,`createTime`) values (2,10001,'2021-10-31 17:00:00');
insert into t_inbox (`userId`,`feedId`,`createTime`) values (3,10001,'2021-10-31 17:00:00');
2、當用戶 ID 爲 1 的用戶進行查看 feed 流時,就將收件箱表中的所有數據進行查出,示例如下:
select feedId from t_inbox where userId = 1 ;
3、對數據進行聚合排序處理
2)存在的問題
1. 即時性較差
當大 V 被很多很多用戶關注的時候,遍歷進行粉絲進行插入數據非常耗時,用戶不能及時收到內容
可嘗試的解決方法:
- 可將任務推入消息隊列中,消費端多線程並行消費。
- 使用插入性能高、數據壓縮率高的數據庫
2. 存儲成本很高
每個粉絲都要存儲一份關注人的微博數據,大 V 粉絲量很高的時候,插入數據量成指數級上升。
並且微博可以將關注的博主進行分組,所以數據不僅要在全部收件箱中插入,也要在分組的收件箱中插入。
可嘗試的解決方法:
數據冷熱分離,熱庫僅保存短時間內的數據,冷庫多保留一段時間的數據,冷熱庫均定時清理數據。
用戶量不斷上漲,使用這種設計方案,終究還是會遇到瓶頸
3. 數據狀態同步
當被關注用戶刪除微博或取關某博主時,需要將所有粉絲的收件箱中的內容都刪除,依然存在一個寫擴散的即時性問題
可嘗試的解決方案:
在拉取數據的時候對微博的狀態進行判斷,過濾已刪除/已取關的微博過濾
以上解決方案可以在一定程度上提升效率,但是不能根源上解決問題。
3)小結
推模式僅適用於粉絲量不會太多的情況,例如微信朋友圈,這樣能夠比較好的控制好即時觸達性、以及數據存儲的成本。
對於微博大 V 這種粉絲量很大的場景並不適合。
4. 使用拉模式
拉模式也稱讀擴散模式,當我們使用拉數據的方式後,用戶獲取數據流程如下:
- 獲取所有關注的博主 ID。
select liker from t_like where userId = 1;
- 根據博主ID進行內容拉取。
select * from t_feed where userId in (4,5,6) and recordStatus = 0;
- 獲取所有內容後根據 timeline 進行排序。
這樣的方案解決了在推模式下存在的三個問題,但是卻也引發了另外的性能問題。
假如,用戶關注的博主非常多,要拉取所有內容並進行排序聚合,這樣的操作必定會耗時很多,請求時延很高。
那麼如何做到低耗時,完成快速響應呢?
單純依靠數據庫是無法達到要求的,所以我們要在中間引入緩存層(分片),通過緩存來降低磁盤 IO。
1)流程爲:
- 關注列表緩存
將用戶關注的所有博主 ID 存入緩存中。以用戶 ID 爲 key,value 爲關注博主 id 集合
- 微博內容緩存
以博主 ID 爲 key,value 爲微博內容集合。博主發佈微博後,將微博內容存入集合中
- 獲取 feed 流時
根據關注的博主 id 集合,在所有緩存分片節點上拉取所有內容並進行排序聚合。
假如緩存分片集羣爲三主三從,也就是一共需要三次請求即可拉取到所有內容,然後進行時間倒排,響應給用戶
2)存在的問題
- 系統的讀壓力很大
假如用戶關注了 1000 個博主,那麼需要拉取這 1000 個博主的所有發佈內容,進行排序聚合,對於緩存服務,以及帶寬壓力都很大。
可嘗試的解決方案:
緩存節點一主多從,通過水平擴容,來分散讀壓力和帶寬瓶頸
對於大 V 用戶,拉模式能夠很好解決寫擴散存在的問題,同時也會帶來上述存在的問題。
三. 總結
分析完推模式和拉模式的優缺點,我們很容易發現
-
推模式適合於粉絲量不大的場景。例如朋友圈,一對一聊天。
-
拉模式適合粉絲量巨大的大 V 用戶。例如微博大 V。
所以在場景設計時,可以將推模式和拉模式結合使用。邏輯如下
-
設定一個大 V 粉絲量閾值,達到閾值後觸發打用戶標籤事件。
-
對於未達到閾值的用戶依然使用寫擴散方式,這樣冗餘的數據量不會太大,也不存在即時性問題。
-
當達到閾值的用戶發微博的時候,將微博內容存入緩存(熱數據),不進行寫擴散,而是粉絲拉取數據與收件箱中的數據進行排序聚合。
PS:這裏還可以通過用戶行爲去維護一個活躍粉絲列表,對於該列表中的粉絲,同樣進行一個寫擴散的行爲,保證即時觸達。
作者:蓋茨狗
來源:https://juejin.cn/post/7025208419875291166
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/W5LypZhFTvGtuRSYTpZONw