數據庫存儲層原來是這麼工作的?

大家好, 我是魚皮, 最近有一些在校的同學問到,在實際中,分佈式數據庫中存儲層工作內容是什麼樣的?下面給大家分享一篇總結文章。

:限定下討論範圍,分佈式數據庫,存儲計算分離,share-noting 架構,僅討論存儲層。

存儲層涉及的東西很龐雜,想說清楚,需要有一個合適的切入角度。數據庫最本質的功能,是存儲數據,以對外提供數據的查詢寫入接口。不妨,就首先以這兩條線串一下各個模塊,然後再補充下不能歸到這兩條線中的一些組件。

查詢

查詢請求進到存儲層,一般表現爲下推的執行計劃,進而轉化爲對底層存儲引擎的單點查詢和範圍查詢,爲了加速查詢,一般會給存儲引擎配備緩存層。對於每個存儲節點來說,爲了應對大量的併發請求,需要做 IO 優化

執行計劃

這是存儲層的入口,是存儲層向查詢層暴露的接口。

一個查詢語句經過查詢層的語法分析(Parser)、語義檢查(Validator)、生成計劃(Planner)、計劃優化(Optimizer)、執行計劃(Executor)幾個步驟之後,會將需要下推給存儲層的算子下發到存儲層對應的分片( Partition)所在節點。

對於火山模型來說,我們可以將執行計劃理解爲一個由基本算子(Executor)組成的 DAG,甚至再簡化一些可以想象成一棵樹。樹中下層的一些小子樹,是可以直接推到存儲層對應的節點去執行的,這些可以下推的算子通常包括:TableScan,Filter,Project,Limit,TopN 等等。

存儲層拿到這些執行計劃後,反序列化,組織成內存中的執行計劃,以迭代模型 [1] 或者向量模型,來對數據進行掃描、過濾、排序、投影、聚合等操作後,將結果集返回給查詢層。

結果集可以有幾種返回方式:

  1. 一次全量返回

  2. 流式返回

  3. 分頁返回

計算下推有諸多好處:

  1. 充分利用存儲層的分佈式節點進行預計算。

  2. 減少存儲層到查詢層的數據傳輸帶寬消耗。

  3. 提高查詢層的處理速度和數據集上限。

緩存

爲了對查詢進行優化,對於讀多寫少的場景,一般會在存儲引擎之上罩一個緩存層。如果是共享存儲層的架構,比如存儲層在雲上,那麼緩存層就必不可少。

緩存在設計時,主要需要考慮緩存粒度生命週期兩方面。

  1. 緩存粒度。爲了保持緩存和後端的數據一致性,勢必需要加鎖,而緩存粒度和加鎖粒度息息相關。一個節點上的不同 Partition 的緩存要不要共享一個緩存池,也是緩存粒度需要考慮的問題。

  2. 生命週期。何時寫入後端,何時讓緩存失效,這涉及到緩存控制策略,是同步讀寫穿透,還是異步更新,都是需要根據實際情況考量的問題。

RPC IO 優化

任何服務都是類似的,大量請求過來時,得用線程池、異步、協程等各種手段優化,提高併發,從而提高吞吐,減小延遲。

有的 RPC 框架能解決這些問題,比如有些 RPC 框架內置協程模型,支持 M 比 N 模型、協程竊取等等。如果 RPC 框架不管,就需要用額外的線程池庫、異步庫(promise、future)、協程庫來手動控制請求的執行流併發執行。

寫入

分佈式系統中,一般會使用多副本來存儲數據。在寫入時,爲了維持所有副本看到一致的寫入順序,會引入共識算法。共識算法通常都是維持一個邏輯上 endless 的邏輯操作日誌,然後每個副本將邏輯日誌應用到自己本地的狀態機——存儲引擎。在寫入數據時,需要對用戶數據進行數據編碼,轉化爲二進制串,從而寫入存儲引擎。對於一些一致性(區別於多副本間的一致,此處是多語句間併發執行的一致)要求嚴苛的場景,數據庫需要對用戶提供多個語句原子化執行的保證,即分佈式事務

共識算法

對於 share-nothing 架構,爲了保證高可用,都會使用多副本(Replication),並放到容錯閾不同的多臺機器上。使用多副本,就自然會引入多副本數據一致性的問題,一般我們會使用共識算法(Raft、MultiPaxos)來解決。

使用共識算法,對於每個數據分片(Partition),可以維護一個多機一致的操作日誌(operation log,WAL):即所有寫入操作,都會序列化成操作日誌記錄,並在所有的副本按唯一的順序進行追加寫。有了一致的操作日誌,我們再將其各種應用到本地的狀態機(也就是存儲引擎),輔以 log id,就可以對外提供一致的讀寫視圖。

