日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

Discord 公司如何使用 Cassandra 存储上亿条线上数据

發(fā)布時(shí)間:2024/8/23 编程问答 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Discord 公司如何使用 Cassandra 存储上亿条线上数据 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Discord 是一款國(guó)外的類似 YY 的語(yǔ)音聊天軟件。Discord 語(yǔ)音聊天軟件及我們的 UGC 內(nèi)容的增長(zhǎng)速度比想象中要快得多。隨著越來越多用戶的加入,帶來了更多聊天消息。2016 年 7 月,每天大約有 4 千萬條消息;2016 年 12 月,每天超過億條。當(dāng)寫這篇文章時(shí)(2017 年 1 月),每天已經(jīng)超過 1.2 億條了。

我們?cè)缙跊Q定永久保存所有用戶的聊天歷史記錄,這樣用戶可以隨時(shí)在任何設(shè)備查找他們的數(shù)據(jù)。這是一個(gè)持續(xù)增長(zhǎng)的高并發(fā)訪問的海量數(shù)據(jù),而且需要保持高可用。如何才能搞定這一切?我們的經(jīng)驗(yàn)是選擇 Cassandra 作為數(shù)據(jù)庫(kù)!

我們?cè)谧鍪裁?/h2>

Discord 語(yǔ)音聊天軟件的最初版本在 2015 年只用了兩個(gè)月就開發(fā)出來。在那個(gè)階段,MongoDB 是支持快速迭代最好的數(shù)據(jù)庫(kù)之一。所有 Discord 數(shù)據(jù)都保存在同一個(gè) MongoDB 集群中,但在設(shè)計(jì)上我們也支持將所有數(shù)據(jù)很容易地遷移到一種新的數(shù)據(jù)庫(kù)(我們不打算使用 MongoDB 數(shù)據(jù)庫(kù)的分片,因?yàn)樗褂闷饋韽?fù)雜以及穩(wěn)定性不好)。

實(shí)際上這是我們企業(yè)文化的一部分:快速搭建來驗(yàn)證產(chǎn)品的特性,但也預(yù)留方法來支持將它升級(jí)到一個(gè)更強(qiáng)大的版本。

消息保存在 MongoDB 中,使用 channel_id 和 created_at 的單一復(fù)合索引。到 2015 年 11 月,存儲(chǔ)的消息達(dá)到了 1 億條,這時(shí),原來預(yù)期的問題開始出現(xiàn):內(nèi)存中再也放不下所有索引及數(shù)據(jù),延遲開始變得不可控,是時(shí)候遷移到一個(gè)更適合這個(gè)項(xiàng)目的數(shù)據(jù)庫(kù)了。

選擇正確的數(shù)據(jù)庫(kù)

在選擇一個(gè)新的數(shù)據(jù)庫(kù)之前,我們必須了解當(dāng)前的讀/寫模式,以及我們目前的解決方案為什么會(huì)出現(xiàn)問題。

  • 很顯然,我們的讀取是非常隨機(jī)的,我們的讀/寫比為 50 / 50。
  • 語(yǔ)音聊天服務(wù)器:它只處理很少的消息,每隔幾天才發(fā)幾條信息。在一年內(nèi),這種服務(wù)器不太可能達(dá)到 1000 條消息。它面臨的問題是,即使請(qǐng)求量很小,它也很難高效,單返回 50 條消息給一個(gè)用戶,就會(huì)導(dǎo)致磁盤中的許多次隨機(jī)查找,并導(dǎo)致磁盤緩存淘汰。
  • 私信聊天服務(wù)器:發(fā)送相當(dāng)數(shù)量的消息,一年下來很容易達(dá)到 10 萬到 100 萬條消息。他們請(qǐng)求的數(shù)據(jù)通常只是最近的。它們的問題是,數(shù)據(jù)由于訪問得不多且分散,因此不太可能被緩存在磁盤中。
  • 大型公共聊天服務(wù)器:發(fā)送大量的消息。他們每天有成千上萬的成員發(fā)送數(shù)以千計(jì)的消息,每年可以輕松地發(fā)送數(shù)以百萬計(jì)的消息。他們幾乎總是在頻繁請(qǐng)求最近一小時(shí)的消息,因此數(shù)據(jù)可以很容易地被磁盤緩存命中。
  • 我們預(yù)計(jì)在未來的一年,將會(huì)給用戶提供更多隨機(jī)讀取數(shù)據(jù)的功能:查看 30 天內(nèi)別人提及到你的消息,然后點(diǎn)擊到某條歷史記錄消息,查閱標(biāo)記(pinned)的消息以及全文搜索等功能。這一切導(dǎo)致更多的隨機(jī)讀取!!

