分散式儲存系統Kudu與HBase的簡要分析與對比
本文來自網易雲社群
作者:閩濤
背景
Cloudera在2016年釋出了新型的分散式儲存系統——kudu,kudu目前也是apache下面的開源專案。Hadoop生態圈中的技術繁多,HDFS作為底層資料儲存的地位一直很牢固。而HBase作為Google BigTable的開源產品,一直也是Hadoop生態圈中的核心元件,其資料儲存的底層採用了HDFS,主要解決的是在超大資料集場景下的隨機讀寫和更新的問題。Kudu的設計有參考HBase的結構,也能夠實現HBase擅長的快速的隨機讀寫、更新功能。那麼同為分散式儲存系統,HBase和Kudu二者有何差異?兩者的定位是否相同?我們通過分析HBase與Kudu整體結構和儲存結構等方面對兩者的差異進行比較。
整體結構
HBase的整體結構
HBase的主要元件包括Master,zookeeper服務,RegionServer,HDFS。
Master:用來管理與監控所有的HRegionServer,也是管理HBase元資料的模組。
zookeeper:作為分散式協調服務,用於儲存meta表的位置,master的位置,儲存RS當前的工作狀態。
RegionServer:負責維護Master分配的region,region對應著表中一段區間內的內容,直接接受客戶端傳來的讀寫請求。
HDFS:負責最終將寫入的資料持久化,並通過多副本複製實現資料的高可靠性。
Kudu的整體結構
Kudu叢集中存在兩種主要元件:
(1)TServer,負責管理Tablet,tablet是負責一張表中某塊內容的讀寫,接收其他TServer中leader tablet傳來的同步資訊。
(2)Master,叢集中的管理節點,用於管理tablet的基本資訊,表的資訊,並監聽TServer的狀態。多個Master之間通過Raft協議實現資料同步和高可用。
主要區別
Kudu結構看上去跟HBase差別並不大,主要的區別包括:
1、Kudu將HBase中zookeeper的功能放進了Master內,Kudu中Master的功能比HBase中的Master任務要多一些。
2、Hbase將資料持久化這部分的功能交給了Hadoop中的HDFS,最終組織的資料儲存在HDFS上。Kudu自己將儲存模組整合在自己的結構中,內部的資料儲存模組通過Raft協議來保證leader Tablet和replica Tablet內資料的強一致性,和資料的高可靠性。為什麼不像HBase一樣,利用HDFS來實現資料儲存,猜測可能是因為HDFS讀小檔案時的時延太大,所以Kudu自己重新完成了底層的資料儲存模組,並將其整合在TServer中。
資料儲存方式
HBase
HBase是一款Nosql資料庫,典型的KV系統,沒有固定的schema模式,建表時只需指定一個或多個列族名即可,一個列族下面可以增加任意個列限定名。一個列限定名代表了實際中的一列,HBase將同一個列族下面的所有列儲存在一起,所以HBase是一種面向列族式的資料庫。
HBase將每個列族中的資料分別儲存,一個列族中的每行資料中,將rowkey、列族名、列名、timestamp組成最終存取的key值,另外為了支援修改,刪除,增加了一個表徵該行資料是否刪除的標記。在同一個列族中的所有資料,按照rowkey:columnfamily:columnQulifier:timestamp組成的key值大小進行升序排列,其中rowkey、columnfamily、columnQulifier採用的是字典順序,其值越大,key越大,而timestamp是值越大,key越小。HBase通過按照列族分開儲存,相對於行式儲存能夠實現更高的壓縮比,這也是其比較重要的一個特性。
HBase對一行資料進行更新時,HBase也是相當於插入一行新資料,在讀資料時HBase按照timestamp的大小得到經過更新過的最新資料。
Kudu
Kudu是一種完全的列式儲存引擎,表中的每一列資料都是存放在一起,列與列之間都是分開的。
為了能夠儲存一部分歷史資料,並實現MVCC,Kudu將資料分為三個部分。一個部分叫做base data,是當前的資料;第二個部分叫做UNDO records,儲存的是從插入資料時到形成base data所進行的所有修改操作,修改操作以一定形式進行組織,實現快速檢視歷史資料;第三個部分是REDO records,儲存的是還未merge到當前資料中的更新操作。下圖中表示的是在Kudu中插入一條資料、更新資料兩個操作的做法,當然做法不唯一,不唯一的原因是Kudu可以選擇先不將更新操作合併到base data中。
差異分析
(1)HBase是面向列族式的儲存,每個列族都是分別存放的,HBase表設計時,很少使用設計多個列族,大多情況下是一個列族。這個時候的HBase的儲存結構已經與行式儲存無太大差別了。而Kudu,實現的是一個真正的面向列的儲存方式,表中的每一列都是單獨存放的;所以HBase與Kudu的差異主要在於類似於行式儲存的列族式儲存方式與典型的面向列式的儲存方式的差異。
(2)HBase是一款NoSQL型別的資料庫,對錶的設計主要在於rowkey與列族的設計,列的型別可以不指定,因為HBase在實際儲存中都會將所有的value欄位轉換成二進位制的位元組流。因為不需要指定型別,所以在插入資料的時候可以任意指定列名(列限定名),這樣相當於可以在建表之後動態改變表的結構。Kudu因為選擇了列式儲存,為了更好的提高列式儲存的效果,Kudu要求在建表時指定每一列的型別,這樣的做法是為了根據每一列的型別設定合適的編碼方式,實現更高的資料壓縮比,進而降低資料讀入時的IO壓力。
(3)HBase對每一個cell資料中加入了timestamp欄位,這樣能夠實現記錄同一rowkey和列名的多版本資料,另外HBase將資料更新操作、刪除操作也是作為一條資料寫入,通過timestamp來標記更新時間,type來區分資料是插入、更新還是刪除。HBase寫入或者更新資料時可以指定timestamp,這樣的設定可以完成某些特定的操作。
Kudu也在資料儲存中加入了timestamp這個欄位,不像HBase可以直接在插入或者更新資料時設定特殊的timestamp值,Kudu的做法是由Kudu內部來控制timestamp的寫入。不過Kudu允許在scan的時候設定timestamp引數,使得客戶端可以scan到歷史資料。
(4)相對於HBase允許多版本的資料存在,Kudu為了提高批量讀取資料時的效率,要求設計表時提供一列或者多列組成一個主鍵,主鍵唯一,不允許多個相同主鍵的資料存在。這樣的設定下,Kudu不能像HBase一樣將更新操作直接轉換成插入一條新版本的資料,Kudu的選擇是將寫入的資料,更新操作分開儲存。
(5)當然還有一些其他的行式儲存與列式儲存之間在不同應用場景下的效能差異。
寫入和讀取過程
一、HBase
HBase作為一種非常典型的LSM結構的分散式儲存系統,是Google bigtable的apache開源版本。經過近10年的發展,HBase已經成為了一個成熟的專案,在處理OLTP型的應用如訊息日誌,歷史訂單等應用較適用。在HBase中真正接受客戶端讀寫請求的RegionServer的結構如下圖所示:
關於HBase的幾個關鍵點:
(1)在HBase中,充當寫入快取的這個結構叫做Memstore,另外會將寫入操作順序寫入HLOG(WAL)中以保證資料不丟失。
(2)為了提高讀的效能,HBase在記憶體中設定了blockcache,blockcache採用LRU策略將最近使用的資料塊放在記憶體中。
(3)作為分散式儲存系統,為保證資料不因為叢集中機器出現故障而導致資料丟失,HBase將實際資料存放在HDFS上,包括storefile與HLOG。HBase與HDFS低耦合,HBase作為HDFS的客戶端,向HDFS讀寫資料。
1、HBase寫過程
(1)客戶端通過客戶端上儲存的RS資訊快取或者通過訪問zk得到需要讀寫的region所在的RS資訊;
(2)RS接受客戶端寫入請求,先將寫入的操作寫入WAL,然後寫入Memstore,這時HBase向客戶端確認寫入成功;
(3)HBase在一定情況下將Memstore中的資料flush成storefile(可能是Memstore大小達到一定閾值或者region佔用的記憶體超過一定閾值或者手動flush之類的),storefile以HFile的形式存放在HDFS上;
(4)HBase會按照一定的合併策略對HDFS上的storefile進行合併操作,減少storefile的數量。
2、HBase讀過程
HBase讀資料的過程比較麻煩,原因包括:
(1)HBase採用了LSM-tree的多元件演算法作為資料組織方式,這種演算法會導致一個region中有多個storefile;
(2)HBase中採用了非原地更新的方式,將更新操作和刪除操作轉換成插入一條新資料的形式,雖然這樣能夠較快的實現更新與刪除,但是將導致滿足指定rowkey,列族、列名要求的資料有多個,並且可能分佈在不同的storefile中;
(3)HBase中允許設定插入和刪除資料行的timestamp屬性,這樣導致按順序落盤的storefile內資料的timestamp可能不是遞增的。
下面介紹從HBase中讀取一條指定(rowkey,column family,column)
(1)讀過程與HBase客戶端寫過程第一步一樣,先嚐試獲取需要讀的region所在的RS相關資訊;
(2)RS接收讀請求,因為HBase中支援多版本資料(允許存在rowkey、列族名、列名相同的資料,不同版本的資料通過timestamp進行區分),另外更新與刪除資料都是通過插入一條新資料實現的。所以要準確的讀到資料,需要找到所有可能儲存有該條資料的位置,包括在記憶體中未flush的memstore,已經flush到HDFS上的storefile,所以需要在1 memstore +N storefile中查詢;
(3)在找到的所有資料中通過判斷timestamp值得到最終的資料。
二、Kudu
(1)Kudu中的Tablet是負責表中一塊內容的讀寫工作,Tablet由一個或多個Rowset組成。其中有一個Rowset處於記憶體中,叫做Memrowset,Memrowset主要負責處理新的資料寫入請求。DiskRowSet是MemRowset達到一定程式刷入磁碟後生成的,實質上是由一個CFile(Base Data)、多個DeltaFile(UNDO records &REDO records)和位於記憶體的DeltaMemStore組成。Base data、UNDO records、和REDO records都是不可修改的,DeltaMemStore達到一定大小後會將資料刷入磁碟生成新的REDO records。Kudu後臺會有一個類似HBase的compaction執行緒按照一定的compaction 策略對tablet進行合併處理:
a、將多個DeltaFile(REDO records)合併成一個大的DeltaFile;
b、將多個REDO reccords檔案與Base data進行合併,並生成新的UNDO records;
c、將多個DiskRowset之間進行合併,減少DiskRowset的數量。
(2)Kudu將最終的資料儲存在本地磁碟上,為了保證資料可靠性,Kudu為一個tablet設定了多個副本(一般為3或5個)。所以一個tablet會由多個TServer負責維護,其中有個副本稱為leader tablet,寫入的請求只能通過leader tablet來處理,副本之間通過Raft協議保證其他副本與leader tablet的強一致性。
1、Kudu寫過程
Kudu與HBase不同,Kudu將寫入操作分為兩種,一種是插入一條新資料,一種是對一條已插入資料的更新。
1、Kudu插入一條新資料
(1)客戶端連線Master獲取表的相關資訊,包括分割槽資訊,表中所有tablet的資訊;
(2)客戶端找到負責處理讀寫請求的tablet所負責維護的TServer。Kudu接受客戶端的請求,檢查請求是否符合要求(表結構);
(3)Kudu在Tablet中的所有rowset(memrowset,diskrowset)中進行查詢,看是否存在與待插入資料相同主鍵的資料,如果存在就返回錯誤,否則繼續;
(4)Kudu在MemRowset中寫入一行新資料,在MemRowset資料達到一定大小時,MemRowset將資料落盤,並生成一個diskrowset用於持久化資料,還生成一個memrowset繼續接收新資料的請求。
2、Kudu對原有資料的更新
(1)客戶端連線Master獲取表的相關資訊,包括分割槽資訊,表中所有tablet的資訊;
(2)Kudu接受請求,檢查請求是否符合要求;
(3)因為待更新資料可能位於memrowset中,也可能已經flush到磁碟上,形成diskrowset。因此根據待更新資料所處位置不同,kudu有不同的做法:
當待更新資料位於memrowset時
a、找到待更新資料所在行,然後將更新操作記錄在所在行中一個mutation連結串列中;在memrowset將資料落盤時,Kudu會將更新合併到base data,並生成UNDO records用於檢視歷史版本的資料和MVCC,UNDO records實際上也是以DeltaFile的形式存放;
當待更新資料位於DiskRowset中
b、找到待更新資料所在的DiskRowset,每個DiskRowset都會在記憶體中設定一個DeltaMemStore,將更新操作記錄在DeltaMemStore中,在DeltaMemStore達到一定大小時,flush在磁碟,形成Delta並存在方DeltaFile中;
實際上Kudu提交更新時會使用Raft協議將更新同步到其他replica上去,當然如果在memrowset和diskrowset中都沒有找到這條資料,那麼返回錯誤給客戶端;另外當DiskRowset中的deltafile太多時,Kudu會採用一定的策略對一組deltafile進行合併。
2、Kudu讀過程
1、客戶端連線Master獲取表的相關資訊,包括分割槽資訊,表中所有tablet的資訊;
2、客戶端找到需要讀取的資料的tablet所在的TServer,Kudu接受讀請求,並記錄timestamp資訊,如果沒有顯式指定,那麼表示使用當前時間;
3、Kudu找到待讀資料的所有相關資訊,當目標資料處於memrowset時,根據讀取操作中包含的timestamp資訊將該timestamp前提交的更新操作合併到base data中,這個更新操作記錄在該行資料對應的mutation連結串列中;
4、當讀取的目標資料位於diskrowset中,在所有DeltaFile中找到所有目標資料相關的UNDO record和REDO records,REDO records可能位於多個DeltaFile中,根據讀操作中包含的timestamp資訊判斷是否需要將base data進行回滾或者利用REDO records將base data進行合併更新。
三、Kudu與HBase在讀寫上過程中的差異
1、寫過程
(1)HBase寫的時候,不管是新插入一條資料還是更新資料,都當作插入一條新資料來進行;而Kudu將插入新資料與更新操作分別看待。
(2)Kudu表結構中必須設定一個唯一鍵,插入資料的時候必須判斷一些該資料的主鍵是否唯一,所以插入的時候其實有一個讀的過程;而HBase沒有太多限制,待插入資料將直接寫進memstore。
(3)HBase實現資料可靠性是通過將落盤的資料寫入HDFS來實現,而Kudu是通過將資料寫入和更新操作同步在其他副本上實現資料可靠性。
結合以上幾點,可以看出Kudu在寫的效能上相對HBase有一定的劣勢。
2、讀過程
(1)在HBase中,讀取的資料可能有多個版本,所以需要結合多個storefile進行查詢;Kudu資料只可能存在於一個DiskRowset或者MemRowset中,但是因為可能存在還未合併進原資料的更新,所以Kudu也需要結合多個DeltaFile進行查詢。
(2)HBase寫入或者更新時可以指定timestamp,導致storefile之間timestamp範圍的規律性降低,增加了實際查詢storefile的數量;Kudu不允許人為指定寫入或者更新時的timestamp值,DeltaFile之間timestamp連續,可以更快的找到需要的DeltaFile。
(3)HBase通過timestamp值可以直接取出資料;而Kudu實現多版本是通過保留UNDO records(已經合併過的操作)和REDO records(未合併過的操作)完成的,在一些情況下Kudu需要將base data結合UNDO records進行回滾或者結合REDO records進行合併然後才能得到真正所需要的資料。
結合以上三點可以得出,不管是HBase還是Kudu,在讀取一條資料時都需要從多個檔案中搜尋相關資訊。相對於HBase,Kudu選擇將插入資料和更新操作分開,一條資料只可能存在於一個DiskRowset或者memRowset中,只需要搜尋到一個rowset中存在指定資料就不用繼續往下找了,使用者不能設定更新和插入時的timestamp值,減少了在rowset中DeltaFile的讀取數量。這樣在scan的情況下可以結合列式儲存的優點實現較高的讀效能,特別是在更新數量較少的情況下能夠有效提高scan效能。
另外,本文在描述HBase讀寫過程中沒有考慮讀寫中使用的優化技術如Bloomfilter、timestamp range等。其實Kudu中也有使用類似的優化技術來提高讀寫效能,本文只是簡單的分析,因此就不再詳細討論讀寫過程。如有需要了解HBase的詳細讀寫過程,可以參考範欣欣的 HBase - 資料寫入流程解析等一系列HBase相關文章。
其他差異
HBase:使用的java,記憶體的釋放通過GC來完成,在記憶體比較緊張時可能引發full GC進而導致服務不穩定;
Kudu:核心模組用的C++來實現,沒有full gc的風險。
總結
本文主要簡單介紹了一下Kudu,並在整體結構,資料儲存結構還有讀寫過程等方面上對HBase和Kudu這兩款分散式儲存系統進行大體上的比較。Kudu通過要求完整的表結構設定,主鍵的設定,以列式儲存作為資料在磁碟上的組織方式,更新和資料分開等技巧,使得Kudu能夠實現像HBase一樣實現資料的隨機讀寫之外,在HBase不太擅長的批量資料掃描(scan)具有較好的效能。而批量讀資料正是olap型應用所關注的重點,正如Kudu官網主頁上描述的,Kudu實現的是既可以實現資料的快速插入與實時更新,也可以實現資料的快速分析。Kudu的定位不是取代HBase,而是以降低寫的效能為代價,提高了批量讀的效能,使其能夠實現快速線上分析。
本文只是簡單的分析一下Kudu的結構,並與HBase比較,若有什麼不對的地方,可以一起探討交流。
本文來自網易雲社群,經作者閔濤授權釋出