存儲引擎

這裏指的是單機存儲引擎,也就是上文所說的狀態機。它解決的問題是,如何將數據組織在單機的存儲體系中,以最少的空間,應對特定場景的高效的寫入和讀取。一般分爲數據編碼、索引組織、併發控制等等幾個子模塊。

存儲引擎主要分爲兩個流派:原地更新的 B-Tree 流派和基於追加的 LSM-Tree 流派。這裏推薦兩個個學習的項目,B-Tree 的可以看看 BoltDB[2];LSM-Tree 可以看看 LevelDB[3]。但實際使用中會用更復雜強大一點的變種,比如 RocksDB。

對於 AP 場景來說,一般使用列式存儲,可以更方便的進行數據壓縮和進行向量化計算。

數據編碼

數據編解碼解決的問題是,如何將邏輯上的一個記錄(如關係型數據庫中的 Row),高效(耗時少、佔空間少)的編碼爲二進制串,寫入存儲引擎。

在編碼時,需要考慮和 Schema (該行有哪些字段,字段的類型是什麼)的對應關係,也要考慮在 Schema 變化時(加字段,刪字段,改字段類型),如何保證數據讀取的兼容性。

分佈式事務

數據庫的一大重要功能就是對事務的保證,利用事務模型的諸多保證(ACID),可以大大減小用戶側使用數據庫的複雜度。當然,這通常是以損失性能爲代價的,在分佈式數據庫中這點尤爲明顯。

如何保證分佈式事務間的原子性和隔離性,業界有諸多方案。最基本的框架是兩階段提交配合全局時鐘(有物理時鐘、邏輯時鐘、混合時鐘和 TSO 等多種解決方案,又是一個比較大的話題),比較經典的是有谷歌的 Percolator 模型。

其他模塊

除了能直接歸到讀寫流程相關的組件,還有一些其他存儲層交互比較頻繁的模塊和一些後臺運行的常駐進程。

Schema 管理

如何劃分命名空間,組織不同的 Schema,就涉及到 Schema 的邏輯管理,如使用樹形組織。

另外,還需要維護 Schema 和數據的對應關係,但在分佈式系統中,如何非阻塞的修改 Schema, 而不影響併發的數據寫入,是一個非常費勁的事情。常見的解決方案有谷歌 F1 的 online DDL。

集羣元信息

集羣元信息主要分兩大塊:

  1. 邏輯上。邏輯上的數據集組織與劃分,比如 Database、Table。即以合適的粒度,對數據集按命名空間進行劃分,進而針對不同的數據集進行不同的配置以及相應的多租戶隔離和權限控制。

  2. 物理上。物理上的節點的組織與劃分,比如 Zone,Node。即以合適的容錯閾,對不同節點進行物理組織,進而在不同節點和容錯閾間處理宕機、均衡數據。

管理邏輯數據到物理節點的映射,即是分佈式系統中最重要的一個方面:調度

調度通常發生在兩個大時刻,一是數據集創建時,一是副本再均衡時(rebalancing,包括機器宕機、新增節點引起的數據再均衡)。

我們會依據節點的不同屬性(容錯閾、剩餘容量)等對數據集的不同分片進行調度。在進行數據移動時,會涉及分片的多個副本的增刪,爲了保證一致性,也需要通過共識協議來完成。

數據導入導出

數據庫最重要的周邊工具就是支持數據以豐富的格式、較高的速度進行導入和導出。

這又可以細分爲幾類:

  1. 數據備份與恢復。即數據生產者和消費者都是本數據庫,此時不用考慮支持不同的的數據格式(即可以自定義編碼,只需要自己認識即可,因此可以怎麼高效怎麼來),而是要考慮支持不同的數據後端:本地、雲上、共享文件系統中等等。同時,也要考慮同時支持全量備份和增量備份。

  2. 其他系統導入。需要考慮支持多種數據源以及不同數據格式,最好能使用一些計算框架(如 Spark、Flink、Kafka)分佈式的導入;也最好能夠支持主流的數據庫接入,比如 MySQL、Postgres 等等。

  3. 數據導出。將數據導出爲多種通用的數據格式,如 csv、json、sql 語句 等等。

倉促成文,遺漏之處,歡迎在評論區補充。

以上就是本期分享了, 感謝原作者授權轉載:

木鳥雜記 分佈式系統、分佈式存儲、攝影分享、讀書筆記

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/W19idyJ9rnHDuYvYpcPpOA