接下來我們來定義一下需求:

  • 線性可擴(kuò)展性? - ?我們不想等幾個(gè)月又要重新考慮新的擴(kuò)展方案,或者是重新拆分?jǐn)?shù)據(jù)。
  • 自動(dòng)故障轉(zhuǎn)移?(failover) - ?我們不希望晚上的休息被打擾,當(dāng)系統(tǒng)出現(xiàn)問題我們希望它盡可能的能自動(dòng)修復(fù)。
  • 低維護(hù)成本? - ?一配置完它就能開始工作,隨著數(shù)據(jù)的增長(zhǎng)時(shí),我們要需要簡(jiǎn)單增加機(jī)器就能解決。
  • 已經(jīng)被驗(yàn)證過的技術(shù)? - ?我們喜歡嘗試新的技術(shù),但不要太新。
  • 可預(yù)測(cè)的性能? - ?當(dāng) API 的響應(yīng)時(shí)間 95% 超過 80ms 時(shí)也無需警示。我們也不想重復(fù)在 Redis 或 Memcached 增加緩存機(jī)制。
  • 非二進(jìn)制存儲(chǔ)? - 由于數(shù)據(jù)量大,我們不太希望寫數(shù)據(jù)之前做一些讀出二進(jìn)制并反序列化的工作。
  • 開源? - ?我們希望能掌控自己的命運(yùn),不想依靠第三方公司。

Cassandra 是唯一能滿足我們上述所有需求的數(shù)據(jù)庫(kù)。我們可以添加節(jié)點(diǎn)來擴(kuò)展它,添加過程不會(huì)對(duì)應(yīng)用程序產(chǎn)生任何影響,也可以容忍節(jié)點(diǎn)的故障。一些大公司如 Netflix 和蘋果,已經(jīng)部署有數(shù)千個(gè) Cassandra 節(jié)點(diǎn)。數(shù)據(jù)連續(xù)存儲(chǔ)在磁盤上,這樣減少了數(shù)據(jù)訪問尋址成本,且數(shù)據(jù)可以很方便地分布在集群上。它依賴 DataStax,但依舊是開源和社區(qū)驅(qū)動(dòng)的。

做出選擇后,我們需要證明它實(shí)際上是可行的。

數(shù)據(jù)模型

向一個(gè)新手描述 Cassandra 數(shù)據(jù)庫(kù)最好的辦法,是將它描述為 KKV 存儲(chǔ),兩個(gè) K 構(gòu)成了主鍵。第一個(gè) K 是分區(qū)鍵(partition key),用于確定數(shù)據(jù)存儲(chǔ)在哪個(gè)節(jié)點(diǎn)上,以及在磁盤上的位置。一個(gè)分區(qū)包含很多行數(shù)據(jù),行的位置由第二個(gè) K 確定,這是聚類鍵(clustering key),聚類鍵充當(dāng)分區(qū)內(nèi)的主鍵,以及決定了數(shù)據(jù)行如何排序。可以將分區(qū)視為有序字典。這些屬性相結(jié)合,可以支持非常強(qiáng)大的數(shù)據(jù)建模。

前面提到過,消息在 MongoDB 中的索引用的是 channel_id 和 created_at,由于經(jīng)常查詢一個(gè) channel 中的消息,因此 channel_id 被設(shè)計(jì)成為分區(qū)鍵,但 created_at 不作為一個(gè)大的聚類鍵,原因是系統(tǒng)內(nèi)多個(gè)消息可能具有相同的創(chuàng)建時(shí)間。

幸運(yùn)的是,Discord 系統(tǒng)的 ID 使用了類似 Twitter Snowflake [1] 的發(fā)號(hào)器(按時(shí)間粗略有序),因此我們可以使用這個(gè) ID。主鍵就變成( channel_id, message_id), message_id 是 Snowflake 發(fā)號(hào)器產(chǎn)生。當(dāng)加載一個(gè) channel 時(shí),我們可以準(zhǔn)確地告訴 Cassandra 掃描數(shù)據(jù)的范圍。

