Categories
程式開發

如何低成本實現數據庫的存儲計算分離?


2015 年,京東實現了數據庫服務的全面容器化,但遺憾的是,在線彈性伸縮和流式資源交付仍然無法實現。為了解決這兩個問題,2016 年底,京東零售存儲與計算平台部開始基於 kubernetes 和 vitess 啟動了彈性數據庫項目。

然而,彈性數據庫項目並不是解決京東數據庫問題的“銀彈”,因此,京東在數據庫服務方面仍面臨著以下挑戰:

  • 沉默成本:目前數據庫服務的所需資源還是要預先申請,而預先申請的資源在一段時間內是沒有被充分使用的,這就是沉默成本。

  • 秒級故障修復以及擴容(快速補從):目前一旦發生故障或者擴容,都是通過拷貝大量數據的方式進行補從完成的,速度比較慢。

  • 大容量數據存儲及容災:由於單機硬件上限的限制,使得單個實例可以承載的上限是有限的,而且數據量越大,災難恢復難度越大,對線上影響越大。

為了解決以上的問題,我們必須解決數據庫服務的三大問題,即存儲之殤、擴容之痛和運維之艱。因此我們在彈性數據庫的基礎上啟動了 disca(disaggregated storage and compute architecture)項目。

注:本文是在假設讀者已經了解 kubernetes 和 vitess 的基礎上進行描述的。

挑戰

存儲之殤

存儲之殤是每個數據產品都會有的,因為我們必須為未來可能產生的數據量提前準備存儲資源,這就好比我們要為將來可能要購買的商品提前付款,對企業和組織來說,相當於是增加了運營成本,而且預先購置的資源不能馬上使用,不可避免的會產生資源浪費。

彈性數據庫雖然實現了流式資源交付,但也只是將資源交付的粒度從原來的 1T 降低到了 64G。實際上,彈性數據庫只是降低了數據庫服務的成本,並沒有完全解決”沉默成本和資源孤島(資源不能共享)”的問題。

如何低成本實現數據庫的存儲計算分離? 1

如上圖所示,以前每個應用使用的數據庫存儲資源都需要提前預估好,並且數據庫服務是一次性將所有存儲資源交付。這是一種非黑即白的交付方式,要么將所有存儲資源全部交付,要么一點資源都不交付。

而彈性數據庫採用了小粒度流式交付,初始交付一部分存儲資源,當磁盤使用率達到 80% 的時候,再進行下一批存儲資源的申請和交付。這種方式相當於是小粒度批量交付,無法完全避免存儲資源的沉默成本,即提供了一部分存儲資源,但是在相當長一段之間內這部分存儲資源並沒有被完全使用。同時,由於各個數據庫實例獨享存儲,不同實例的磁盤 IO 負載壓力不同,導致某些實例 IO 極度緊張,而某些實例 IO 十分空閒。

擴容之痛

彈性數據庫雖然實現了用戶無感知的在線伸縮容,但其仍是傳統的數據庫服務,在面對實例達到硬件上限或者實例所在的宿主機無法申請到額外硬件資源的情況時,仍然需要遷移擴容。

1、在滿足擴容資源需求的宿主機上申請到容器。

2、從原來容器拷貝存量備份文件到新容器中,基於存量數據啟動 mysql 服務,與主庫構建主從關係,然後追加 binlog。

3、待新從庫與主庫一致,進行切換。若原來的實例是主庫,則先進行主從切換再刪除原來數據庫實例,否則直接刪除原來數據庫實例。

從上述過程可以看出,遷移擴容過程複雜,且需要傳輸大量數據(存量數據傳輸 + 追加 binlog),耗費時間極長。如果遇到大量數據寫入,有可能出現新加的從庫一直無法追上主庫,導致擴容還未完成,原來的數據庫實例硬盤的剩餘空間就已經被耗盡,造成數據庫宕機,無法提供服務。

根據我們的經驗,80% 的擴容和拆分都是由於數據量增大、本地磁盤不足引起的。數據量雖然一直在增大,系統使用的基本都是熱數據(近期生成的數據,這部分數據的數據量基本不會有太大變化),對於冷數據(很早的歷史數據),訪問頻度其實不高。因此為了不經常訪問或者幾乎不訪問的數據進行頻繁擴容實際上沒有太大必要性。若本地磁盤能夠足夠大,那麼這些擴容就可以避免。

運維之艱

彈性數據庫雖然具備故障自動恢復和處理,但是數據庫與存儲的耦合導致數據庫實例一旦出現故障,必須添加一個新的數據庫實例進行故障恢復。這個過程和前文提到的數據庫擴容的最壞場景一樣複雜,涉及大量數據傳輸,且在極端情況下(大量數據寫入)會出現新添實例無法追上現存 master 的情況。

