面試官:系統(tǒng)有萬(wàn)億條消息怎么存儲(chǔ)?
我們?nèi)绾卧O(shè)計(jì)一個(gè)能存儲(chǔ)數(shù)萬(wàn)億條信息的系統(tǒng)?
Discord 的消息存儲(chǔ)演進(jìn)給我們提供了真實(shí)案例參考。
下圖顯示了 Discord 消息存儲(chǔ)的演變過(guò)程:MongoDB -> Cassandra -> ScyllaDB
圖片
第一階段
2015 年,Discord 的第一個(gè)版本建立在單個(gè) MongoDB 之上。2015 年 11 月左右,MongoDB 存儲(chǔ)了 1 億條消息,其內(nèi)存無(wú)法再容納數(shù)據(jù)和索引。延遲變得不可預(yù)測(cè)。消息存儲(chǔ)需要轉(zhuǎn)移到另一個(gè)數(shù)據(jù)庫(kù)。這時(shí) Cassandra 被選中。
第二階段
2017 年,Discord 擁有 12 個(gè) Cassandra 節(jié)點(diǎn),存儲(chǔ)了數(shù)十億條消息。
2022 年初,Discord 擁有 177 個(gè) Cassandra 節(jié)點(diǎn),存儲(chǔ)了數(shù)萬(wàn)億條消息。此時(shí),延遲再次變得難以預(yù)測(cè),維護(hù)的成本也變得過(guò)于昂貴。
造成這一問(wèn)題有幾個(gè)原因:
- Cassandra 使用 LSM 樹作為內(nèi)部數(shù)據(jù)結(jié)構(gòu)。讀取比寫入更昂貴。在一臺(tái)擁有數(shù)百名用戶的服務(wù)器上,可能會(huì)有很多并發(fā)讀取,從而導(dǎo)致熱點(diǎn)問(wèn)題。
- 維護(hù)集群(如壓縮 SSTables)會(huì)影響性能。
- 垃圾回收會(huì)導(dǎo)致明顯的延遲
第三階段
這時(shí),Discord 重新設(shè)計(jì)了消息存儲(chǔ)的架構(gòu):
- 采用集中式的數(shù)據(jù)服務(wù),其使用單體 API來(lái)訪問(wèn),并用 Rust 重寫。
- 采用基于 ScyllaDB 的存儲(chǔ)。ScyllaDB 是用 C++ 編寫的 Cassandra 兼容數(shù)據(jù)庫(kù)。
新架構(gòu)的優(yōu)勢(shì)在于:
- 用 C++ 而不是 Java 編寫,消除了垃圾回收暫停的干擾。
- 按核分片模型(Shard-per-Core model)提供更好的負(fù)載隔離,防止熱分區(qū)在節(jié)點(diǎn)間產(chǎn)生級(jí)聯(lián)延遲。
- 優(yōu)化了反向查詢性能,以滿足 Discord 的需求。
- 節(jié)點(diǎn)減少到 72 個(gè),同時(shí)將每個(gè)節(jié)點(diǎn)的磁盤空間增加到 9 TB。
為了進(jìn)一步保護(hù) ScyllaDB,Discord 針對(duì)數(shù)據(jù)服務(wù)還做了以下優(yōu)化:
- 在 Rust 中構(gòu)建中間數(shù)據(jù)服務(wù),限制并發(fā)流量峰值。
- 數(shù)據(jù)服務(wù)位于應(yīng)用程序接口和數(shù)據(jù)庫(kù)之間,可聚合請(qǐng)求。
- 即使多個(gè)用戶請(qǐng)求相同的數(shù)據(jù),也只需查詢一次數(shù)據(jù)庫(kù)。
- Rust 提供了快速、安全的并發(fā)功能,是這種工作負(fù)載的理想選擇。
優(yōu)化后的系統(tǒng)性能大大提高:
- ScyllaDB 的 p99 讀取延遲為 15 毫秒,而 Cassandra 為 40-125 毫秒。
- ScyllaDB 的 p99 的寫延遲為 5 毫秒,而 Cassandra 為 5-70 毫秒。
該系統(tǒng)可輕松應(yīng)對(duì)世界杯流量高峰。
本文參考 Discord blog。