下面是我們的消息表的簡(jiǎn)化模式。

CREATE TABLE messages (channel_id bigint,message_id bigint,author_id bigint,content text,PRIMARY KEY (channel_id, message_id) ) WITH CLUSTERING ORDER BY (message_id DESC);

Cassandra 的 schema 與關(guān)系數(shù)據(jù)庫(kù)模式有很大區(qū)別,調(diào)整 schema 非常方便,不會(huì)帶來任何臨時(shí)性的性能影響。因此我們獲得了最好的二進(jìn)制存儲(chǔ)和關(guān)系型存儲(chǔ)。

當(dāng)我們開始向 Cassandra 數(shù)據(jù)庫(kù)導(dǎo)入現(xiàn)有的消息時(shí),馬上看見出現(xiàn)在日志上的警告,提示分區(qū)的大小超過 100MB。發(fā)生了什么?!Cassandra 可是宣稱單個(gè)分區(qū)可以支持 2GB!顯然,支持那么大并不意味著它應(yīng)該設(shè)成那么大。

大的分區(qū)在進(jìn)行壓縮、集群擴(kuò)容等操作時(shí)會(huì)對(duì) Cassandra 帶來較大的 GC 壓力。大分區(qū)也意味著它的數(shù)據(jù)不能分布在集群中。很明顯,我們必須限制分區(qū)的大小,因?yàn)橐粋€(gè)單一的 channel 可以存在多年,且大小不斷增長(zhǎng)。

我們決定按時(shí)間來歸并我們的消息并放在一個(gè) bucket 中。通過分析最大的 channel,我們來確定 10 天的消息放在一個(gè) bucket 中是否會(huì)超過 100mb。Bucket 必須從 message_id 或時(shí)間戳來歸并。

DISCORD_EPOCH = 1420070400000 BUCKET_SIZE = 1000 * 60 * 60 * 24 * 10def make_bucket(snowflake):if snowflake is None:timestamp = int(time.time() * 1000) - DISCORD_EPOCHelse:# When a Snowflake is created it contains the number of# seconds since the DISCORD_EPOCH.timestamp = snowflake_id >> 22return int(timestamp / BUCKET_SIZE)def make_buckets(start_id, end_id=None):return range(make_bucket(start_id), make_bucket(end_id) + 1)

Cassandra 數(shù)據(jù)庫(kù)的分區(qū)鍵可以復(fù)合,所以我們新的主鍵成為 (( channel_id, bucket), message_id)。

CREATE TABLE messages (channel_id bigint,bucket int,message_id bigint,author_id bigint,content text,PRIMARY KEY ((channel_id, bucket), message_id) ) WITH CLUSTERING ORDER BY (message_id DESC);

為了方便查詢最近的消息,我們生成了一個(gè)從當(dāng)前時(shí)間到 channel_id(也是 Snowflake 發(fā)號(hào)器生成,要比第一個(gè)消息舊)的 bucket。然后我們依次查詢分區(qū)直到收集到足夠的消息。這種方法的缺點(diǎn)是,不活躍的 channel 需要遍歷多個(gè) bucket 從而收集到足夠返回的消息。在實(shí)踐中,這已被證明還行得通,因?yàn)閷?duì)于活躍的 channel,查詢第一個(gè) bucket 就可以返回足夠多的數(shù)據(jù)。

將消息導(dǎo)入到 Cassandra 數(shù)據(jù)庫(kù)十分順利,我們準(zhǔn)備嘗試遷移到生產(chǎn)環(huán)境。

冒煙啟動(dòng)

在生產(chǎn)環(huán)境引入新系統(tǒng)總是可怕的,因此最好在不影響用戶的前提下先進(jìn)行測(cè)試。我們將代碼設(shè)置成雙讀/寫到 MongoDB 和 Cassandra。

一啟動(dòng)系統(tǒng)我們就收到 bug 追蹤器發(fā)來的錯(cuò)誤信息,錯(cuò)誤提示 author_id 為 null。怎么會(huì)是 null ?這是一個(gè)必需的字段!在解釋這個(gè)問題之前,先介紹一下問題的背景。

