Kudu:一個融合低延遲寫入和高性能分析的存儲系統
Kudu 是一個基於 Raft 的分散式存儲系統,它致力於融合低延遲寫入和高性能分析這兩種場景,並且能很好的嵌入到 Hadoop 生態系統裡面,跟其他系統譬如 Cloudera Impala,Apache Spark 等對接。
Kudun 很類似 TiDB。最開始,TiDB 是為了 OLTP 系統設計的,但後來發現我們 OLAP 的功能也越來越強大,所以就有了融合 OLTP 和 nOLAP 的想法,當然這條路並不是那麼容易,我們還有很多工作要做。因為 Kudu 的理念跟我們類似,所以我也很有興趣去研究一下它,這裡主要是依據n Kudu 在 2015 發布的 paper,因為 Kudu 是開源的,並且在不斷的更新,所以現在代碼裡面一些實現可能還跟 paper n不一樣了,但這裡僅僅先說一下我對 paper 的理解,實際的代碼我後續研究了在詳細說明。
為什麼需要 Kudu?
結構化數據存儲系統在 Hadoop 生態系統裡面,通常分為兩類:
靜態數據,數據通常都是使用二進位格式存放到n HDFS 上面,譬如 Apache Avro,Apache Parquet。但無論是 HDFS n還是相關的系統,都是為高吞吐連續訪問數據這些場景設計的,都沒有很好的支持單獨 record 的更新,或者是提供好的隨機訪問的能力。
動態數據,數據通常都是使用半結構化的方式存儲,譬如 Apache HBase,Apache Cassandra。這些系統都能低延遲的讀寫單獨的 record,但是對於一些像 SQL 分析這樣需要連續大量讀取數據的場景,顯得有點捉緊見拙。
上面的兩種系統,各有自己的側重點,一類是低延遲的隨機訪問特定數據,而另一類就是高吞吐的分析大量數據。之前,我們並沒有這樣的系統可以融合上面兩種情況,所以通常的做法就是使用n pipeline,譬如我們非常熟悉的 Kafka,通常我們會將數據快速寫到 HBase 等系統裡面,然後通過 npipeline,在導出給其它分析系統。雖然我們在一定層面上面,我們其實通過 pipeline n來對整個系統進行了解耦,但總歸要維護多套系統。而且數據更新之後,並不能直接實時的進行分析處理,有延遲的開銷。所以在某些層面上面,並不是一個很好的解決方案。
Kudun 致力於解決上面的問題,它提供了簡單的來處理數據的插入,更新和刪除,同時提供了 table scan n來處理數據分析。通常如果一個系統要融合兩個特性,很有可能就會陷入兩邊都做,兩邊都沒做好的窘境,但 Kudu n很好的在融合上面取得了平衡,那麼它是如何做到的呢?
Keyword
Tables 和 schemas
Kudun 提供了 table 的概念。用戶可以建立多個 table,每個 table 都有一個預先定義好的 schema。Schema 裡面定義了這個 ntable 多個 column,每個 column 都有名字,類型,是否允許 null 等。一些 columns 組成了 primary nkey。
可以看到,Kudun 的數據模型非常類似關係資料庫,在使用之前,用戶必須首先建立一個 table,訪問不存在的 table 或者 column n都會報錯。用戶可以使用 DDL 語句添加或者刪除 column,但不能刪除包含 primary key 的 column。
但在 Paper 裡面說到 Kudu 不支持二級索引以及除了 primary key 之外的唯一索引,這個後續可以通過更新的代碼來確定下。
其實我這裡非常關注的是 Kudu 的 Online DDL 是如何做的,只是 Paper 裡面貌似沒有提及,後面只能看代碼了。
API
Kudu 提供了 Insert,Update 和 Delete 的 write API。不支持多行事務 API,這個不知道最新的能支持了沒有,因為僅僅能對單行數據操作,還遠遠不夠。
Kudu 提供了 Scan read API 讓用戶去讀取數據。用戶可以指定一些特定的條件來過濾結果,譬如用一個常量跟一個 column 裡面的值比較,或者一段 primary key 的範圍等條件。
提供 API 的好處在於實現簡單,但對於用戶來說,其實更好的使用方式仍然是 SQL,一些複雜的查詢最好能通過 SQL 搞定,而不是讓用戶自己去 scan 數據,然後自己組裝。
一致性模型
Kudu 提供兩種一致性模型:snapshot consistency 和 external consistency。
默認n Kudu 提供 Snapshot consistency, 它具有更好的讀性能,但可能會有 write skew 問題。而 External nconsistency 則能夠完全保證整個系統的 nlinearizability,也就是當寫入一條數據之後,後面的任何讀取都一定能讀到最新的數據。
為了實現 External consistency,Kudu 提供了幾種方法:
在n clients 之間顯示的傳遞時間戳。當寫入一條數據之後,用戶用要求 client 去拿一個時間戳作為 token,然後通過一個 nexternal channel 的方式傳遞給另一個 client。然後另一個 client 就可以通過這個 token n去讀取數據,這樣就一定能保證讀取到最新的數據了。不過這個方法實在是有點複雜。
提供類似n Spanner 的 commit-wait 機制。當寫入一條數據之後,client 需要等待一段時間來確定寫入成功。Kudu 並沒有採用 nSpanner TrueTime 的方案,而是使用了 HybridTime 的方案。HybridTime 依賴 NTP,這個可能導致 wait n的時間很長,但 Kudu 認為未來隨著 read-time clock 的完善,這應該不是問題了。
Kudun 是我已知的第二個採用 HybridTime 來解決 External consistency 的產品,第一個當然就是 CockroachDB n了。TiDB 跟他們不一樣,我們採用的是全局授時的方案,這個會簡單很多,但其實也有跟 PD 交互的網路開銷。後續TiDB 可能使用類似 nSpanner 的 GPS + 原子鐘,現階段相關硬體的製造方式 Google n並沒有說明,但其實難度不大。因為已經有很多硬體廠商主動找我們希望一起合作提供,只是比較貴,而現階段我們大多數客戶並沒有跨全球事務這種場景。
Kudun 的一致性模型依賴時間戳,這應該是現在所有分散式系統通用的做法。Kudu n並沒有給用戶保留時間戳的概念,主要是覺得用戶很可能會困惑,畢竟不是所有的用戶都能很好的理解 MVCC 這些概念。當然,對於 read nAPI,還是允許用戶指定特定的一個時間戳,這樣就能讀取到歷史數據。這個 TiDB 也是類似的做法,用戶不知道時間戳,只是我們額外提供了一個設置 nsnapshot 的操作,讓用戶指定生成某個時間點的快照,讀取那個時間點的數據。這個功能已經幫很多公司恢復了因為錯誤操作寫壞的數據了。
架構
上面說了一些 Kudu 的 keyword, 現在來說說 Kudu 的整體架構。Kudu 類似 GFS,提供了一個單獨的 Master 服務,用來管理整個集群的元信息,同時有多個 Tablet 服務,用來存儲實際的數據。
分區
Kudun 支持對數據按照 Range 以及 Hash 的方式進行分區。 每個大的 table 都可以通過這種方式將數據分不到不同的 Tablet n上面。當用戶創建一個表的時候,同時也可以指定特定的 partition schema,partition schema 會將 primary nkey 映射成對應的 partition key。每個 Tablet 上面會覆蓋一段或者多段 partition keys 的range。當 nclient 需要操作數據的時候,它可以很方便的就知道這個數據在哪一個 Tablet 上面。
一個 partition schema 可以包括 0 或者多個 hash-partitioning 規則和最多一個 range-partitioning 規則。用戶可以根據自己實際的場景來設置不同的 partition 規則。
譬如有一行數據是 (host, metric, time, value),timen 是單調遞增的,如果我們將 time 按照 hash 的方式分區,雖然能保證數據分散到不同的 Tablets n上面,但如果我們想查詢某一段時間區間的數據,就得需要全部掃描所有的 Tablets 了。所以通常對於 time,我們都是採用 range n的分區方式。但 range 的方式會有 hot range 的問題,也就是同一個時間會有大量的數據寫到一個 range 上面,而這個 hot nrange 是沒法通過 scale out 來緩解的,所以我們可以將 (host, metric) 按照 hash 分區,這樣就在 write 和 read 之間提供了一個平衡。
通過多個 partition 規則組合,能很好的應對一些場景,但同時這個這對用戶的要求比較高,他們必須更加了解 Kudu,了解自己的整個系統數據會如何的寫入以及查詢。現在 TiDB 還只是單純的支持 range 的分區方式,但未來不排除也引入 hash。
Raft
Kudu 使用 Raft 演算法來保證分散式環境下面數據一致性,這裡就不再詳細的說明 Raft 演算法了,因為有太多的資料了。
Kudu 的 heartbeat 是 500 毫秒,election timeout 是 1500 毫秒,這個時間其實很頻繁,如果 Raft group 到了一定量級,網路開銷會比較大。另外,Kudu 稍微做了一些 Raft 的改動:
使用了 exponential back-off 演算法來處理 leader re-election 問題。
當一個新的 leader 跟 follower 進行交互的時候,Raft 會嘗試先找到這兩個節點的 log 分叉點,然後 leader 再從這個點去發送 log。Kudu 直接是通過 committedIndex 這個點來發送。
對於 membership change,Kudu 採用的是 one-by-one 演算法,也就是每次只對一個節點進行變更。這個演算法的好處是不像 joint consensus 那樣複雜,容易實現,但其實還是會有一些在極端情況下面的 corner case 問題。
當添加一個新的節點之後,Kudu 首先要走一個 remote bootstrap 流程。
將新的節點加入到 Raft 的 configuration 裡面
Leader 發送 StartEmoteBootstrap RPC,新的 follower 開始拉去 snapshot 和之後的 log
Follower 接受完所有數據並 apply 成功之後,開始響應 Raft RPC
可以看到,這個流程跟 TiKV 的做法類似,這個其實有一個缺陷的。假設我們有三個節點,加入第四個之後,如果新的節點還沒 apply 完 snapshot,這時候掛掉了一個節點,那麼整個集群其實是沒法工作的。
為了解決這個問題,Kudu 引入了 PRR_VOTER 概念。當新的節點加入的時候,它是 PRE_VOTE 狀態,這個節點不會參與到 Raft Vote 裡面,只有當這個節點接受成功 snapshot 之後,才會變成 VOTER。
當刪除一個節點的時候,Leadern 直接提交一個新的 configuration,刪除這個節點,當這個 log 被 committed n之後,這個節點就把刪除了。被刪除的節點有可能不知道自己已經被刪除了,如果它長時間沒有收到其他的節點發過來的消息,就會問下 Master n自己還在不在,如果不在了,就自己幹掉自己。這個做法跟 TiKV 也是類似的。
Master
Kudu 的 Master 是整個集群最核心的東西,類似於 TiKV 裡面的 PD。在分散式系統裡面,一些系統採用了無中心化的架構設計方案,但我個人覺得,有一個中心化的單點,能更好的用全局視角來控制和調度整個系統,而且實現起來很簡單。
在n Kudu 裡面,Master 自己也是一個單一的 Tablet ntable,只是對用戶不可見。它保存了整個集群的元信息,並且為了性能,會將其全部緩存到內存上面。因為對於集群來說,元信息的量其實並不大,所以在很長一段時間,Mastern 都不會有 scale 的風險。同時 Master 也是採用 Raft 機制複製,來保證單點問題。
這個設計其實跟n PD 是一樣的,PD 也將所有的元信息放到內存。同時,PD 內部集成 etcd,來保證整個系統的可用性。跟 Kudu Master n不一樣的地方在於,PD 是一個獨立的組件,而 Kudu 的 Master 其實還是集成在 Kudu 集群裡面的。
Kudu 的 Master 主要負責以下幾個事情:
Catalog manager
Mastern 的 catalog table 會管理所有 table 的一些元信息,譬如當前 table schema 的版本,table 的 nstate(creating,running,deleting 等),以及這個 table 在哪些 Tables 上面。
當用戶要創建一個n table 的時候,首先 Master 在 catalog table 上面寫入需要創建 table 的記錄,table 的 state 為 nCREATING。然後非同步的去選擇 Tablet servers 去創建相關的元信息。如果中間 Master 掛掉了,table 記錄裡面的 nCREATING state 會表明這個 table 還在創建中,新的 Master leader 會繼續這個流程。
Cluster coordinator
當 Tablet server 啟動之後,會給 Master 註冊,並且持續的給 Master 進行心跳彙報消後續的狀態變化。
雖然n Master 是整個系統的中心,但它其實是一個觀察者,它的很多信息都需要依賴 Tablet server 的上報,因為只有 Tablet nserver 自己知道當前自己有哪一些 tablet 在進行 Raft 複製,Raft 的操作是否執行成功,當前 tablet 的版本等。因為 nTablet 的狀態變更依賴 Raft,每一次變更其實就在 Raft log 上面有一個對應的 index,所以上報給 Master n的消息一定是冪等的,因為 Master 自己會比較 tablet 上報的 log index 跟當前自己保存的 index,如果上報的 log nindex 是舊的,那麼會直接丟棄。
這個設計的好處在於極大的簡化了整個系統的設計,如果要n Master 自己去負責管理整個集群的狀態變更,譬如 Master 給一個 tablet n發送增加副本的命令,然後等待這個操作完成,在繼續處理後面的流程。整個系統光異常處理,都會變得特別複雜,譬如我們需要關注網路是不是斷開了,超時了到底是成功了還是失敗了,要不要再去n tablet 上面查一下?
相反,如果n Master 只是給 tablet 發送一個添加副本的命令,然後不管了,剩下的事情就是一段時間後讓 tablet n自己上報回來,如果成功了繼續後面的處理,不成功則嘗試在加一次。雖然依賴 tablet 的上報會有延遲(通常情況,只要有變動,tablet n會及時的上報通知,所以這個延遲其實挺小的),整個架構簡單了很多。
其實看到這裡的時候,我覺得非常的熟悉,因為我們也是採用的這一套架構方案。最開始設計n PD 的時候,我們還設想的是 PD 主動去控制 TiKV,也就是我上面說的那套複雜的發命令流程。但後來發現實在是太複雜了,於是改成 TiKV n主動上報,這樣 PD 其實就是一個無狀態的服務了,無狀態的服務好處就是如果掛了,新啟動的 PD n能立刻恢復(當然,實際還是要做一些很多優化工作的)。
Tablet directory
因為 Master 知道集群所有的信息,所以當 client 需要讀寫數據的時候,它一定要先跟 Master 問一下對應的數據在哪一個 Tablet server 的 tablet 上面,然後才能發送對應的命令。
如果每次操作都從 Master 獲取信息,那麼 Master 鐵定會成為一個性能瓶頸,鑒於 tablet 的變更不是特別的頻繁,所以很多時候,client 會緩存訪問的 tablet 信息,這樣下次再訪問的時候就不用從 Master 再次獲取。
因為 tablet 也可能會變化,譬如 leader 跑到了另一個 server 上面,或者 tablet 已經不在當前 server 上面,client 會收到相關的錯誤,這時候,client 就重新再去 Master 獲取一下最新的路由信息。
這個跟我們的做法仍然是一樣的,client 緩存最近的路由信息,當路由失效的時候,重新去 PD 獲取一下。當然,如果只是單純的 leader 變更,其實返回的錯誤裡面通常就會帶上新的 leader 信息,這時候 client 直接刷新緩存,在直接訪問了。
Tablet storage
Tablet server 是 Kudu 用來存放實際數據的服務,為了更好的性能,Kudu 自己實現了一套 tablet storage,而沒有用現有的開源解決方案。Tablet storage 目標主要包括:
快速的按照 Column 掃描數據
低延遲的隨機更新
一致的性能
RowSets
Tabletsn 在 Kudu 裡面被切分成更小的單元,叫做 RowSets。一些 RowSets 只存在於內存,叫做 MemRowSets,而另一些則是使用 ndisk 和 memory 共享存放,叫做 DiskRowSets。任何一行數據只存在一個 RowSets 裡面。
在任何時候,一個 tablet 僅有一個單獨的 MemRowSet 用來保存最近插入的數據。後台有一個線程會定期的將 這些 MemRowSets 刷到 disk 上面。
當一個n MemRowSet 被刷到 disk 之後,一個新的空的 MemRowSet 被創建出來。之前的 MemRowSet 在刷到 disk n之後,就變成了 DiskRowSet。當刷的同時,如果有新的寫入,仍然會寫到這個正在刷的 MemRowSet 上面,Kudu n有一套機制能夠保證新寫入的數據也能一起被刷到 disk 上面。
MemRowSet
MemRowSet 是一個支持並發,提供鎖優化的 B-tree,主要基於 MassTree,也有一些不同:
因為 Kudu 使用的是 MVCC,所以任何的刪除其實也是插入,所以這個 tree 沒有刪除操作。
不支持任意的 in-place 數據變更操作,除非這次操作不會改變 value 的大小。
將 Leaf link 起來,類似 B+-tree,這樣對於 scan 會有明顯的性能提升。
並沒有完全實現 trie of trees,是只是使用了一個單一 tree,因為 Kudu 並沒有太多高頻隨機訪問的場景。
DiskRowSet
當n MemRowSets 被刷到 disk 之後,就變成了 DiskRowSets。當 MemRowSets 被刷到 disk 的時候,Kudu n發現超過 32 MB 了就滾動一個新的 DiskRowSet。因為 MemRowSet 是順序的,所以 DiskRowSets n也是順序的,各滾動的 DiskRowSet 裡面的 primary keys 都是不相交的。
一個n DiskRowSet 包含 base data 和 delta data。Base data 按照 column n組織,也就是通常我們說的列存。各個 column 會被獨立的寫到 disk 裡面一段連續的 block 上面,數據會被切分成多個 npage,使用一個 B-tree 進行高效索引。
除了刷用戶自定義的 column,Kudu 還默認將 primary key index 寫到一個 column,同時使用 Bloom filter 來保證能快速通過找到 primary key。
為了簡單,當n column 的數據刷到 disk,它就是默認 immutable 的了,但在刷的過程中,有可能有更新的數據,Kudu 將這些數據放到一個 ndelta stores 上面。Delta stores 可能在內存 DeltaMemStores,或者 disk DeltaFiles。
Delta store 維護的一個 map,key 是 (row_offset, timestamp),valuen 就是 RowChangeList 記錄。Row offset 就是 row 在 RowSet 裡面的索引,譬如,有最小 primary keyn 的 row 在 RowSet 裡面是排在最前面的,它的 offset 就是 0。Timestamp 就是通常的 MVCC timestamp。
當需要給n DiskRowSet 更新數據的時候,Kudu 首先通過 primary key 找到對應的 row。通過 B-tree 索引,能知道哪一個 npage 包含了這個 row,在 page 裡面,可以計算 row 在整個 DiskRowSet 的 offset,然後就把這個 offset n插入到 DeltaMemStore 裡面。
當 DeltaMemStore 超過了一個閥值,一個新的 DeltaMemStore 就會生成,原先的就會被刷到 disk,變成 immutable DeltaFile。
每個n DiskRowSet 都有一個 Bloom filter,便於快速的定位一個 key 是否存在於該 DiskRowSet n裡面。DIskRowSet 還保存了最小和最大的 primary key,這樣外面就能通過 key 落在哪一個 key range n裡面,快速的定位到這個 key 屬於哪一個 DiskRowSet。
Compaction
當做查詢操作的時候,Kudun 也會從 DeltaStore 上面讀取數據,所以如果 DeltaStore 太多,整個讀性能會急劇下降。為了解決這個問題,Kudu n在後台會定期的將 delta data 做 compaction,merge 到 base data 裡面。
同時,Kudu 還會定期的將一些 DIskRowSets 做 compaction,生成新的 DiskRowSets,對 RowSet 做 compaction 能直接去掉 deleted rows,同時也能減少重疊的 DiskRowSets,加速讀操作。
總結
上面對n Kudu 大概進行了介紹,主要還是參考 Kudu 自己的論文。Kudu 在設計上面跟 TiKV n非常類似,所以對於很多設計,我是特別能理解為啥要這麼做的,譬如 Master 的信息是通過 tablet 上報這種的。Kudu 對 Raft n在實現上面做了一些優化,以及在數據 partition 上面也有不錯的做法,這些都是後面能借鑒的。
對於n Tablet Storage,雖然 Kudu 是自己實現的,但我發現,很多方面其實跟 RocksDB 差不了多少,類似 LSM n架構,只是可能這套系統專門為 Kudu 做了定製優化,而不像 RocksDB 那樣具有普適性。對於 storage 來說,現在我們還是考慮使用 nRocksDB。
另外,Kudun 採用的是列存,也就是每個列的數據單獨聚合存放到一起,而 TiDB 這邊還是主要使用的行存,也就是存儲整行數據。列存對於 OLAP n非常友好,但在寫入的時候壓力可能會比較大,如果一個 table 有很多 column,寫入性能影響會非常明顯。行存則是對於 OLTP n比較友好,但在讀取的時候會將整行數據全讀出來,在一些分析場景下壓力會有點大。但無論列存還是行存,都是為滿足不同的業務場景而服務的,TiDB n後續其實可以考慮的是行列混存,這樣就能適配不同的場景了,只是這個目標比較遠大,希望感興趣的同學一起加入來實現。(文/唐劉)
推薦閱讀:
※為什麼以色列的網路安全,存儲方面這麼有競爭力?
※家庭裝修超六類、七類網線一般買哪些品牌?
※國政通是一家什麼樣的公司?
※把理論上存在的硬碟加起來能容納包含所有圍棋可能的棋局嗎?
※西部數據的紅盤、綠盤、黑盤在物理結構和性能參數上有什麼區別?
TAG:数据存储技术 |