位元組跳動極高可用 KV 儲存系統詳解

架構師修行手冊發表於2023-03-16

導讀 Abase 是位元組跳動線上推薦的底層儲存,也是位元組跳動最大規模的線上 KV 儲存系統,承擔著 90% 以上的 KV 儲存需求、支援多個位元組跳動產品和業務。本次分享詳細介紹了 Abase 的技術實現和高可用等關鍵技術。

今天的介紹圍繞下面 4 點展開:

1. Abase 簡介

2. 高可用挑戰

3. 解決方案

4. 關鍵技術

分享嘉賓|劉健 位元組跳動 研發工程師

編輯整理|張德通

出品社群|DataFun


01

Abase 簡介

Abase 是什麼?Abase 最初用作位元組跳動線上推薦的底層儲存。

位元組跳動極高可用 KV 儲存系統詳解

刷抖音的時候,服務端需要記錄已經給使用者推薦過的影片列表,再推薦給使用者更符合需求的其他影片,使用者的瀏覽歷史列表就儲存在 Abase 內。

位元組跳動極高可用 KV 儲存系統詳解

在 2016 年時 Abase 正式立項。Abase 的 A 取自位元組跳動的辦公地點中航廣場英文名的第一個字母,無特殊含義。2017 年 Abase 作為位元組跳動推薦的核心儲存大規模上線。

2018-2019 年,隨著位元組跳動業務快速擴張,Abase 也從支援推薦儲存變成了支援全公司基本所有業務線的線上的 KV 儲存。這期間 Abase 從單純地支援一個 KV 介面擴充套件到支援各種 Redis 複雜命令和資料結構,也支援了多機房容災的功能。

2020 年 Abase 在位元組跳動已經有相當大的規模了,與立項時 Abase 定位是單叢集高效能 KV 介面的設計初衷大不相同,此時已經不再完全符合位元組跳動大規模的業務需求。於是我們啟動了 Abase 的第二代專案,第二代 Abase 核心是做高可用。2021 年 Abase 2.0 上線,2022 年支援多租戶、以 Serverless 方式提供服務,大幅降低線上儲存成本。

位元組跳動極高可用 KV 儲存系統詳解

目前 Abase 是位元組跳動最大規模的線上 KV 儲存系統,承擔了位元組 90% 以上的 KV 儲存需求。Abase 支援的位元組產品線包括推薦、搜尋、廣告、電商、抖音、飛書、懂車帝等等。它的核心特點是大容量、大吞吐、低延時、高可用、易擴充套件,Abase 的叢集規模比通常的 KV 儲存更大,容量和吞吐方面做的最佳化更多。

使用場景方面,Abase 可以用來做大容量的快取,以及持久化 KV 的場景。如 Reids 叢集的記憶體規模受限、需要用磁碟快取資料的場景,Abase 相容 Redis 協議,如果用記憶體版 Redis 成本太高,可以用 Abase 替代。

Abase 相容多種資料生態,支援 Hive 透過 bulk load 把資料匯入到 Abase。

Abase 為了極致地容災,支援跨地區資料同步,可以做異地多活。Abase 在一個叢集內支援異地多活,降低跨叢集之間的鏈路傳輸代價。

目前 Abase 在位元組跳動已經部署超過 5 萬臺伺服器,QPS 在百億級別。Abase 支援的業務數超過 5000,基本覆蓋了位元組的全部產品線,有超過百 P 級別的資料量。

位元組跳動極高可用 KV 儲存系統詳解

Abase 第二代架構針對第一代架構的痛點進行了最佳化。其特點如下:

(1)Abase 2.0 是一套多寫架構,可以做到極致高可用。多寫的架構沒有了主從架構的切換主節點的時間,也沒有秒級別的主從切換不可用問題;多寫架構也從架構層面遮蔽了慢節點,規避了慢節點問題。

(2)Abase 2.0 解決多寫架構的寫衝突方面,對於 KV 結構支援 last write win 這種透過時間戳的方式解決衝突;對於一些複雜資料結構,如 string 的 incr、append 或者雜湊結構,支援 CRDT 的解決方案。此外 Abase 2.0 還會做快速的資料一致。

(3)Abase 2.0 沒有用純非同步的程式設計框架,我們用協程的方式讓所有請求都在單執行緒內完成,讓請求儘量 RunToComplete,沒有執行緒切換的開銷和代價。

