Kudu - 一个融合低延迟写入和高性能分析的存储系统
Kudu 是一個(gè)基于 Raft 的分布式存儲(chǔ)系統(tǒng),它致力于融合低延遲寫入和高性能分析這兩種場(chǎng)景,并且能很好的嵌入到 Hadoop 生態(tài)系統(tǒng)里面,跟其他系統(tǒng)譬如 Cloudera Impala,Apache Spark 等對(duì)接。
Kudu 很類似 TiDB。最開(kāi)始,TiDB 是為了 OLTP 系統(tǒng)設(shè)計(jì)的,但后來(lái)發(fā)現(xiàn)我們 OLAP 的功能也越來(lái)越強(qiáng)大,所以就有了融合 OLTP 和 OLAP 的想法,當(dāng)然這條路并不是那么容易,我們還有很多工作要做。因?yàn)?Kudu 的理念跟我們類似,所以我也很有興趣去研究一下它,這里主要是依據(jù) Kudu 在 2015 發(fā)布的 paper,因?yàn)?Kudu 是開(kāi)源的,并且在不斷的更新,所以現(xiàn)在代碼里面一些實(shí)現(xiàn)可能還跟 paper 不一樣了,但這里僅僅先說(shuō)一下我對(duì) paper 的理解,實(shí)際的代碼我后續(xù)研究了在詳細(xì)說(shuō)明。
為什么需要 Kudu?
結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)系統(tǒng)在 Hadoop 生態(tài)系統(tǒng)里面,通常分為兩類:
-
靜態(tài)數(shù)據(jù),數(shù)據(jù)通常都是使用二進(jìn)制格式存放到 HDFS 上面,譬如 Apache Avro,Apache Parquet。但無(wú)論是 HDFS 還是相關(guān)的系統(tǒng),都是為高吞吐連續(xù)訪問(wèn)數(shù)據(jù)這些場(chǎng)景設(shè)計(jì)的,都沒(méi)有很好的支持單獨(dú) record 的更新,或者是提供好的隨機(jī)訪問(wèn)的能力。
-
動(dòng)態(tài)數(shù)據(jù),數(shù)據(jù)通常都是使用半結(jié)構(gòu)化的方式存儲(chǔ),譬如 Apache HBase,Apache Cassandra。這些系統(tǒng)都能低延遲的讀寫單獨(dú)的 record,但是對(duì)于一些像 SQL 分析這樣需要連續(xù)大量讀取數(shù)據(jù)的場(chǎng)景,顯得有點(diǎn)捉緊見(jiàn)拙。
上面的兩種系統(tǒng),各有自己的側(cè)重點(diǎn),一類是低延遲的隨機(jī)訪問(wèn)特定數(shù)據(jù),而另一類就是高吞吐的分析大量數(shù)據(jù)。之前,我們并沒(méi)有這樣的系統(tǒng)可以融合上面兩種情況,所以通常的做法就是使用 pipeline,譬如我們非常熟悉的 Kafka,通常我們會(huì)將數(shù)據(jù)快速寫到 HBase 等系統(tǒng)里面,然后通過(guò) pipeline,在導(dǎo)出給其它分析系統(tǒng)。雖然我們?cè)谝欢▽用嫔厦?#xff0c;我們其實(shí)通過(guò) pipeline 來(lái)對(duì)整個(gè)系統(tǒng)進(jìn)行了解耦,但總歸要維護(hù)多套系統(tǒng)。而且數(shù)據(jù)更新之后,并不能直接實(shí)時(shí)的進(jìn)行分析處理,有延遲的開(kāi)銷。所以在某些層面上面,并不是一個(gè)很好的解決方案。
Kudu 致力于解決上面的問(wèn)題,它提供了簡(jiǎn)單的來(lái)處理數(shù)據(jù)的插入,更新和刪除,同時(shí)提供了 table scan 來(lái)處理數(shù)據(jù)分析。通常如果一個(gè)系統(tǒng)要融合兩個(gè)特性,很有可能就會(huì)陷入兩邊都做,兩邊都沒(méi)做好的窘境,但 Kudu 很好的在融合上面取得了平衡,那么它是如何做到的呢?
Keyword
Tables 和 schemas
Kudu 提供了 table 的概念。用戶可以建立多個(gè) table,每個(gè) table 都有一個(gè)預(yù)先定義好的 schema。Schema 里面定義了這個(gè) table 多個(gè) column,每個(gè) column 都有名字,類型,是否允許 null 等。一些 columns 組成了 primary key。
可以看到,Kudu 的數(shù)據(jù)模型非常類似關(guān)系數(shù)據(jù)庫(kù),在使用之前,用戶必須首先建立一個(gè) table,訪問(wèn)不存在的 table 或者 column 都會(huì)報(bào)錯(cuò)。用戶可以使用 DDL 語(yǔ)句添加或者刪除 column,但不能刪除包含 primary key 的 column。
但在 Paper 里面說(shuō)到 Kudu 不支持二級(jí)索引以及除了 primary key 之外的唯一索引,這個(gè)后續(xù)可以通過(guò)更新的代碼來(lái)確定下。
其實(shí)我這里非常關(guān)注的是 Kudu 的 Online DDL 是如何做的,只是 Paper 里面貌似沒(méi)有提及,后面只能看代碼了。
API
Kudu 提供了 Insert,Update 和 Delete 的 write API。不支持多行事務(wù) API,這個(gè)不知道最新的能支持了沒(méi)有,因?yàn)閮H僅能對(duì)單行數(shù)據(jù)操作,還遠(yuǎn)遠(yuǎn)不夠。
Kudu 提供了 Scan read API 讓用戶去讀取數(shù)據(jù)。用戶可以指定一些特定的條件來(lái)過(guò)濾結(jié)果,譬如用一個(gè)常量跟一個(gè) column 里面的值比較,或者一段 primary key 的范圍等條件。
提供 API 的好處在于實(shí)現(xiàn)簡(jiǎn)單,但對(duì)于用戶來(lái)說(shuō),其實(shí)更好的使用方式仍然是 SQL,一些復(fù)雜的查詢最好能通過(guò) SQL 搞定,而不是讓用戶自己去 scan 數(shù)據(jù),然后自己組裝。
一致性模型
Kudu 提供兩種一致性模型:snapshot consistency 和 external consistency。
默認(rèn) Kudu 提供 Snapshot consistency, 它具有更好的讀性能,但可能會(huì)有 write skew 問(wèn)題。而 External consistency 則能夠完全保證整個(gè)系統(tǒng)的 linearizability,也就是當(dāng)寫入一條數(shù)據(jù)之后,后面的任何讀取都一定能讀到最新的數(shù)據(jù)。
為了實(shí)現(xiàn) External consistency,Kudu 提供了幾種方法:
-
在 clients 之間顯式地傳遞時(shí)間戳。當(dāng)寫入一條數(shù)據(jù)之后,用戶用要求 client 去拿一個(gè)時(shí)間戳作為 token,然后通過(guò)一個(gè) external channel 的方式傳遞給另一個(gè) client。然后另一個(gè) client 就可以通過(guò)這個(gè) token 去讀取數(shù)據(jù),這樣就一定能保證讀取到最新的數(shù)據(jù)了。不過(guò)這個(gè)方法實(shí)在是有點(diǎn)復(fù)雜。
-
提供類似 Spanner 的 commit-wait 機(jī)制。當(dāng)寫入一條數(shù)據(jù)之后,client 需要等待一段時(shí)間來(lái)確定寫入成功。Kudu 并沒(méi)有采用 Spanner TrueTime 的方案,而是使用了 HybridTime 的方案。HybridTime 依賴 NTP,這個(gè)可能導(dǎo)致 wait 的時(shí)間很長(zhǎng),但 Kudu 認(rèn)為未來(lái)隨著 read-time clock 的完善,這應(yīng)該不是問(wèn)題了。
Kudu 是我已知的第二個(gè)采用 HybridTime 來(lái)解決 External consistency 的產(chǎn)品,第一個(gè)當(dāng)然就是 CockroachDB 了。TiDB 跟他們不一樣,我們采用的是全局授時(shí)的方案,這個(gè)會(huì)簡(jiǎn)單很多,但其實(shí)也有跟 PD 交互的網(wǎng)絡(luò)開(kāi)銷。后續(xù)TiDB 可能使用類似 Spanner 的 GPS + 原子鐘,現(xiàn)階段相關(guān)硬件的制造方式 Google 并沒(méi)有說(shuō)明,但其實(shí)難度不大。因?yàn)橐呀?jīng)有很多硬件廠商主動(dòng)找我們希望一起合作提供,只是比較貴,而現(xiàn)階段我們大多數(shù)客戶并沒(méi)有跨全球事務(wù)這種場(chǎng)景。
Kudu 的一致性模型依賴時(shí)間戳,這應(yīng)該是現(xiàn)在所有分布式系統(tǒng)通用的做法。Kudu 并沒(méi)有給用戶保留時(shí)間戳的概念,主要是覺(jué)得用戶很可能會(huì)困惑,畢竟不是所有的用戶都能很好的理解 MVCC 這些概念。當(dāng)然,對(duì)于 read API,還是允許用戶指定特定的一個(gè)時(shí)間戳,這樣就能讀取到歷史數(shù)據(jù)。這個(gè) TiDB 也是類似的做法,用戶不知道時(shí)間戳,只是我們額外提供了一個(gè)設(shè)置 snapshot 的操作,讓用戶指定生成某個(gè)時(shí)間點(diǎn)的快照,讀取那個(gè)時(shí)間點(diǎn)的數(shù)據(jù)。這個(gè)功能已經(jīng)幫很多公司恢復(fù)了因?yàn)殄e(cuò)誤操作寫壞的數(shù)據(jù)了。
架構(gòu)
上面說(shuō)了一些 Kudu 的 keyword, 現(xiàn)在來(lái)說(shuō)說(shuō) Kudu 的整體架構(gòu)。Kudu 類似 GFS,提供了一個(gè)單獨(dú)的 Master 服務(wù),用來(lái)管理整個(gè)集群的元信息,同時(shí)有多個(gè) Tablet 服務(wù),用來(lái)存儲(chǔ)實(shí)際的數(shù)據(jù)。
分區(qū)
Kudu 支持對(duì)數(shù)據(jù)按照 Range 以及 Hash 的方式進(jìn)行分區(qū)。 每個(gè)大的 table 都可以通過(guò)這種方式將數(shù)據(jù)分不到不同的 Tablet 上面。當(dāng)用戶創(chuàng)建一個(gè)表的時(shí)候,同時(shí)也可以指定特定的 partition schema,partition schema 會(huì)將 primary key 映射成對(duì)應(yīng)的 partition key。每個(gè) Tablet 上面會(huì)覆蓋一段或者多段 partition keys 的range。當(dāng) client 需要操作數(shù)據(jù)的時(shí)候,它可以很方便的就知道這個(gè)數(shù)據(jù)在哪一個(gè) Tablet 上面。
一個(gè) partition schema 可以包括 0 或者多個(gè) hash-partitioning 規(guī)則和最多一個(gè) range-partitioning 規(guī)則。用戶可以根據(jù)自己實(shí)際的場(chǎng)景來(lái)設(shè)置不同的 partition 規(guī)則。
譬如有一行數(shù)據(jù)是?(host, metric, time, value),time 是單調(diào)遞增的,如果我們將 time 按照 hash 的方式分區(qū),雖然能保證數(shù)據(jù)分散到不同的 Tablets 上面,但如果我們想查詢某一段時(shí)間區(qū)間的數(shù)據(jù),就得需要全部掃描所有的 Tablets 了。所以通常對(duì)于 time,我們都是采用 range 的分區(qū)方式。但 range 的方式會(huì)有 hot range 的問(wèn)題,也就是同一個(gè)時(shí)間會(huì)有大量的數(shù)據(jù)寫到一個(gè) range 上面,而這個(gè) hot range 是沒(méi)法通過(guò) scale out 來(lái)緩解的,所以我們可以將?(host, metric)按照 hash 分區(qū),這樣就在 write 和 read 之間提供了一個(gè)平衡。
通過(guò)多個(gè) partition 規(guī)則組合,能很好的應(yīng)對(duì)一些場(chǎng)景,但同時(shí)這個(gè)這對(duì)用戶的要求比較高,他們必須更加了解 Kudu,了解自己的整個(gè)系統(tǒng)數(shù)據(jù)會(huì)如何的寫入以及查詢。現(xiàn)在 TiDB 還只是單純的支持 range 的分區(qū)方式,但未來(lái)不排除也引入 hash。
Raft
Kudu 使用 Raft 算法來(lái)保證分布式環(huán)境下面數(shù)據(jù)一致性,這里就不再詳細(xì)的說(shuō)明 Raft 算法了,因?yàn)橛刑嗟馁Y料了。
Kudu 的 heartbeat 是 500 毫秒,election timeout 是 1500 毫秒,這個(gè)時(shí)間其實(shí)很頻繁,如果 Raft group 到了一定量級(jí),網(wǎng)絡(luò)開(kāi)銷會(huì)比較大。另外,Kudu 稍微做了一些 Raft 的改動(dòng):
-
使用了 exponential back-off 算法來(lái)處理 leader re-election 問(wèn)題。
-
當(dāng)一個(gè)新的 leader 跟 follower 進(jìn)行交互的時(shí)候,Raft 會(huì)嘗試先找到這兩個(gè)節(jié)點(diǎn)的 log 分叉點(diǎn),然后 leader 再?gòu)倪@個(gè)點(diǎn)去發(fā)送 log。Kudu 直接是通過(guò) committedIndex 這個(gè)點(diǎn)來(lái)發(fā)送。
對(duì)于 membership change,Kudu 采用的是 one-by-one 算法,也就是每次只對(duì)一個(gè)節(jié)點(diǎn)進(jìn)行變更。這個(gè)算法的好處是不像 joint consensus 那樣復(fù)雜,容易實(shí)現(xiàn),但其實(shí)還是會(huì)有一些在極端情況下面的?corner case?問(wèn)題。
當(dāng)添加一個(gè)新的節(jié)點(diǎn)之后,Kudu 首先要走一個(gè) remote bootstrap 流程。
可以看到,這個(gè)流程跟 TiKV 的做法類似,這個(gè)其實(shí)有一個(gè)缺陷的。假設(shè)我們有三個(gè)節(jié)點(diǎn),加入第四個(gè)之后,如果新的節(jié)點(diǎn)還沒(méi) apply 完 snapshot,這時(shí)候掛掉了一個(gè)節(jié)點(diǎn),那么整個(gè)集群其實(shí)是沒(méi)法工作的。
為了解決這個(gè)問(wèn)題,Kudu 引入了?PRE_VOTER概念。當(dāng)新的節(jié)點(diǎn)加入的時(shí)候,它是?PRE_VOTE狀態(tài),這個(gè)節(jié)點(diǎn)不會(huì)參與到 Raft Vote 里面,只有當(dāng)這個(gè)節(jié)點(diǎn)接受成功 snapshot 之后,才會(huì)變成?VOTER。
當(dāng)刪除一個(gè)節(jié)點(diǎn)的時(shí)候,Leader 直接提交一個(gè)新的 configuration,刪除這個(gè)節(jié)點(diǎn),當(dāng)這個(gè) log 被 committed 之后,這個(gè)節(jié)點(diǎn)就把刪除了。被刪除的節(jié)點(diǎn)有可能不知道自己已經(jīng)被刪除了,如果它長(zhǎng)時(shí)間沒(méi)有收到其他的節(jié)點(diǎn)發(fā)過(guò)來(lái)的消息,就會(huì)問(wèn)下 Master 自己還在不在,如果不在了,就自己干掉自己。這個(gè)做法跟 TiKV 也是類似的。
Master
Kudu 的 Master 是整個(gè)集群最核心的東西,類似于 TiKV 里面的 PD。在分布式系統(tǒng)里面,一些系統(tǒng)采用了無(wú)中心化的架構(gòu)設(shè)計(jì)方案,但我個(gè)人覺(jué)得,有一個(gè)中心化的單點(diǎn),能更好的用全局視角來(lái)控制和調(diào)度整個(gè)系統(tǒng),而且實(shí)現(xiàn)起來(lái)很簡(jiǎn)單。
在 Kudu 里面,Master 自己也是一個(gè)單一的 Tablet table,只是對(duì)用戶不可見(jiàn)。它保存了整個(gè)集群的元信息,并且為了性能,會(huì)將其全部緩存到內(nèi)存上面。因?yàn)閷?duì)于集群來(lái)說(shuō),元信息的量其實(shí)并不大,所以在很長(zhǎng)一段時(shí)間,Master 都不會(huì)有 scale 的風(fēng)險(xiǎn)。同時(shí) Master 也是采用 Raft 機(jī)制復(fù)制,來(lái)保證單點(diǎn)問(wèn)題。
這個(gè)設(shè)計(jì)其實(shí)跟 PD 是一樣的,PD 也將所有的元信息放到內(nèi)存。同時(shí),PD 內(nèi)部集成 etcd,來(lái)保證整個(gè)系統(tǒng)的可用性。跟 Kudu Master 不一樣的地方在于,PD 是一個(gè)獨(dú)立的組件,而 Kudu 的 Master 其實(shí)還是集成在 Kudu 集群里面的。
Kudu 的 Master 主要負(fù)責(zé)以下幾個(gè)事情:
Catalog manager
Master 的 catalog table 會(huì)管理所有 table 的一些元信息,譬如當(dāng)前 table schema 的版本,table 的 state(creating,running,deleting 等),以及這個(gè) table 在哪些 Tables 上面。
當(dāng)用戶要?jiǎng)?chuàng)建一個(gè) table 的時(shí)候,首先 Master 在 catalog table 上面寫入需要?jiǎng)?chuàng)建 table 的記錄,table 的 state 為 CREATING。然后異步的去選擇 Tablet servers 去創(chuàng)建相關(guān)的元信息。如果中間 Master 掛掉了,table 記錄里面的 CREATING state 會(huì)表明這個(gè) table 還在創(chuàng)建中,新的 Master leader 會(huì)繼續(xù)這個(gè)流程。
Cluster coordinator
當(dāng) Tablet server 啟動(dòng)之后,會(huì)給 Master 注冊(cè),并且持續(xù)的給 Master 進(jìn)行心跳匯報(bào)消后續(xù)的狀態(tài)變化。
雖然 Master 是整個(gè)系統(tǒng)的中心,但它其實(shí)是一個(gè)觀察者,它的很多信息都需要依賴 Tablet server 的上報(bào),因?yàn)橹挥?Tablet server 自己知道當(dāng)前自己有哪一些 tablet 在進(jìn)行 Raft 復(fù)制,Raft 的操作是否執(zhí)行成功,當(dāng)前 tablet 的版本等。因?yàn)?Tablet 的狀態(tài)變更依賴 Raft,每一次變更其實(shí)就在 Raft log 上面有一個(gè)對(duì)應(yīng)的 index,所以上報(bào)給 Master 的消息一定是冪等的,因?yàn)?Master 自己會(huì)比較 tablet 上報(bào)的 log index 跟當(dāng)前自己保存的 index,如果上報(bào)的 log index 是舊的,那么會(huì)直接丟棄。
這個(gè)設(shè)計(jì)的好處在于極大的簡(jiǎn)化了整個(gè)系統(tǒng)的設(shè)計(jì),如果要 Master 自己去負(fù)責(zé)管理整個(gè)集群的狀態(tài)變更,譬如 Master 給一個(gè) tablet 發(fā)送增加副本的命令,然后等待這個(gè)操作完成,在繼續(xù)處理后面的流程。整個(gè)系統(tǒng)光異常處理,都會(huì)變得特別復(fù)雜,譬如我們需要關(guān)注網(wǎng)絡(luò)是不是斷開(kāi)了,超時(shí)了到底是成功了還是失敗了,要不要再去 tablet 上面查一下?
相反,如果 Master 只是給 tablet 發(fā)送一個(gè)添加副本的命令,然后不管了,剩下的事情就是一段時(shí)間后讓 tablet 自己上報(bào)回來(lái),如果成功了繼續(xù)后面的處理,不成功則嘗試在加一次。雖然依賴 tablet 的上報(bào)會(huì)有延遲(通常情況,只要有變動(dòng),tablet 會(huì)及時(shí)的上報(bào)通知,所以這個(gè)延遲其實(shí)挺小的),整個(gè)架構(gòu)簡(jiǎn)單了很多。
其實(shí)看到這里的時(shí)候,我覺(jué)得非常的熟悉,因?yàn)槲覀円彩遣捎玫倪@一套架構(gòu)方案。最開(kāi)始設(shè)計(jì) PD 的時(shí)候,我們還設(shè)想的是 PD 主動(dòng)去控制 TiKV,也就是我上面說(shuō)的那套復(fù)雜的發(fā)命令流程。但后來(lái)發(fā)現(xiàn)實(shí)在是太復(fù)雜了,于是改成 TiKV 主動(dòng)上報(bào),這樣 PD 其實(shí)就是一個(gè)無(wú)狀態(tài)的服務(wù)了,無(wú)狀態(tài)的服務(wù)好處就是如果掛了,新啟動(dòng)的 PD 能立刻恢復(fù)(當(dāng)然,實(shí)際還是要做一些很多優(yōu)化工作的)。
Tablet directory
因?yàn)?Master 知道集群所有的信息,所以當(dāng) client 需要讀寫數(shù)據(jù)的時(shí)候,它一定要先跟 Master 問(wèn)一下對(duì)應(yīng)的數(shù)據(jù)在哪一個(gè) Tablet server 的 tablet 上面,然后才能發(fā)送對(duì)應(yīng)的命令。
如果每次操作都從 Master 獲取信息,那么 Master 鐵定會(huì)成為一個(gè)性能瓶頸,鑒于 tablet 的變更不是特別的頻繁,所以很多時(shí)候,client 會(huì)緩存訪問(wèn)的 tablet 信息,這樣下次再訪問(wèn)的時(shí)候就不用從 Master 再次獲取。
因?yàn)?tablet 也可能會(huì)變化,譬如 leader 跑到了另一個(gè) server 上面,或者 tablet 已經(jīng)不在當(dāng)前 server 上面,client 會(huì)收到相關(guān)的錯(cuò)誤,這時(shí)候,client 就重新再去 Master 獲取一下最新的路由信息。
這個(gè)跟我們的做法仍然是一樣的,client 緩存最近的路由信息,當(dāng)路由失效的時(shí)候,重新去 PD 獲取一下。當(dāng)然,如果只是單純的 leader 變更,其實(shí)返回的錯(cuò)誤里面通常就會(huì)帶上新的 leader 信息,這時(shí)候 client 直接刷新緩存,在直接訪問(wèn)了。
Tablet storage
Tablet server 是 Kudu 用來(lái)存放實(shí)際數(shù)據(jù)的服務(wù),為了更好的性能,Kudu 自己實(shí)現(xiàn)了一套 tablet storage,而沒(méi)有用現(xiàn)有的開(kāi)源解決方案。Tablet storage 目標(biāo)主要包括:
- 快速的按照 Column 掃描數(shù)據(jù)
- 低延遲的隨機(jī)更新
- 一致的性能
RowSets
Tablets 在 Kudu 里面被切分成更小的單元,叫做 RowSets。一些 RowSets 只存在于內(nèi)存,叫做 MemRowSets,而另一些則是使用 disk 和 memory 共享存放,叫做 DiskRowSets。任何一行數(shù)據(jù)只存在一個(gè) RowSets 里面。
在任何時(shí)候,一個(gè) tablet 僅有一個(gè)單獨(dú)的 MemRowSet 用來(lái)保存最近插入的數(shù)據(jù)。后臺(tái)有一個(gè)線程會(huì)定期的將 這些 MemRowSets 刷到 disk 上面。
當(dāng)一個(gè) MemRowSet 被刷到 disk 之后,一個(gè)新的空的 MemRowSet 被創(chuàng)建出來(lái)。之前的 MemRowSet 在刷到 disk 之后,就變成了 DiskRowSet。當(dāng)刷的同時(shí),如果有新的寫入,仍然會(huì)寫到這個(gè)正在刷的 MemRowSet 上面,Kudu 有一套機(jī)制能夠保證新寫入的數(shù)據(jù)也能一起被刷到 disk 上面。
MemRowSet
MemRowSet 是一個(gè)支持并發(fā),提供鎖優(yōu)化的 B-tree,主要基于 MassTree,也有一些不同:
DiskRowSet
當(dāng) MemRowSets 被刷到 disk 之后,就變成了 DiskRowSets。當(dāng) MemRowSets 被刷到 disk 的時(shí)候,Kudu 發(fā)現(xiàn)超過(guò) 32 MB 了就滾動(dòng)一個(gè)新的 DiskRowSet。因?yàn)?MemRowSet 是順序的,所以 DiskRowSets 也是順序的,各滾動(dòng)的 DiskRowSet 里面的 primary keys 都是不相交的。
一個(gè) DiskRowSet 包含 base data 和 delta data。Base data 按照 column 組織,也就是通常我們說(shuō)的列存。各個(gè) column 會(huì)被獨(dú)立的寫到 disk 里面一段連續(xù)的 block 上面,數(shù)據(jù)會(huì)被切分成多個(gè) page,使用一個(gè) B-tree 進(jìn)行高效索引。
除了刷用戶自定義的 column,Kudu 還默認(rèn)將 primary key index 寫到一個(gè) column,同時(shí)使用 Bloom filter 來(lái)保證能快速通過(guò)找到 primary key。
為了簡(jiǎn)單,當(dāng) column 的數(shù)據(jù)刷到 disk,它就是默認(rèn) immutable 的了,但在刷的過(guò)程中,有可能有更新的數(shù)據(jù),Kudu 將這些數(shù)據(jù)放到一個(gè) delta stores 上面。Delta stores 可能在內(nèi)存 DeltaMemStores,或者 disk DeltaFiles。
Delta store 維護(hù)的一個(gè) map,key 是?(row_offset, timestamp),value 就是 RowChangeList 記錄。Row offset 就是 row 在 RowSet 里面的索引,譬如,有最小 primary key 的 row 在 RowSet 里面是排在最前面的,它的 offset 就是 0。Timestamp 就是通常的 MVCC timestamp。
當(dāng)需要給 DiskRowSet 更新數(shù)據(jù)的時(shí)候,Kudu 首先通過(guò) primary key 找到對(duì)應(yīng)的 row。通過(guò) B-tree 索引,能知道哪一個(gè) page 包含了這個(gè) row,在 page 里面,可以計(jì)算 row 在整個(gè) DiskRowSet 的 offset,然后就把這個(gè) offset 插入到 DeltaMemStore 里面。
當(dāng) DeltaMemStore 超過(guò)了一個(gè)閥值,一個(gè)新的 DeltaMemStore 就會(huì)生成,原先的就會(huì)被刷到 disk,變成 immutable DeltaFile。
每個(gè) DiskRowSet 都有一個(gè) Bloom filter,便于快速的定位一個(gè) key 是否存在于該DiskRowSet 里面。DIskRowSet 還保存了最小和最大的 primary key,這樣外面就能通過(guò) key 落在哪一個(gè) key range 里面,快速的定位到這個(gè) key 屬于哪一個(gè) DiskRowSet。
Compaction
當(dāng)做查詢操作的時(shí)候,Kudu 也會(huì)從 DeltaStore 上面讀取數(shù)據(jù),所以如果 DeltaStore 太多,整個(gè)讀性能會(huì)急劇下降。為了解決這個(gè)問(wèn)題,Kudu 在后臺(tái)會(huì)定期的將 delta data 做 compaction,merge 到 base data 里面。
同時(shí),Kudu 還會(huì)定期的將一些 DIskRowSets 做 compaction,生成新的 DiskRowSets,對(duì) RowSet 做 compaction 能直接去掉 deleted rows,同時(shí)也能減少重疊的 DiskRowSets,加速讀操作。
總結(jié)
上面對(duì) Kudu 大概進(jìn)行了介紹,主要還是參考 Kudu 自己的論文。Kudu 在設(shè)計(jì)上面跟 TiKV 非常類似,所以對(duì)于很多設(shè)計(jì),我是特別能理解為啥要這么做的,譬如 Master 的信息是通過(guò) tablet 上報(bào)這種的。Kudu 對(duì) Raft 在實(shí)現(xiàn)上面做了一些優(yōu)化,以及在數(shù)據(jù) partition 上面也有不錯(cuò)的做法,這些都是后面能借鑒的。
對(duì)于 Tablet Storage,雖然 Kudu 是自己實(shí)現(xiàn)的,但我發(fā)現(xiàn),很多方面其實(shí)跟 RocksDB 差不了多少,類似 LSM 架構(gòu),只是可能這套系統(tǒng)專門為 Kudu 做了定制優(yōu)化,而不像 RocksDB 那樣具有普適性。對(duì)于 storage 來(lái)說(shuō),現(xiàn)在我們還是考慮使用 RocksDB。
另外,Kudu 采用的是列存,也就是每個(gè)列的數(shù)據(jù)單獨(dú)聚合存放到一起,而 TiDB 這邊還是主要使用的行存,也就是存儲(chǔ)整行數(shù)據(jù)。列存對(duì)于 OLAP 非常友好,但在刷盤的時(shí)候壓力可能會(huì)比較大,如果一個(gè) table 有很多 column,寫入性能可會(huì)有點(diǎn)影響。行存則是對(duì)于 OLTP 比較友好,但在讀取的時(shí)候會(huì)將整行數(shù)據(jù)全讀出來(lái),在一些分析場(chǎng)景下壓力會(huì)有點(diǎn)大。但無(wú)論列存還是行存,都是為滿足不同的業(yè)務(wù)場(chǎng)景而服務(wù)的,TiDB 后續(xù)其實(shí)可以考慮的是行列混存,這樣就能適配不同的場(chǎng)景了,只是這個(gè)目標(biāo)比較遠(yuǎn)大,希望感興趣的同學(xué)一起加入來(lái)實(shí)現(xiàn)。
總結(jié)
以上是生活随笔為你收集整理的Kudu - 一个融合低延迟写入和高性能分析的存储系统的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 高可用系统架构设计 技术方案
- 下一篇: 规则引擎:大厂营销系统资格设计全解