解決方案

為了解決上述三大問題,我們開始研究 disca 項目,通過整合 ChubaoFS、kubernetes、MyRocks 和 Vitess,以極其簡單的方式實現了存儲計算分離。

ChubaoFS

ChubaoFS 是為大規模容器平台設計的分佈式文件系統,可以同時提供對象存儲和文件系統協議的存儲產品。該產品具備以下特點:

  • 可擴展性:ChubaoFS 中使用了分佈式元數據子系統,以便提供更高的可擴展性。

  • 多租戶:在多租戶高並發的條件下,提供了對大文件和小文件的隨機 / 順序讀寫的支持。

  • 強一致性複制:根據文件寫入方式的不同採用不同的複制協議來保障副本之間的一致性。

  • 兼容 Posix 接口:簡化上層應用的開發,降低新用戶的學習難度。同時,ChubaoFS 在實現時放鬆了對 POSIX 語義的一致性要求來兼顧文件和元文件操作的性能。

  • 兼容 S3 接口:提供與 Amazon S3 相兼容的對象存儲接口,可以和 POSIX 接口同時使用。一份數據,多種接口,根據用戶的使用場景可以靈活選擇,幫用戶應對日益複雜的存儲使用場景。

Kubernetes

Kubernetes 是由 google 開源的自動化容器編排、調度和管理系統。具備以下特點:

  • 可移植: 支持公有云、私有云、混合雲、多重雲(multi-cloud)。

  • 可擴展: 模塊化、插件化、可掛載、可組合。

  • 自動化: 自動部署、自動重啟、自動複製、自動伸縮 / 擴展。

MyRocks

MyRocks 是 MySQL 基於 RocksDB 實現的存儲引擎,更適合運行在 SSD 等高速存儲設備上,與 Innodb 相比具備節省存儲空間、高效寫入性能的優點。

Vitess

Vitess 用於實現 mysql 快速在線彈性擴容的數據庫集群系統,具備在線快速擴容、故障自愈、自帶內存池和查詢緩存的優點。

整體架構

常規彈性數據庫的架構如下圖所示:

如何低成本實現數據庫的存儲計算分離? 2

常規彈性數據庫的架構採用本地存儲,通過控制台與 kubernetes api server 交互、通過直接創建 pod 的方式進行數據庫服務部署。但是這種架構的缺陷就是:

1、一旦集群擴容或者故障恢復,由於各個數據庫實例均採用本地存儲,因此需要添加新的數據庫實例,並通過網絡傳輸數據文件和binlog,會產生大量的網絡數據傳輸,最終影響擴容以及故障恢復的時效性和穩定性。

2、採用本地磁盤導致不同實例的磁盤負載不同(容量和 IO),有的實​​例存儲資源緊缺,有的實例存儲資源空閒,存儲資源不能共享,出現“資源孤島”。

Disca 就是為了彌補常規彈性數據庫的不足而產生的,其架構如下圖所示:

如何低成本實現數據庫的存儲計算分離? 3

從圖中我們可以看出,與常規彈性數據庫相比有如下改變:

1、不再使用宿主機的本地磁盤,而使用 ChubaoFS,這樣每個數據庫實例與本地磁盤完全解耦。

2、從原來的 MySQL innodb 引擎轉變為 MyRocks 引擎。通過測試以及線上服務經驗,我們發現 ChubaoFS 在批量追加寫入的場景下表現出來的性能更加優異,而 MyRocks 就是採用 LSM Tree,以批量追加 / 覆蓋的方式進行數據寫入。兩者結合可以發揮最大的性能優勢。

3、拋棄原來採用Pod 進行數據庫實例,集群的編排、擴容和調度工作完全交給控制台的方式,而是充分利用kubernetes 的能力進行服務編排和調度,通過statefulset 進行數據庫實例管理、編排和調度,通過pvc 實現數據庫實例與數據的粘性,進而保證數據不丟。

實現方案

透明集成 ChubaoFS

為了實現ChbuaoFS 對用戶的透明,ChubaoFS 就需要作為Kubernetes 可以自動識別的遠程分佈式存儲服務存在,因此ChubaoFS 實現了kuberne 的CSI(Container Storage Interface), 從而實現與kubernetes 的無縫集成和基於POD 的動態掛載。

存儲共享

