B 站評論系統的多級存儲架構
1. 背景
評論是 B 站生態的重要組成部分,涵蓋了 UP 主與用戶的互動、平臺內容的推薦與優化、社區文化建設以及用戶情感滿足。B 站的評論區不僅是用戶互動的核心場所,也是平臺運營和用戶粘性的關鍵因素之一,尤其是在與彈幕結合的情況下,成爲平臺的標誌性特色。
在社會熱點事件發生時,評論區的讀寫流量會急劇增加,直接影響業務運行,對用戶體驗、內容創作和社區文化等多個方面產生負面影響,所以評論服務的穩定性至關重要。
評論系統對緩存命中率要求非常高,一旦發生緩存失效,大量請求會直接訪問 TiDB,如果 TiDB 出現問題,將導致評論服務不可用。所以評論需要構建一套可靠的容災系統,並具備自動降級能力,以提升評論服務的整體穩定性。
2. 架構設計
評論系統架構主要依賴 Redis 緩存和 TiDB 存儲,列表接口中依賴多種排序索引,如點贊序、時間序、熱度序等,這些索引通過 Redis 的 Sorted Set 數據結構進行存儲。
在大評論區中查詢這些排序索引,當 Redis 緩存 miss 時需要回源 TiDB 查詢,因數據量過大、查詢耗時較長,慢查詢會佔用大量 CPU 和內存,進而導致其他查詢的延遲或阻塞,嚴重影響整個 TiDB 的吞吐量和性能。例如查詢點贊序排序索引:
SELECT id FROM reply WHERE ... ORDER BY like_count DESC LIMIT m,n
爲了避免 TiDB 故障導致評論服務不可用,我們希望建立一套新的存儲系統,解決 TiDB 單點故障問題。該系統不僅爲業務提供容災能力和自動降級通道,還能在大流量查詢場景下提供更優的查詢性能,從而提升整體評論服務的穩定性。
基於 B 站自研的泰山 KV 存儲(Taishan),我們搭建了「多級存儲架構」,整體設計方案的核心思路包括:
-
將排序索引的存儲從「結構化」轉爲「非結構化」
-
將排序索引查詢從「SQL」轉換爲性能更高的「NoSQL」
-
通過「寫場景的複雜度」來換取「更優的讀場景性能」
3. 存儲設計
存儲模型
在評論的業務場景中,我們抽象了兩種數據模型:排序索引(Index)、評論物料(KV)
下圖以按點贊序排序的前 10 條評論爲例,展示了使用 Index + KV 模型實現的具體思路。在更復雜的推薦排序場景中,依然可以通過此模型來實現。具體流程是:首先通過排序索引召回一批評論 ID,再通過推薦算法對這些 ID 進行重排,最終根據重排後的 ID 獲取評論詳情並返回給用戶。
將領域對象的存儲建模劃分爲 Index 和 KV 兩種模型,可以利用不同的底層存儲結構來分別優化查詢、掃描、排序、分頁等場景。使用 Redis 或 Memcache 作爲緩存構建 KV 模型,提升查詢性能,使用 Redis 的 Sorted Set 構建 Index 模型,支持增量數據實時更新排序索引,並提供極高效的分頁查詢性能。在關鍵詞搜索場景,可採用 ElasticSearch 作爲檢索索引,避免在原始數據庫上進行低效的遍歷操作;
基於 KV 作爲唯一事實表,採用同步全量數據和實時捕獲增量數據的方法,將原始數據轉換爲下游索引表。這樣可以靈活構建定製化的排序索引,以應對多變的評論業務需求。同時該方案在不影響原有業務邏輯和存儲資源的情況下,實現了業務、代碼和數據的解耦。
如果索引的定義和實現不再侷限於源數據庫的原生索引,而是擴展到應用邏輯,並在其他存儲上自行維護物化視圖,這必然會帶來額外的理解和維護成本,同時引入一致性的難題。然而考慮到評論業務的數據量級和複雜度,該方案的整體優勢仍然大於劣勢。所以我們需要新的存儲方案,既支持基本的 Index 和 KV 模型,又能滿足高性能、可用性和擴展性等方面的需求。
數據類型
我們期望將數據類型從 SQL 轉向 NoSQL,因爲 NoSQL 提供了更靈活的數據模型,意味着更可解釋的執行計劃和更高的優化潛力。例如 Taishan 查詢 Redis Sorted Set 的 P999 耗時約 10ms,查詢 KV 的 P999 耗時約 5ms,這種高效的查詢性能對評論業務尤爲重要。
相比 TiDB,Taishan 不支持 ACID 事務、二級索引等功能,提供的能力更爲精簡。基於之前的經驗和問題,有時候 “less is more” 反而能帶來更高的可用性。以下是評論業務在使用 TiDB 時遇到的一些問題:
-
MVCC 機制: TiDB 的事務實現基於 MVCC 機制,當新寫入的數據覆蓋舊數據時,舊數據不會被刪除,而是以時間戳區分多個版本,並通過定期 GC 清理不再需要的數據。在熱門評論區中,頻繁更新點贊數時,排序索引的 MVCC 歷史版本過多,導致 TiDB 的讀寫性能下降。相比之下,不支持 MVCC 的 Taishan 在查詢排序索引時能提供更高效、更穩定的性能;
-
分片策略: TiDB 不直接支持完全自定義的分片策略,而 Taishan 支持哈希標籤(hash tags),可以在 Key 中使用大括號 { } 指定參與哈希計算的部分。這樣多個 Key 可以使用相同的 ID 進行分區路由,確保同一評論區的評論位於同一分片。在批量查詢評論時,這能大幅降低扇出度,減少長尾耗時的影響。而對於可能出現熱點的場景,Taishan 可以選擇不使用哈希標籤,從而打散請求,爲性能要求高的評論業務提供更大的優化空間。
基於現有評論在 TiDB 中的存儲結構和索引設計,以時間序和點贊序爲例,列舉 Taishan 的數據模型如下:
4. 數據一致性
從 TiDB 的結構化數據轉變爲 Taishan 的非結構化數據,目前缺乏現成的同步工具,需要業務自行實現數據同步。然而數據同步過程中可能出現數據丟失、寫入失敗、寫衝突、順序錯亂和同步延遲等問題,導致數據不一致。由於評論業務對數據一致性要求較高,我們需要一套可靠的數據同步方案,確保兩者之間的數據一致性。
重試隊列
針對寫失敗的問題,我們通過引入重試隊列來解決,將寫失敗的請求放入隊列中進行異步重試,確保數據不會因暫時性問題而永久丟失。引入重試隊列可能會導致寫併發產生數據競爭,進而引發數據最終不一致。雖然可以通過 **CAS(Compare-and-Swap)**來解決數據競爭問題,確保 “讀取 - 修改 - 寫回” 操作的原子性,但這也可能帶來亂序問題。
亂序問題
由於寫數據有多個場景來源,包括 binlog 同步、重試隊列,這些併發寫操作導致數據錯誤,此外 MQ 消息因 rebalance 可能會被重新消費,導致消息回放,所以數據同步過程中不僅需要保證冪等性,還必須確保消息的順序性:
例如併發寫的場景,評論 A 被點贊兩次,點贊數(like_count)爲 2,TiDB 會生成兩條 binlog 數據:
-
第一條數據 binlog_0 中,like_count = 1,由於網絡原因寫入失敗,數據被轉入重試隊列進行異步處理。
-
第二條數據 binlog_1 中,like_count = 2,寫入成功,評論 A 的 like_count 更新爲 2,符合預期。
-
然而,重試隊列繼續處理 binlog_0,由於無法保證兩個寫操作的順序,寫入後 like_count 被更新爲 1,導致數據不一致。
回退問題
在消息回放場景中,假設評論 A 被點贊三次,點贊數(like_count)爲 3,TiDB 會生成三條 binlog 數據 [a, b, c]。正常情況下,這三條數據會被順序消費並處理。如果在消費過程中發生 rebalance,導致消息回放,這三條數據會被重新消費,從而導致點贊數出現短暫的數據回退。
版本號
爲避免亂序和回退問題導致的數據不一致,我們引入了版本號機制,每次評論數據變更時,版本號會遞增。
UPDATE reply SET like_count=like_count+1, version=version+1 WHERE id = xxx
在 CAS 寫操作時,將 binlog 數據中的 version 值與 Taishan 中數據的 version 值進行比對。如果 binlog 中的 version 值大於或等於當前數據的 version 值,則執行更新;否則認爲該數據爲過期數據,予以丟棄。
對賬系統
根據 CAP 理論,在保證可用性(Availability)和分區容忍性(Partition)之後,分佈式系統無法完全保證一致性(Consistency)。儘管引入了重試機制、CAS 和版本號機制,但由於網絡調用的不可避免失敗,評論數據之間難免會出現長期或短期的不一致狀態。一旦發生不一致,需要有一套對賬機制來及時發現並修復這些不一致的數據。
實時對賬
通過 TiDB 的 Binlog 事件驅動,使用延遲隊列延遲 n 秒後消費, binlog 數據關聯查詢 Taishan 數據,並對比兩者的數據。對於發現的異常數據,進行通知並觸發數據修復。
離線對賬
利用 TiDB 和 Taishan 的數倉離線數據,進行 T+1 數據對比,驗證數據的最終一致性。
5. 降級策略
評論業務對可用性的要求非常高,尤其是在高併發、實時性強、用戶互動頻繁的場景下。通過搭建多級存儲架構,我們能夠在 TiDB 故障時自動降級到 Taishan,確保評論服務持續正常運行,我們的目標是實現每個請求的自動降級。
每次請求時,首先嚐試從主存儲獲取數據。當主存儲服務返回錯誤或長時間無響應時,降級到次要存儲服務獲取數據。在設計降級策略時,通常採用串行或並行方式,分別影響系統的響應時間和複雜性,而且整體耗時不能超過上游的超時限制,否則降級無效。
串行策略無法滿足評論業務對響應時間的要求,而並行策略則可能浪費資源。所以我們選擇了「對沖策略」(Hedging Policy)。在主節點請求超時後,我們會發起一個延遲 x 毫秒「備份請求」(backup request)到次節點。如果主節點返回成功,則直接返回結果,否則等待次節點的響應,優先選擇主節點的結果。通過根據主次節點的耗時特性設置合理的延遲閾值,我們在整體響應時間和資源消耗之間達到了平衡。
在實際生產環境中,我們根據具體業務場景設定 TiDB 和 Taishan 的主次關係。對於對數據實時性敏感、查詢輕量的場景,設定 TiDB 爲主存儲,Taishan 爲次存儲;而對於數據實時性要求較低、大 SQL 查詢的場景,則設定 Taishan 爲主存儲,TiDB 爲次存儲。
某天凌晨,TiDB 底層的 TiKV 節點宕機,在 TiKV 自愈期間,系統自動降級到 Taishan,評論業務未受影響,線上服務持續穩定運行。
6. 總結與展望
B 站評論服務對社區業務和用戶體驗至關重要。我們不僅致力於持續提升評論服務的穩定性,還不斷優化評論業務,以便用戶能夠看到更優質的評論內容,從而增強歸屬感和認同感,提供更好的消費體驗。
最後,發一條友善的評論吧,遇見和你一樣有趣的人。
作者丨 rumble、脆鯊鯊
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ko7pbQSqvqOo0E2iiDyXJg