B 站千億級點贊系統服務架構設計
本期作者
蘆文超
嗶哩嗶哩資深開發工程師
一、前言
1、什麼是點贊
點贊相信大家都不陌生,在 B 站,點贊是 UP 主們跟粉絲之間的特殊羈絆。B 站的點贊系統提供了對視頻、動態、專欄、評論、彈幕等多種實體維度的點贊、點踩以及其餘配套的數據查詢的能力。
- 稿件點贊(類推動態、專欄、評論等也具備該功能)
- 總獲贊數
- 點贊記錄
2、點贊服務需要提供哪些系統能力
業務能力:
以 “稿件” 爲例,點贊服務需要提供
-
對某個稿件點贊(取消點贊)、點踩(取消點踩)
-
查詢是否對 單個 或者 一批稿件 點過贊(踩) - 即點贊狀態查詢
-
查詢某個稿件的點贊數
-
查詢某個用戶的點贊列表
-
查詢某個稿件的點贊人列表
-
查詢用戶收到的總點贊數
平臺能力:
點贊作爲一個與社區實體共存的服務,中臺化、平臺化也是點贊服務需要具備的能力
-
提供業務快速接入的能力(配置級別)
-
數據存儲上(緩存、DB),具備數據隔離存儲的能力(多租戶)
容災能力:
作爲被用戶強感知的站內功能,需要考慮到各種情況下的系統容災。例如當:
存儲不可用
-
例如當 DB 不可用時,需要依託緩存儘可能提供服務。
-
同理當緩存不可用時,DB 也需要保證自己不宕機的情況下儘可能提供服務。
消息隊列不可用
- 當消息隊列不可用時,依託 B 站自研的 railgun,通過 RPC 調用的方式自動降級
機房災難
- 切換機房
數據同步延遲
- 比如點贊就遇到過因爲 Tidb 的數據同步問題(斷流、延遲)導致的點贊計數同步延遲問題。
當然還有其他比如 下游依賴宕機、點贊消息堆積 以及其他未知問題
3、系統需要承載哪些壓力
1. 流量壓力
全局流量壓力:
全站點贊狀態查詢、點贊數查詢等【讀流量】超過 300k,點贊、點踩等【寫流量】超過 15K
-
針對寫流量,爲了保證數據寫入性能,我們在寫入【點贊數】數據的時候,在內存中做了部分聚合寫入,比如聚合 10s 內的點贊數,一次性寫入。如此可大量減少數據庫的 IO 次數。
-
同時數據庫的寫入我們也做了全面的異步化處理,保證了數據庫能以合理的速率處理寫入請求。
-
另外爲了保證點贊狀態的正確性,且同時讓程序擁有自我糾錯的能力,我們在每一次更新點贊狀態之前,都會取出老的點贊狀態作爲更新依據。例如:如果當前是取消點贊,那麼需要數據庫中已有點贊狀態。
單點流量(熱點)壓力:
熱門事件、稿件等帶來的系統熱點問題,包括 DB 熱點、緩存熱點
- 當一個稿件成爲超級熱門的時候,大量的流量就會湧入到存儲的單個分片上,造成讀寫熱點問題。此時需要有熱點識別機制來識別該熱點,並將數據緩存至本地,並設置合理的 TTL。例如,UP 主 【傑威爾音樂】發佈第一個稿件的時候就是一次典型熱點事件。
2. 數據存儲壓力
-
點贊數據存儲超過千億級別
-
如何高效的利用存儲介質存放這些數據才能既滿足業務需求,也能最大程度節省成本,也是一個點贊服務正在努力改造的工程 - KV 化存儲
3. 面對未知災難
- DB 宕機、Redis 集羣抖動、機房故障、網絡故障等
針對前言中提到的點讚的平臺能力、系統壓力與容災,我會在下文中作出更加詳細的介紹。
二、點贊整體系統架構簡介
爲了在提供上述能力的前提下經受住流量、存儲、容災三大壓力,點贊目前的系統實現方式如下:
1、系統架構簡介
整個點贊服務的系統可以分爲五個部分
1、流量路由層(決定流量應該去往哪個機房)
2、業務網關層(統一鑑權、反黑灰產等統一流量篩選)
3、點贊服務(thumbup-service), 提供統一的 RPC 接口
4、點贊異步任務(thumbup-job)
5、數據層(db、kv、redis)
下文將重點分享下 數據存儲層、點贊服務層(thumbup-service) 與 異步任務層(thumbup-job) 的系統設計
2、三級數據存儲
基本數據模型:
-
點贊記錄表:記錄用戶在什麼時間對什麼實體進行了什麼類型的操作 (是贊還是踩,是取消點贊還是取消點踩) 等
-
點贊計數表:記錄被點贊實體的累計點贊(踩)數量
①、第一層存儲:DB 層 - (TiDB)
點贊系統中最爲重要的就是點贊記錄表(likes)和點贊計數表(counts),負責整體數據的持久化保存,以及提供緩存失效時的回源查詢能力。
-
點贊記錄表 - likes : 每一次的點贊記錄(用戶 Mid、被點讚的實體 ID(messageID)、點贊來源、時間)等信息,並且在 Mid、messageID 兩個維度上建立了滿足業務求的聯合索引。
-
點贊數表 - counts : 以業務 ID(BusinessID)+ 實體 ID(messageID) 爲主鍵,聚合了該實體的點贊數、點踩數等信息。並且按照 messageID 維度建立滿足業務查詢的索引。
-
由於 DB 採用的是分佈式數據庫 TiDB,所以對業務上無需考慮分庫分表的操作
②、第二層存儲
緩存層 Cache:點贊作爲一個高流量的服務,緩存的設立肯定是必不可少的。點贊系統主要使用的是 CacheAside 模式。這一層緩存主要基於 Redis 緩存:以點贊數和用戶點贊列表爲例
- 點贊數
key-value = count:patten:{business_id}:{message_id} - {likes},{disLikes}
用業務ID和該業務下的實體ID作爲緩存的Key,並將點贊數與點踩數拼接起來存儲以及更新
- 用戶點贊列表
key-value = user:likes:patten:{mid}:{business_id} - member(messageID)-score(likeTimestamp)
* 用mid與業務ID作爲key,value則是一個ZSet,member爲被點讚的實體ID,score爲點讚的時間。當改業務下某用戶有新的點贊操作的時候,被點讚的實體則會通過 zadd的方式把最新的點贊記錄加入到該ZSet裏面來
爲了維持用戶點贊列表的長度(不至於無限擴張),需要在每一次加入新的點贊記錄的時候,按照固定長度裁剪用戶的點贊記錄緩存。該設計也就代表用戶的點贊記錄在緩存中是有限制長度的,超過該長度的數據請求需要回源DB查詢
③、第三層存儲
LocalCache - 本地緩存
-
本地緩存的建立,目的是爲了應對緩存熱點問題。
-
利用最小堆算法,在可配置的時間窗口範圍內,統計出訪問最頻繁的緩存 Key, 並將熱 Key(Value)按照業務可接受的 TTL 存儲在本地內存中。
-
其中熱點的發現之前也有同步過:https://mp.weixin.qq.com/s/C8CI-1DDiQ4BC_LaMaeDBg
④、針對 TIDB 海量歷史數據的遷移歸檔
遷移歸檔的原因 (初衷),是爲了減少 TIDB 的存儲容量, 節約成本的同時也多了一層存儲,可以作爲災備數據。
以下是在 KV 數據庫(taishan)中點讚的數據與索引的組織形式:
- 點贊記錄
1_{mid}_${business_id}_${type}_${message_id} => {origin_id}_{mtime}
- 用戶點贊列表索引
2_{mid}_${business_id}_${type}_${mtime}_{message_id} => {origin_id}
- 實體維度點贊記錄索引
3_{message_id}_${business_id}_${type}_${mtime}_${mid}=>{origin_id}
3、存儲層的優化和思考
作爲一個典型的大流量基礎服務,點讚的存儲架構需要最大程度上滿足兩個點
①、滿足業務讀寫需求的同時具備最大的可靠性
②、選擇合適的存儲介質與數據存儲形態,最小化存儲成本
從以上兩點觸發,考慮到 KV 數據在業務查詢以及性能上都更契合點讚的業務形態,且 TaiShan 可以水平擴容來滿足業務的增長。點贊服務從當前的關係型數據庫(TiDB)+ 緩存(Redis)逐漸過渡至 KV 型數據庫(Taishan)+ 緩存(Redis),以具備更強的可靠性。
同時 TaiShan 作爲公司自研的 KV 數據庫,在成本上也能更優於使用 TiDB 存儲。
4、點贊服務層(thumbup-service)
作爲面對 C 端流量的直接接口,在提供服務的同時,需要思考在面對各種未知或者可預知的災難時,如何儘可能提供服務
- 存儲(db、redis 等)的容災設計(同城多活)
在 DB 的設計上,點贊服務有兩地機房互爲災備,正常情況下,機房 1 承載所有寫流量與部分讀流量,機房 2 承載部分讀流量。當 DB 發生故障時,通過 db-proxy(sidercar)的切換可以將讀寫流量切換至備份機房繼續提供服務。
在緩存(Redis)上,點贊服務也擁有兩套處於不同機房的集羣,並且通過異步任務消費 TiDB 的 binLog 維護兩地緩存的一致性。可以在需要時切換機房來保證服務的提供,而不會導致大量的冷數據回源數據庫。
服務的容災與降級
(以點贊數、點贊狀態、點贊列表爲例),點贊作爲一個用戶強交互的社區功能服務,對於災難發生時用戶體驗的保證是放在第一位的。所以針對重點接口,我們都會有兜底的數據作爲返回。
多層數據存儲互爲災備
-
點讚的熱數據在 redis 緩存中存有一份。
-
kv 數據庫中存有全量的用戶數據,當緩存不可用時,KV 數據庫會扛起用戶的所有流量來提供服務。
-
TIDB 目前也存儲有全量的用戶數據,當緩存、KV 均不可用時,tidb 會依託於限流,最大程度提供用戶數據的讀寫服務。
-
因爲存在多重存儲,所以一致性也是業務需要衡量的點。
-
首先寫入到每一個存儲都是有錯誤重試機制的,且重要的環節,比如點贊記錄等是無限重試的。
-
另外,在擁有重試機制的場景下,極少數的不同存儲的數據不一致在點讚的業務場景下是可以被接受的
-
多地方機房互爲災備
-
點贊機房、緩存、數據庫等都在不同機房有備份數據,可以在某一機房或者某地中間件發生故障時快速切換。
-
點贊重點接口的降級
-
點贊數、點贊、列表查詢、點贊狀態查詢等接口,在所有兜底、降級能力都已失效的前提下也不會直接返回錯誤給用戶,而是會以空值或者假特效的方式與用戶交互。後續等服務恢復時,再將故障期間的數據寫回存儲。
5、異步任務層(thumbup-job)
- 異步任務主要作爲點贊數據寫入、刷新緩存、爲下游其他服務發送點贊、點贊數消息等功能
首先是最重要的用戶行爲數據(點贊、點踩、取消等)的寫入。搭配對數據庫的限流組件以及消費速度監控,保證數據的寫入不超過數據庫的負荷的同時也不會出現數據堆積造成的 C 數據端查詢延遲問題。
-
緩存刷新:點贊狀態緩存、點贊列表緩存、點贊計數緩存
-
同步點贊消息
-
點贊事件異步消息、點贊計數異步消息
-
針對 WriteBack 方式的思考
-
由於目前點贊系統異步處理能力或者說速率是能夠滿足業務的。所以當前寫 DB 與寫緩存都放在異步流程中。
-
後續隨着流量的增加,實施流程中寫緩存,再由異步 Job 寫入持久層相對來說是一個更好的方案。
點贊 job 對 binLog 的容災設計
由於點讚的存儲爲 TiDB, 且數據量較大。在實際生產情況中,binLog 會偶遇數據延遲甚至是斷流的問題。爲了減少 binLog 數據延遲對服務數據的影響。服務做了以下改造。
- 監控:
首先在運維層面、代碼層面都對 binLog 的實時性、是否斷流做了監控
- 應對
脫離 binlog,由業務層(thumb-service)發送重要的數據信息(點贊數變更、點贊狀態事件)等。當發生數據延遲時,程序會自動同時消費由 thumbup-service 發送的容災消息,繼續向下遊發送。
三、未來規劃
1、點贊服務單元化:要陸續往服務單元化的方向迭代、演進。
2、點贊服務平臺化:在目前的業務接入基礎上增加迭代數據分庫存儲能力,做到服務、數據自定義隔離。
3、點贊業務形態探索:以社區爲基礎,繼續探索通過點贊衍生的業務形態。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/4T_S7nR8-HXJ59IbK4FBWQ