天天看點

Kudu 架構原理

1、表與schema

Kudu設計是面向結構化存儲的,是以Kudu的表需要使用者在建表時定義它的Schema資訊。

Schema資訊包含:列定義(含類型),Primary Key定義(使用者指定的若幹個列的有序組合)。資料的唯一性,依賴于使用者所提供的Primary Key中的Column組合的值的唯一性。 Kudu提供了Alter指令來增删列,但位于Primary Key中的列是不允許删除的。

Kudu目前并不支援二級索引。 從使用者角度來看,Kudu是一種存儲結構化資料表的存儲系統。

    在一個Kudu叢集中可以定義任意數量的table,每個table都需要預先定義好schema。

    每個table的列數是确定的,每一列都需要有名字和類型,每個表中可以把其中一列或多列定義為主鍵。這麼看來,Kudu更像關系型資料庫,而不是像HBase、Cassandra和MongoDB這些NoSQL資料庫。不過Kudu目前還不能像關系型資料一樣支援二級索引。

Kudu使用确定的列類型,而不是類似于NoSQL的“everything is byte”。這可以帶來兩點好處: 确定的列類型使Kudu可以進行類型特有的編碼。可以提供 SQL-like 中繼資料給其他上層查詢工具,比如BI工具。

2、Kudu 底層資料模型

Kudu的底層資料檔案的存儲,未采用HDFS這樣的較高抽象層次的分布式檔案系統,而是自行開發了一套可基于

Table/Tablet/Replica視圖級别的底層存儲系統。

這套實作基于如下的幾個設計目标:

• 可提供快速的列式查詢

• 可支援快速的随機更新

• 可提供更為穩定的查詢性能保障

Kudu 架構原理

具體Kudu資料存儲結構如下所示:

Kudu 架構原理

一張表會分成若幹個tablet,每個tablet包括MetaData元資訊及若幹個RowSet,RowSet包含一個MemRowSet及若幹個DiskRowSet,DiskRowSet中包含一個BloomFile、Ad_hoc Index、BaseData、DeltaMem及若幹個

RedoFile和UndoFile(UndoFile一般情況下隻有一個)。

MemRowSet:用于新資料insert及已在MemRowSet中的資料的更新,一個MemRowSet寫滿後會将資料刷到磁盤形成若幹個DiskRowSet。每次到達32M生成一個DiskRowSet。

DiskRowSet:用于老資料的變更(mutation),背景定期對DiskRowSet做compaction,以删除沒用的資料及合并曆史資料,減少查詢過程中的IO開銷。

BloomFile:根據一個DiskRowSet中的key生成一個bloom filter,用于快速模糊定位某個key是否在DiskRowSet中存在。

Ad_hocIndex:是主鍵的索引,用于定位key在DiskRowSet中的具體哪個偏移位置。

BaseData是MemRowSet flush下來的資料,按列存儲,按主鍵有序。

UndoFile是基于BaseData之前時間的曆史資料,通過在BaseData上apply UndoFile中的記錄,可以獲得曆史資料。

RedoFile是基于BaseData之後時間的變更(mutation)記錄,通過在BaseData上apply RedoFile中的記錄,可獲得較新的資料。

DeltaMem用于DiskRowSet中資料的變更mutation,先寫到記憶體中,寫滿後flush到磁盤形成RedoFile。

DiskRowSet資料結構如下:

Kudu 架構原理

與HBase類似,也是通過增加一條新的記錄來描述這次更新/删除操作的。DiskRowSet是不可修改了,那麼 KUDU 要如何應對資料的更新呢?在KUDU中,把DiskRowSet分為了兩部分:base data、delta stores。

- base data 負責存儲基礎資料

- delta stores負責存儲 base data 中的變更資料

Kudu 架構原理

3、Tablet 發現過程

當建立Kudu用戶端時,其會從主伺服器上擷取tablet位置資訊,然後直接與服務于該tablet的伺服器進行交談。

為了優化讀取和寫入路徑,用戶端将保留該資訊的本地緩存,以防止他們在每個請求時需要查詢主機的tablet位置資訊。

随着時間的推移,用戶端的緩存可能會變得過時,并且當寫入被發送到不再是tablet上司者的tablet伺服器

時,則将被拒絕。然後用戶端将通過查詢主伺服器發現新上司者的位置來更新其緩存。

程式代碼如下:

// 建構KuduClient執行個體對象

kuduClient = new KuduClient.KuduClientBuilder(masterAddresses) //

    // 設定逾時時間間隔,預設值為10s

    .defaultSocketReadTimeoutMs(6000)

    // 采用建造者模式建構執行個體對象

    .build() ;

// a. 依據表的名稱擷取KuduTable執行個體對象,操作表的句柄

KuduTable kuduTable = kuduClient.openTable("itcast_users");

Kudu 架構原理

4、Kudu 寫流程

Kudu 架構原理

具體寫入資料流程如下所述:

第一、寫入操作先被送出到tablet 的預寫日志(WAL)上,然後根據Raft 一緻性算法取得追随節點的同意後,才會被添加到其中一個tablet 的記憶體中,插入到MemRowSet中。(因為在MemRowSet 中支援了多版本并發控制(mvcc) ,對最近插入的行(未重新整理到磁盤上的新的行)的更新和删除操作将被追加到MemRowSet中的原始行之後以生成Redo 記錄的清單)。

第二、Kudu在MemRowSet 中寫入新資料,在MemRowSet 達到一定大小或者時間限制(1G 或者 120s),MemRowSet 會将資料落盤,生成一個DiskRowSet 用于持久化資料 和 一個 MemRowSet 繼續接受新資料的請求。

5、Kudu 讀流程

Kudu 架構原理

如上圖,資料讀取過程大緻如下:先根據要掃描資料的主鍵範圍,定位到目标的Tablets,然後讀取Tablets 中的RowSets。

在讀取每個RowSet時,先根據主鍵過濾要scan範圍,然後加載範圍内的base data,再找到對應的delta stores,應用所有變更,最後union上MemRowSet中的内容,傳回資料給Client。

6、Kudu 更新流程

Kudu 架構原理

具體更新操作如下所述:

a、用戶端連接配接到TMaster擷取表的相關資訊(分區和tablet資訊);

b、找到負責寫請求的tablet 所在的TServer,kudu接受用戶端的請求,檢查本次寫操作是否符合要求;

c、因為待更新的資料可能位于MemRowSet ,也可能位于DiskRowSet 中,是以根據待更新的資料所處的位置,kudu有不同的做法:

   i、當待更新的資料位于MemRowSet時,找到它所在的行,然後将跟新操作記錄在所在行中的一個mutation的連結清單中,在MemRowSet 資料落地的時候,kudu會将更新合并到base data,并生成undo records 用于檢視曆史版本的資料和MVCC, undo records 實際上也是以 DeltaFile 的形式存放;

   ii、當待跟新的資料位于DiskRowSet時,找到待跟新資料所在的DiskRowSet ,每個DiskRowSet 都會在記憶體中設定一個DeltaMemStore,将更新操作記錄在DeltaMemStore中,在DeltaMemStore達到一定大小時,flush 在磁盤,形成Delta并存放在DeltaFile中。

d、定時合并 `RedoDeltaFile`

   - 合并政策有三種, 常見的有兩種, 一種是 `major`, 會将資料合并到基線資料中, 一種是 `minor`, 隻合并 `RedoDeltaFile`