(4)Abase 2.0 原生支援多租戶。雖然 SSD 的隨機 IO 效能很好,但如果 IO 模式過於離散會導致效能變差,因此最好保證有單一的寫入流。多租戶會把不同使用者的寫入做聚合,而且對使用者使用的資源用資源池進行限制,防止部分使用者使用 IO 過高佔用資源過多影響整個叢集使用者使用的問題。Abase 多租戶的功能使用了資源池,把負載均衡做的很好,降低了硬體成本。

Abase 2.0 原生支援異地多活架構,常見的異地多活指的是在不同地域搭建多套叢集,如在華東、華北分別搭建兩個不同 KV 叢集,但兩個叢集之間需要透過中介軟體同步資料,難以保證資料最終一致性。後面會介紹到這方面的解決方案。

02

位元組跳動 Abase 面臨的高可用挑戰

1. 高可用的分散式儲存

一般高可用指的是同一分片的資料有多個副本。

以寫舉例,一般是主從架構的模式,有一個主節點負責寫入、兩個從節點負責跟進寫入的資料以及作為寫節點的熱備。如果寫入的節點當機或掛掉,可透過檢測或心跳探測,快速地把主節點切換為其他節點。

一般做到這個就可以自稱為是“一個高可用的系統”了。但這樣的系統會有一些問題。

那麼 Abase 自稱為“極高可用”是指什麼呢?

位元組跳動極高可用 KV 儲存系統詳解

主從透過心跳或者其他探測模式切換,必然存在主從切換的時間,這段時間內是不可寫的。所有工程界實踐的主從切換時間一般都在秒級別。

Abase 所服務的位元組跳動使用者,對可用性要求非常高。平均延遲需要在毫秒左右或者P99 在 10 毫秒以內。秒級別的服務不可用也是使用者希望能夠儘量避免的。

選主我們還可以逐漸最佳化,可以從 30 秒最佳化到 1 秒,甚至 500 毫秒。更難解決的問題是,在主從架構下寫是一個單點,如果寫節點完全不可用,可以立刻進行切主。但如果寫節點沒有徹底掛,由於磁碟溫度過高導致 IO 效能變差,CPU 負載過高導致執行緒排程變差,網路延遲上升或者是網路卡插鬆了等等,各種各樣的原因導致節點的延遲上升,比其他的節點更慢的情況出現。此時雖然可以透過一些經驗設定閾值,判斷節點的指標超過什麼閾值就踢掉節點,但這種透過經驗設定閾值的手段存在很多問題。

如果閾值設低了,可能導致大量線上節點被誤踢,那影響範圍更大、引起更大問題;而如果閾值設得太高,對慢節點的處理也沒有什麼幫助。我們之前做了很多監控,分析了不同節點的方差來發現慢節點。

但對於一個寫入節點來說,所有寫都經過這個單點,很難判斷問題是慢節點導致的,還是由於負載高導致的。

相對來說,讀請求的慢節點更容易處理,讀請求可以選擇 backup request 策略。當查一個副本的平均延遲在 P99 或是 80 分位沒有返回資料時,可以透過向其他部分發請求完成讀請求。但如果寫請求處理慢了,把請求發給其他節點也無濟於事。切主對於所有系統都是有一定代價的,頻繁切主可能引起更大的問題。

這就是傳統的這種高可用架構不能滿足位元組跳動業務場景下對更極致可用性的需求。我們希望從架構上徹底解決慢節點,線上某些延遲升高、抖動,運維的痛點問題。

2. 極端故障情況

上一代系統中,解決機房徹底斷掉、網路孤島的策略之一是把所有資料進行 3AZ 部署。具體方法是在同一個地域有相互臨近的三個機房,副本分佈在三個機房內。當極端故障情況發生時做自動切主,一般儲存系統中會有 safe mode 模式,如果大多數節點同時故障,判定檢測故障的邏輯有問題,系統會進行熔斷。

在這種極端故障情況下,大多數情況會觸發叢集的 HA 或 fail over 熔斷,這時需要人工決策是否進入容災模式,或是否把所有主節點從一個地域切換到另一個地域,這個切換過程比較平滑、但需要人工響應,耗時在 5 分鐘到半小時,但這段時間內服務會受影響。

另一種處理極端故障的方法是搭建多活模式叢集透過中介軟體同步資料。但這種方案存在的問題是叢集間資料難以保障最終一致性,一旦資料有衝突就會對使用者造成困擾。

03

Abase 2.0 應對業務高可用挑戰的解決方案

1. Abase 2.0 架構