在使用 ChubaoFS 時,申請的每個 Volume 的大小是配額記錄信息,不會預先分配資源,也不會以此來限制用戶使用的存儲上限。比如:ChubaoFS 總體的存儲空間是1TB,有十個數據庫實例去申請ChubaoFS 的空間,每個數據庫實例申請的PV(persistent volumes)的大小都可以是1TB,只要十個數據庫實例實際使用的磁盤之和不超過1TB 即可。而實際每個用戶使用的磁盤空間的大小,由 ChubaoFS 的資源管理節點進行統一監控,一旦總體使用磁盤容量超過總容量的 80%,ChubaoFS 資源管理節點會第一時間監控到,並進行報警。

感謝 ChubaoFS 共享磁盤的設計,disca 可以為每個數據庫實例申請比較大的磁盤(默認:2TB),而不用擔心浪費或者超過集群總容量。從而可以減少數據庫實例因為磁盤空間不足而不得不進行拆分或者擴容的情況。

保證數據粘性

數據庫服務不同於應用服務,應用服務本地存儲的一般是日誌文件,其所屬 pod 重啟或重新調度後,本地數據可以被刪除。而數據庫服務在重啟或者重新調度後希望原來的數據繼續保留和使用,這就是數據粘性:當某個數據庫實例所在的pod 重啟或者重新調度,該數據庫實例以前的數​​據不會丟失,並且在數據庫重啟或者重新調度到另一個Node 上時,可以重新使用以前的數據。

要實現數據粘性,我們需要使用 kubernetes 中的三種資源:statefulset、pvc(persistent volume claim)和 pv。 statefulset 中的每個 pod 都是通過 volumeClaimTemplates 來動態地生成與 pod 一一對應的 pv 和 pvc,並且 statefulset 還保證了每個 pod 的唯一性和順序性,三種資源的關聯關係如下圖所示:

如何低成本實現數據庫的存儲計算分離? 4

所以通過將這三類資源搭配使用可以實現數據粘性,代碼片段如下:

如何低成本實現數據庫的存儲計算分離? 5

一鍵服務部署

採用 helm 基於 statefulset、pv、pvc 以及 storageclass 完成彈性數據庫 On ChubaoFS 的一鍵化部署,如下圖所示:

如何低成本實現數據庫的存儲計算分離? 6

針對圖中的流程,進行詳細說明:

1、每次用戶向控制台發起創建 keyspace 請求時,會攜帶請求參數,這些參數包括:副本數、存儲引擎類型、資源類型、資源數等。

2、控制台首先檢查本地是否有對應的charts 模板,若有,則進行使用請求中攜帶的參數進行替換;否則從charts 中心(託管charts 文件,目前我們是基於httpd 部署的服務,並且掛載ChubaoFS以保證charts 文件的安全和容災)下載並緩存到本地,然後執行參數替換。

說明:由於 charts 模板一旦生成,幾乎不再變化,因此 charts 中心的 charts 文件由管理員進行不定期更新。

3、基於參數替換後的 charts 文件在 IDC1 中創建 replica 類型的 statefulset 實例:sts1,該實例中包含兩個 mysql 實例:master 和 slave1,並且 slave1 與 master 自動創建主從復制關係。 mysql 實例通過 pvc 直接綁定 ChubaoFS 中的 pv 到本地。

說明:vitess 中的數據庫節點分為兩類:replica 類型和 readonly 類型。其中 replica 類型的節點是指 master 以及與 master 同機房的 slave 節點。 readonly 類型節點是指與 master 不在同一個機房的 slave 節點。針對每種類型的節點都有一種 statefulset 與其對應。因此創建一個具備跨機房容災的數據庫集群至少需要為每種類型的 statefulset 資源都創建一個實例。

4、master 和 slave1 節點一旦服務啟動,會自動去 etcd 註冊自己的元數據:所屬 keyspace、shard、tablet 類型、主從關係等。

5、基於參數替換後的 charts 文件在另一個機房 IDC2 中創建 readonly 類型的 statefulset 實例:sts2,該實例中包含一個 mysql 實例:slave2,並自動將 salve2 與 master 建立主從復制關係。

6、slave2 節點一旦啟動,會自動去 etcd 註冊自己的元數據:所屬 keyspace、shard、tablet 類型、主從關係等。

收益

數據安全

ChubaoFS 保證了數據的永久留存和分佈式存儲,通過statefulset 並且配合MyRocks 的事務提交同步刷盤設置,可以實現每次事務的寫入數據都可以在事務提交時寫入到分佈式存儲中,進而使得MyRocks 可以在任何故障情況下保證數據安全性。

節省成本

如何低成本實現數據庫的存儲計算分離? 7

雖然彈性數據庫與傳統 mysql 數據庫相比已經節省了成本,但是在彈性數據庫的基礎上 disca 還將進一步優化,將成本節省到極致:

