如何設計一個微博 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)方案

  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 被很多很多用戶關注的時候,遍歷進行粉絲進行插入數據非常耗時,用戶不能及時收到內容

可嘗試的解決方法:

  1. 可將任務推入消息隊列中,消費端多線程並行消費。
  2. 使用插入性能高、數據壓縮率高的數據庫
2. 存儲成本很高

每個粉絲都要存儲一份關注人的微博數據,大 V 粉絲量很高的時候,插入數據量成指數級上升。

並且微博可以將關注的博主進行分組,所以數據不僅要在全部收件箱中插入,也要在分組的收件箱中插入。

可嘗試的解決方法:

數據冷熱分離,熱庫僅保存短時間內的數據,冷庫多保留一段時間的數據,冷熱庫均定時清理數據。

用戶量不斷上漲,使用這種設計方案,終究還是會遇到瓶頸

3. 數據狀態同步

當被關注用戶刪除微博或取關某博主時,需要將所有粉絲的收件箱中的內容都刪除,依然存在一個寫擴散的即時性問題

可嘗試的解決方案:

在拉取數據的時候對微博的狀態進行判斷,過濾已刪除/已取關的微博過濾

以上解決方案可以在一定程度上提升效率,但是不能根源上解決問題。

3)小結

推模式僅適用於粉絲量不會太多的情況,例如微信朋友圈,這樣能夠比較好的控制好即時觸達性、以及數據存儲的成本。

對於微博大 V 這種粉絲量很大的場景並不適合。

4. 使用拉模式

拉模式也稱讀擴散模式,當我們使用拉數據的方式後,用戶獲取數據流程如下:

  1. 獲取所有關注的博主 ID。
select liker from t_like where userId = 1;
  1. 根據博主ID進行內容拉取。
select * from t_feed where userId in (4,5,6) and recordStatus = 0;
  1. 獲取所有內容後根據 timeline 進行排序。

這樣的方案解決了在推模式下存在的三個問題,但是卻也引發了另外的性能問題。

假如,用戶關注的博主非常多,要拉取所有內容並進行排序聚合,這樣的操作必定會耗時很多,請求時延很高。

那麼如何做到低耗時,完成快速響應呢?

單純依靠數據庫是無法達到要求的,所以我們要在中間引入緩存層(分片),通過緩存來降低磁盤 IO。

1)流程爲:

  1. 關注列表緩存

將用戶關注的所有博主 ID 存入緩存中。以用戶 ID 爲 key,value 爲關注博主 id 集合

  1. 微博內容緩存

以博主 ID 爲 key,value 爲微博內容集合。博主發佈微博後,將微博內容存入集合中

  1. 獲取 feed 流時

根據關注的博主 id 集合,在所有緩存分片節點上拉取所有內容並進行排序聚合。

假如緩存分片集羣爲三主三從,也就是一共需要三次請求即可拉取到所有內容,然後進行時間倒排,響應給用戶

2)存在的問題

  1. 系統的讀壓力很大

假如用戶關注了 1000 個博主,那麼需要拉取這 1000 個博主的所有發佈內容,進行排序聚合,對於緩存服務,以及帶寬壓力都很大。

可嘗試的解決方案:

緩存節點一主多從,通過水平擴容,來分散讀壓力和帶寬瓶頸

對於大 V 用戶,拉模式能夠很好解決寫擴散存在的問題,同時也會帶來上述存在的問題。

三. 總結

分析完推模式和拉模式的優缺點,我們很容易發現

  1. 推模式適合於粉絲量不大的場景。例如朋友圈,一對一聊天。

  2. 拉模式適合粉絲量巨大的大 V 用戶。例如微博大 V。

所以在場景設計時,可以將推模式和拉模式結合使用。邏輯如下

  1. 設定一個大 V 粉絲量閾值,達到閾值後觸發打用戶標籤事件。

  2. 對於未達到閾值的用戶依然使用寫擴散方式,這樣冗餘的數據量不會太大,也不存在即時性問題。

  3. 當達到閾值的用戶發微博的時候,將微博內容存入緩存(熱數據),不進行寫擴散,而是粉絲拉取數據與收件箱中的數據進行排序聚合。

PS:這裏還可以通過用戶行爲去維護一個活躍粉絲列表,對於該列表中的粉絲,同樣進行一個寫擴散的行爲,保證即時觸達。

作者:蓋茨狗

來源:https://juejin.cn/post/7025208419875291166

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/W5LypZhFTvGtuRSYTpZONw