我們的思路源於 Dynamo 多年前的一篇論文中做到了極致高可用的 Column 讀寫方案。但這一方案又不完全滿足我們的需求,我們提出了自己的方案。

Abase 2.0 架構如下圖,和多數 KV 儲存架構類似,有專門節點負責資料 location 資訊,Data Node(資料節點)承載實際資料讀寫,有一個 Client 直連 Node、或透過 Proxy 處理多語言的請求轉發。核心是中心節點、資料節點、Proxy 三大模組。

位元組跳動極高可用 KV 儲存系統詳解

資料部署時,Abase 是一個多地域系統,一個 Abase 叢集跨多地域部署,一個 Abase 可能包含一個華東 Region 和一個華北 Region。華北 Region 又被分為 3 個 AZ/IDC,其中的 Region 可能是由位元組在A、B、C或者其他華北附近 300 公里內的機房 IDC 內的物理機共同組成。

位元組跳動極高可用 KV 儲存系統詳解

POD 是介於 IDC 和實體 RAC 機器之間的一個網路概念,是 Abase 2.0 的一層抽象,不是 K8S 中的 pod 概念。

如果一個機房某房間的空調故障,這個房間的所有機器都可能因為過熱當機。Abase 2.0 會保證多副本不部署在同一個 POD 中。一般一個房間的所有機器都不會跨 POD,機器所在房間空調故障或機房過熱、甚至房間失火都不會影響資料所有的副本。

POD 下繫結具體的路由器、交換機和具體的一臺伺服器,機器上有不同 DataNode。DataNode 上掛著磁碟。

對應的機器上 Abase 2.0 為每個 CPU 核(Core)都起了工作執行緒,所有的請求從處理到執行、最終返回,都是在這個 CPU Core 的工作執行緒下執行的。CPU Core 下面有一個副本。

邏輯層面上,Abase 叢集有很多使用者的庫,我們稱為 Namespace。使用者在一個 Namespace 中會分很多邏輯表。資料庫把邏輯表分給很多 Partition(分片)。為了做高可用、讓資料高可靠,一個分片要有多個副本,每個副本稱為一個 Replica。

2. Abase 2.0 的高可用方案

Abase 2.0 借鑑了 Dynamo 無主架構多點寫入的一套方案,為什麼這套方案對可能性提升很大呢?

位元組跳動極高可用 KV 儲存系統詳解

在有主時,任何一個單點故障都需要一秒或者幾秒的心跳探測時間,這段時間內叢集不可寫。無主的架構下就沒有主節點不可用的影響。

如果寫節點是個慢節點,上層可以做一些 backup retry 或重試時寫到其他節點,該過程對使用者基本無感,以更低代價把慢節點規避掉,縮短 P99 延遲。

Dynamo 方案中存在的問題是資料 Diff 代價大、讀 QPS 放大,且資料修復週期時間較長。位元組跳動百億級別的資料量不能忍受讀 QPS 放大和長時間的資料修復。

位元組跳動極高可用 KV 儲存系統詳解

做分散式系統常說的 CAP 定理認為,在分散式系統中,一致性、可用性和分割槽容錯三個特性最多有兩個。雖然 CAP 定理對分散式系統開發有很好的指導性,但我們不能被 CAP 定理限制思路。

一般分散式系統的分割槽容錯是必須解決和不能避免的問題,絕大多數分散式系統會在一致性和可用性之間做選擇。但我們認為,系統在一致性和可用性之間做取捨後,雖然能做到強一致,但可用性會打折扣。

一致性協議,如果想做到強一致,可以用傳統的兩階段提交 2PC 協議實現。但 2PC 協議在任何一個節點故障的情況下都無法成功傳送,這時單節點故障就會導致整個系統不可用。

如果用 Raft 這類共識協議,我們可以做到對使用者表現出讀寫的強一致性,單個節點網路隔離時整個系統依然可用。我們認為 Raft 這類協議比 2PC 協議可用性高。對於沒有更新的資料可以用 quorum 協議,也可以做到強一致。我們認為 quorum 協議由於沒有選主,可用性更高。

位元組跳動極高可用 KV 儲存系統詳解

如果做的是任何時刻都可用的系統, 就無法做到任何時刻都是一致的;但我們可以做一個弱一致的、最終一致的系統。也可以做不完全強一致、不完全保證可用性的系統,在這之間進行取捨,只要使用者可以接受即可。因此設計系統時不一定在 CA 之間做非此即彼的選擇。

位元組跳動極高可用 KV 儲存系統詳解