1、傳統的數據庫架構中,從庫的作用是提升讀能力和數據容災。但是在 disca 中使用 ChubaoFS 實現了數據容災,所以就沒有必要再通過從庫進行數據容災,從庫的唯一價值只剩下提升讀能力。因此對於讀流量不高的業務也就沒有必要添加從庫,僅僅通過單個 master 就可以實現數據容災,節省了多個從庫佔用的額外資源。

2、通過 ChubaoFS 實現存儲容量共享,避免存儲碎片和資源預購,真正實現了存儲資源的流式提供和交付,極大地節省了存儲資源成本。

3、由於ChubaoFS 的整合IO 能力,打破了傳統方式IO 壁壘,所有應用都可以充分共享和利用集群的所有IO 能力,徹底避免了業務A IO 吃緊,而業務B IO 空閒的場景,通過IO 共享,避免了資源浪費。

4、由於 ChubaoFS 實現了可擦除編碼,因此平均數據副本只有 1.5 左右,進一步降低了存儲成本。

服務質量

傳統的故障切換分為兩種:主庫故障切換和從庫故障切換,disca 提升了兩種故障切換場景的效率。

主庫故障恢復

傳統主從架構的主庫故障切換分為兩步:切主和補從。在切主過程中 disca 發揮的作用不大,但其重新定義了補從過程。

傳統補從過程:基於以前靜態備份(通常是一天以前)啟動一個新的 mysql 實例,並作為 slave 節點加入到集群中,然後應用從靜態備份時間以後的所有 binlog,直到追上 master 的狀態。這個追加 binlog 的過程極其漫長。

Disca 補從過程:基於 statefulset 進行自動補從。針對宕機的mysql,master 會自動拉起一個mysql 實例,mysql 實例會查找自己所屬集群的最新master,然後自己作為從庫加入到集群,由於故障master 通過pvc 已經與ChubaoFS 的PV 建立了存儲粘性關係,因此新啟動的mysql 實例會將故障master 的pv 繼續掛載新的容器上,只需要對宕機恢復期間(通常數秒時間)delay 的binlog 進行追加即可,極大降低了故障恢復的補從時間。

從庫故障恢復

當一個從庫出現故障,傳統的處理方式是基於一份靜態備份數據新建一個 slave,然後追加從上一次靜態備份時間以後所有 binlog。

而disca 在從庫故障後,會自動拉起一個新的slave 實例,但是不會基於靜態備份+ 大量binlog 追加的模式進行從庫數據恢復,而是直接將故障從庫的pv 掛載到新的slave 實例上,然後追加故障恢復期間的少量binlog,極大地縮短了從庫故障的恢復時間。

讀寫性能

讀性能

在相同配置參數、相同 CPU 和內存資源,不同並發場景和不同網絡條件的情況下,我們對 disca 和本地 MyRocks 的讀性能進行了對比測試,測試結果如下:

如何低成本實現數據庫的存儲計算分離? 8

從上圖可以看出:

TPS:在並發低於 256 的時候, disca 比 myrocks local 略高,在 256 並發下,discs 開始低於 myrocks local。

TP95:在不同並發條件下,disca 的 TP95 與 myrocks local 的幾乎持平。

因此 disca 在讀性能上,相對於本地存儲不具備明顯優勢。

寫性能

無論是local 還是disca 模式,我們測試發現在並發為512 條件下,雙方寫入性能最優,因此我們在512 並發條件下,針對影響寫入性能的不同參數組合場景進行了寫入性能對比測試,測試結果如下:

如何低成本實現數據庫的存儲計算分離? 9

從上圖可以看出:我們分別對本地 nvme 環境、ChubaoFS-sata 集群環境和 ChubaoFS-ssd 集群環境進行了對比測試,結論如下:

TPS:disca 的平均 TPS 比 Myrocks local 的平均 TPS 高出 19.1%。

TP95: disca 的平均 TP95 是 myrocks local 的平均 TP95 的 44.7%。

Disca 與本地 nvme 環境下 myrocks 相比,在寫性能上優勢比較明顯。

結束語

通過集成 MyRocks、ChubaoFS、Vitess 和 Kubernetes,我們探索出了一條更為簡單易實現的存儲計算道路,而且這種方案對於大部分中小企業來說都可以快速落地實施。相比於參考 aurora 論文去修改 mysql 源碼的方式,這種方式節省了大量的研發投入,降低了技術門檻。

通過 disca,我們也得到了切實的好處:大幅降低存儲成本、提升寫入性能,實現了快速故障處理和數據容災。

需要注意的是,從最後的測試數據我們可以看到,disca 也不是一枚銀彈,它更適合於高並發寫入的場景。

作者簡介:

呂信,京東零售數據庫技術部負責人,先後主導並從事大數據產品以及數據庫產品研發,目前專注於京東數據庫存儲計算分離產品的設計與研發。