最終一致性

Cassandra 是一個(gè) AP 數(shù)據(jù)庫(kù),這意味著它犧牲了強(qiáng)一致性(C)來?yè)Q取可用性(A),這也正是我們所需要的。在 Cassandra 中讀寫是一個(gè)反模式(讀比寫的代價(jià)更昂貴)。你也可以寫入任何節(jié)點(diǎn),在 column 的范圍,它將使用“l(fā)ast write wins”的策略自動(dòng)解決寫入沖突,這個(gè)策略對(duì)我們有何影響?請(qǐng)看下面動(dòng)畫。

在例子中,一個(gè)用戶編輯消息時(shí),另一個(gè)用戶刪除相同的消息,當(dāng) Cassandra 執(zhí)行 upsert 之后,我們只留下了主鍵和另外一個(gè)正在更新文本的列。

有兩個(gè)可能的解決方案來處理這個(gè)問題:

  • 編輯消息時(shí),將整個(gè)消息寫回。這有可能找回被刪除的消息,但是也增加了更多數(shù)據(jù)列沖突的可能。
  • 能夠判斷消息已經(jīng)損壞時(shí),將其從數(shù)據(jù)庫(kù)中刪除。

我們選擇第二個(gè)選項(xiàng),我們按要求選擇一列(在這種情況下, author_id),如果消息是空的就刪除。

在解決這個(gè)問題時(shí),我們也注意到我們的寫入效率很低。由于 Cassandra 被設(shè)計(jì)為最終一致性,因此執(zhí)行刪除操作時(shí)不會(huì)立即刪除數(shù)據(jù),它必須復(fù)制刪除到其他節(jié)點(diǎn),即使其他節(jié)點(diǎn)暫時(shí)不可用,它也照做。

Cassandra 為了方便處理,將刪除處理成一種叫“墓碑”的寫入形式。在處理過程中,它只是簡(jiǎn)單跳過它遇到的墓碑。墓碑通過一個(gè)可配置的時(shí)間而存在(默認(rèn) 10 天),在逾期后,會(huì)在壓縮過程中被永久刪除。

刪除列以及將 null 寫入列是完全相同的事情。他們都產(chǎn)生墓碑。因?yàn)樗性?Cassandra 數(shù)據(jù)庫(kù)中的寫入都是更新插入(upsert),這意味著哪怕第一次插入 null 都會(huì)生成一個(gè)墓碑。

實(shí)際上,我們整個(gè)消息數(shù)據(jù)包含 16 個(gè)列,但平均消息長(zhǎng)度可能只有了 4 個(gè)值。這導(dǎo)致新插入一行數(shù)據(jù)沒緣由地將 12 個(gè)新的墓碑寫入至 Cassandra 中。

解決這個(gè)問題的方法很簡(jiǎn)單:只給 Cassandra 數(shù)據(jù)庫(kù)寫入非空值。

性能

Cassandra 以寫入速度比讀取速度要快著稱,我們觀察的結(jié)果也確實(shí)如此。寫入速度通常低于 1 毫秒而讀取低于 5 毫秒。我們觀察了數(shù)據(jù)訪問的情況,性能在測(cè)試的一周內(nèi)保持了良好的穩(wěn)定性。沒什么意外,我們得到了我們所期望的數(shù)據(jù)庫(kù)。

說到快速、一致的讀取性能,這里有一個(gè)例子,跳轉(zhuǎn)到某個(gè)上百萬條消息的 channel 的一年前的某條消息,請(qǐng)看動(dòng)畫

巨大的意外

一切都很順利,因此我們將它切換成我們的主數(shù)據(jù)庫(kù),然后在一周內(nèi)淘汰掉 MongoDB。Cassandra 工作一切正常,直到 6 個(gè)月后有一天,Cassandra 突然變得反應(yīng)遲鈍。我們注意到 Cassandra 開始出現(xiàn) 10 秒鐘的 GC 全停頓(Stop-the-world) ,但是我們不知道原因。

我們開始定位分析,發(fā)現(xiàn)加載某個(gè) channel 需要 20 秒。一個(gè)叫 “Puzzles & Dragons Subreddit” 的公共 channel 是罪魁禍?zhǔn)住R驗(yàn)樗且粋€(gè)開放的 channel,因此我們也跑進(jìn)去探個(gè)究竟。