Abase 方案是無主架構,參考了多主架構的優點。

在一些快取場景下,可以接受個別資料丟失和非同步複製,但要求極致效能,這時可以設定 W=1(寫),這時 Abase 提供的方案是不完全保證寫請求成功,發出寫請求後立刻返回。

R(讀)也可以設定為 1,即使用者沒能拿到最新資料,但我們可以在 6-7 毫秒內讓資料達到最終一致。這裡的實現借鑑了多主架構的快速同步資料的方式。

什麼是無主架構、什麼是多主架構?

Dynamo 的客戶端可以直接寫多個副本,取最快的幾個副本的成功即可返回客戶端成功,這就是一個無主架構。但無主架構中的核心特徵是 coordinator 層和 write pointer 層接收使用者寫入,再把資料分發給多副本。

多主架構中一個副本中有一個或多個主,但每個副本都可以是主,每個副本都能夠接受寫入再轉發給其他副本。

多主架構和無主架構很相似,但無主架構可以允許亂序提交。最新寫入的資料寫多數或某個節點成功,不依賴於任何之前寫入的資料,只要當前可以寫成功、寫入流程就算成功。

但有主架構的資料同步過程有一個資料同步序列。單主架構下用 Raft 組成的多副本的資料是嚴格按照日誌的 sequence id 遞增同步的。

無主架構下由於需要比對所有資料,資料達到一致的代價更大;但無主架構的優勢是消除了慢節點和不依賴之前的資料同步。有主架構的優勢是資料同步簡潔,只要關注 session id 不斷拉資料即可,不需要像無主架構一樣用 Merkle tree 做全量資料 Diff。但缺點是如果前面有任何資料沒有同步成功,後面的資料同步會被卡住,可用性有所下降。

Abase 2.0 是一套參考了多主架構設計優勢的無主架構系統。首先 Abase 把寫入點收斂到了每個副本上,write point 的記錄和副本節點同級部署。另外我們也儘量讓資料保持連續,如寫入 A 副本的資料遞增地同步到其他副本、減少了資料 Diff 的代價。

但在資料恢復時是否需要等所有資料同步完成後才能接受寫請求呢?針對這個問題,我們對部分場景做了最佳化,讓 Abase 允許亂序提交,但只有在主從落後太多時才允許亂序提交。這樣我們就保證了整體一致性的演算法效率較高的同時保證了可用性。

Abase 2.0 解決了接受多寫面臨的寫衝突問題。兩副本都可以寫,在發生網路隔離後,如下圖中的副本 1、2 網路隔離時發生了兩個更新、網路恢復後要以哪個為準呢?

位元組跳動極高可用 KV 儲存系統詳解

我們採用 Last Write Win 的方式處理這個問題。對所有資料寫入都分配時間戳記錄 write point,以最後寫入為準。但分配物理時間戳是不準的,不同機器物理時間戳之間沒有保障,且物理時間戳也可能重複。

Abase 2.0 使用了混合邏輯時鐘加物理位置,作為全球和全域性唯一的時間戳。混合邏輯時鐘指的是如果兩節點之間有互動,副本 1 的物理時鐘比副本 2 的物理時鐘快。

不使用混合邏輯時鐘可能導致 t1 比 t2 時間更晚,資料 Merge 後以 t1 為準造成混亂。使用混合邏輯時鐘後,只要和 Client 有互動就可以讓後寫入的副本一定在時間戳上大於前面寫入的副本。

如果發生網路隔離,位置資訊可以做到副本時間戳全域性、全球唯一。

位元組跳動極高可用 KV 儲存系統詳解

使用無主架構的同時,不同使用者會有不同需求和場景。例如飛書的業務特點是不需要極致的吞吐量,但要求資料一致性和高可用;對於一些離線業務不需要高效能和可用性,但要保證資料一致性。

為了支援多樣的使用者需求,Abase 2.0 也開放了使用者可配置的能力,有主無主模式和 quorum 數都開放給使用者配置。

Abase 2.0 既支援多主模式、也支援單主模式。多主是單主的集,首先叢集會進行選主,使用者如果要求時效性則讀寫都可以訪問主節點,否則讀寫可以訪問任何副本。

Abase 2.0 還可以讓使用者配置 quorum 數。使用者如果想要更好的可靠性,配置 quorum 數量為 2 時意味著資料至少同步到 2 個節點、落盤後才會返回成功;快取場景下追求極致效能,可接受以不同步方式完成資料同步,則可以設定 quorum 數為 1,即寫一個副本落盤成功後就返回使用者成功,再非同步地執行同步。

04

Abase 關鍵技術

1. 多寫下的一致性效率問題

下圖中是 Abase 的使用者資料流轉流程。首先使用者的寫請求會被髮到 Proxy,副本 1、2、3 都有 coordinator 可以接受寫請求。Proxy 會隨機找到一個 coordinator,coordinator 為這個寫請求打上全域性唯一的混合邏輯時鐘時間戳,再把請求併發地發給副本 1、2、3。

位元組跳動極高可用 KV 儲存系統詳解

如果使用者配置了 quorum 為 2,在任意兩個節點寫 WAL 成功就會返回使用者成功、同時把資料非同步地同步到其他副本上。

下圖中,副本 A 寫入資料,正常情況下它的 Log 是有一定序號的,可以認為像 Raft 的主一樣有 A1、A2、A3、A4。但副本 B 本來接收的順序是 B1、B2、B3、B4,副本 A 中這個遞增的 Log 流和副本 B 的 Log 流會在網路正常連通的情況下互相地轉發同步資料。

位元組跳動極高可用 KV 儲存系統詳解

每個資料都有自身混合邏輯時鐘,和自然時鐘有一定的關係,我們會定期進行資料的比對,如果所有節點都收到了一定時間內的所有資料,節點就會把資料進行衝突的解決、打平、落盤。

2. 多寫下的效能問題

Abase 使用 last write win 解決寫衝突,因此需要保留資料寫入 Key 時的時間戳。如兩個副本,副本 A 和副本 B 中都寫入了某個 Key,在比較時間戳寫入時間後返回給使用者更新的資料。

我們第一期實現的方案是把時間戳直接拼在 Key 後作為編碼,資料儲存到 RocksDB 中。這個實現帶來的問題是使用者需要查某個 Key 時,RocksDB 中只能透過 Scan 操作查詢資料,而 Scan 操作比點查開銷大、效能差。

位元組跳動極高可用 KV 儲存系統詳解

我們的最佳化方案是定期地處理資料衝突和打平,在正常網路狀況下秒級別即可同步所有資料。某個時間戳之前的資料已經完全一致,即可把多版本進行合併。

Abase 把引擎分為兩層,把多版本資料合併後唯一的單版本資料儲存進 KV 引擎。目前 KV 引擎支援 RocksDB 和位元組的 RocksDB 最佳化版和雜湊引擎。

未打平的資料儲存在 Log 內,而 Log 不支援查詢,Abase 就在記憶體中建了索引,在記憶體中指向 Log 支援查詢。這樣一來,在 LSM 引擎內的查詢是點查,在記憶體中的資料有記憶體索引,查詢效率非常高。

05

問答環節

Q1:整體 Abase 的 QPS、響應延遲儲存空間指標如何?如何解決瓶頸?

A1:Abase 的 QPS 是百億級別,資料量在百 P 級別,延遲 P99 在 50ms 內。SLA 是 99.95% 左右。對於高優叢集由於獨立部署,P99 在 10ms 內。

效能瓶頸需要具體業務具體分析。一些使用者的 Value 很大,可能每條資料 1M,讀寫吞吐非常高,對於此類場景吞吐就是瓶頸。且對於大 Value,LSM tree 有很大的讀寫放大問題,一般用讀寫分離的方式緩解讀寫放大問題。但仍然有可能存在 2-3 倍寫放大。

對於帶固定 TTL 資料,對於大 Value 還帶有 TTL 的資料我們不寫入 LSM 引擎、只寫入 Log 後等待失效即可。在 Abase 上線了兩層引擎後,為大 Value 場景提供了很好的支撐。

另一類使用者場景是 Value 實際不大,這類場景瓶頸在 CPU 上。

Q2:考慮過用傲騰作持久化記憶體作為緩衝來解決小 Value 寫入的效能問題麼?

A2:我們還沒有用到 Pmem,它的優勢首先是使用了記憶體介面響應快。

Pmem 在吞吐方面雖然優勢不是那麼的大,它可能能到,比如 6G 頻寬 Nvme 盤也可能勉強也能到,但它的介面是基於記憶體的,每次讀寫 catch line,這個開銷與塊裝置每次讀寫一個扇區相比,對小 Value 的 IOPS 提升很大。我們有計劃開發 Pmem 的原生引擎支援。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2940036/,如需轉載,請註明出處,否則將追究法律責任。

相關文章