令我們驚訝的是,channel 里只有 1 條消息。我們也了解到他們用我們的 API 刪除了數(shù)百萬條消息,只在 channel 中留下了 1 條消息。

上文提到 Cassandra 是如何用墓碑(在最終一致性中提及過)來處理刪除動(dòng)作的。當(dāng)一個(gè)用戶載入這個(gè) channel,雖然只有 1 條的消息,Cassandra 不得不掃描百萬條墓碑(產(chǎn)生垃圾的速度比虛擬機(jī)收集的速度更快)。

我們通過如下措施解決:

  • 因?yàn)槲覀兠客矶紩?huì)運(yùn)行 Cassandra 數(shù)據(jù)庫(kù)修復(fù)(一個(gè)反熵進(jìn)程),我們將墓碑的生命周期從 10 天降低至 2 天。
  • 我們修改了查詢代碼,用來跟蹤空的 buckets,并避免他們?cè)谖磥淼?channel 中加載。這意味著,如果一個(gè)用戶再次觸發(fā)這個(gè)查詢,最壞的情況,Cassandra 數(shù)據(jù)庫(kù)只在最近的 bucket 中進(jìn)行掃描。

未來

我們目前在運(yùn)行著一個(gè)復(fù)制因子是 3 的 12 節(jié)點(diǎn)集群,并根據(jù)業(yè)務(wù)需要持續(xù)增加新的節(jié)點(diǎn),我相信這種模式可以支撐很長(zhǎng)一段時(shí)間。但隨著 Discord 軟件的發(fā)展,相信有一天我們可能需要每天存儲(chǔ)數(shù)十億條消息。

Netflix 和蘋果都維護(hù)了運(yùn)行著數(shù)千個(gè)節(jié)點(diǎn)的集群,所以我們知道目前這個(gè)階段不太需要顧慮太多。當(dāng)然我們也希望有一些點(diǎn)子可以未雨綢繆。

近期工作

將我們的消息集群從 Cassandra 2 升級(jí)到 Cassandra 3。Cassandra 3 有一個(gè)新的存儲(chǔ)格式,可以將存儲(chǔ)大小減少 50% 以上。新版 Cassandra 單節(jié)點(diǎn)可以處理更多數(shù)據(jù)。目前,我們?cè)诿總€(gè)節(jié)點(diǎn)存儲(chǔ)了將近 1TB 的壓縮數(shù)據(jù)。我們相信我們可以安全地?cái)U(kuò)展到 2TB,以減少集群中節(jié)點(diǎn)的數(shù)量。

長(zhǎng)期工作

嘗試下 Scylla [4],它是一款用 C++ 編寫與 Cassandra 兼容的數(shù)據(jù)庫(kù)。在正常操作期間,我們 Cassandra 節(jié)點(diǎn)實(shí)際上是沒有占用太多的 CPU,然而在非高峰時(shí)間,當(dāng)我們運(yùn)行修復(fù)(一個(gè)反熵進(jìn)程)變得相當(dāng)占用 CPU,同時(shí),繼上次修復(fù)后,修復(fù)持續(xù)時(shí)間和寫入的數(shù)據(jù)量也增大了許多。 Scylla 宣稱有著極短的修復(fù)時(shí)間。

將沒使用的 Channel 備份成谷歌云存儲(chǔ)上的文件,并且在有需要時(shí)可以加載回來。我們其實(shí)也不太想做這件事,所以這個(gè)計(jì)劃未必會(huì)執(zhí)行。

結(jié)論

切換之后剛剛過去一年,盡管經(jīng)歷過“巨大的意外”,一切還是一帆風(fēng)順。從每天 1 億條消息到目前超過 1.2 億條,一直保持著良好的性能和穩(wěn)定性。由于這個(gè)項(xiàng)目的成功,因此我們將生產(chǎn)環(huán)境的其他數(shù)據(jù)也遷移到 Cassandra,并且也取得了成功。


原文鏈接
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。

總結(jié)

以上是生活随笔為你收集整理的Discord 公司如何使用 Cassandra 存储上亿条线上数据的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。