Kudu 是 Cloudera 開源的新型列式存儲系統,是 Apache Hadoop 生態圈的新成員之一( incubating ),專門為了對快速變化的數據進行快速的分析,填補了以往 Hadoop 存儲層的空缺。本文主要對 Kudu 的動機、背景,以及架構進行簡單介紹。
背景——功能上的空白
Hadoop 生態系統有很多組件,每一個組件有不同的功能。在現實場景中,用戶往往需要同時部署很多 Hadoop 工具來解決同一個問題,這種架構稱為 混合架構 (hybrid architecture) 。 比如,用戶需要利用 Hbase 的快速插入、快讀 random access 的特性來導入數據, HBase 也允許用戶對數據進行修改, HBase 對於大量小規模查詢也非常迅速。同時,用戶使用 HDFS/Parquet + Impala/Hive 來對超大的數據集進行查詢分析,對於這類場景, Parquet 這種列式存儲文件格式具有極大的優勢。
很多公司都成功地部署了 HDFS/Parquet + HBase 混合架構,然而這種架構較為復雜,而且在維護上也十分困難。首先,用戶用 Flume 或 Kafka 等數據 Ingest 工具將數據導入 HBase ,用戶可能在 HBase 上對數據做一些修改。然后每隔一段時間 ( 每天或每周 ) 將數據從 Hbase 中導入到 Parquet 文件,作為一個新的 partition 放在 HDFS 上,最后使用 Impala 等計算引擎進行查詢,生成最終報表。
這樣一條工具鏈繁瑣而復雜,而且還存在很多問題,比如:
- 如何處理某一過程出現失敗?
- 從 HBase 將數據導出到文件,多久的頻率比較合適?
- 當生成最終報表時,最近的數據並無法體現在最終查詢結果上。
- 維護集群時,如何保證關鍵任務不失敗?
- Parquet 是 immutable ,因此當 HBase 中刪改某些歷史數據時,往往需要人工干預進行同步。
這時候,用戶就希望能夠有一種優雅的存儲解決方案,來應付不同類型的工作流,並保持高性能的計算能力。 Cloudera 很早就意識到這個問題,在 2012 年就開始計划開發 Kudu 這個存儲系統,終於在 2015 年發布並開源出來。 Kudu 是對 HDFS 和 HBase 功能上的補充,能提供快速的分析和實時計算能力,並且充分利用 CPU 和 I/O 資源,支持數據原地修改,支持簡單的、可擴展的數據模型。
背景——新的硬件設備
RAM 的技術發展非常快,它變得越來越便宜,容量也越來越大。 Cloudera 的客戶數據顯示,他們的客戶所部署的服務器, 2012 年每個節點僅有 32GB RAM ,現如今增長到每個節點有 128GB 或 256GB RAM 。存儲設備上更新也非常快, 在很多普通服務器中部署 SSD 也是屢見不鮮。 HBase 、 HDFS 、以及其他的 Hadoop 工具都在不斷自我完善,從而適應硬件上的升級換代。然而,從根本上, HDFS 基於 03 年 GFS , HBase 基於 05 年 BigTable ,在當時系統瓶頸主要取決於底層磁盤速度。當磁盤速度較慢時, CPU 利用率不足的根本原因是磁盤速度導致的瓶頸,當磁盤速度提高了之后, CPU 利用率提高,這時候 CPU 往往成為系統的瓶頸。 HBase 、 HDFS 由於年代久遠,已經很難從基本架構上進行修改,而 Kudu 是基於全新的設計,因此可以更充分地利用 RAM 、 I/O 資源,並優化 CPU 利用率。我們可以理解為, Kudu 相比與以往的系統, CPU 使用降低了, I/O 的使用提高了, RAM 的利用更充分了。
簡介
Kudu 設計之初,是為了解決一下問題:
- 對數據掃描 (scan) 和隨機訪問 (random access) 同時具有高性能,簡化用戶復雜的混合架構
- 高 CPU 效率,使用戶購買的先進處理器的的花費得到最大回報
- 高 IO 性能,充分利用先進存儲介質
- 支持數據的原地更新,避免額外的數據處理、數據移動
- 支持跨數據中心 replication
Kudu 的很多特性跟 HBase 很像,它支持索引鍵的查詢和修改。 Cloudera 曾經想過基於 Hbase 進行修改,然而結論是對 HBase 的改動非常大, Kudu 的數據模型和磁盤存儲都與 Hbase 不同。 HBase 本身成功的適用於大量的其它場景,因此修改 HBase 很可能吃力不討好。最后 Cloudera 決定開發一個全新的存儲系統。
Kudu 的定位是提供 ”fast analytics on fast data” ,也就是在快速更新的數據上進行快速的查詢。它定位 OLAP 和少量的 OLTP 工作流,如果有大量的 random accesses ,官方建議還是使用 HBase 最為合適。
架構與設計
1. 基本框架
Kudu 是用於存儲結構化( structured )的表( Table )。表有預定義的帶類型的列( Columns ),每張表有一個主鍵( primary key )。主鍵帶有唯一性( uniqueness )限制,可作為索引用來支持快速的 random access 。
類似於 BigTable , Kudu 的表是由很多數據子集構成的,表被水平拆分成多個 Tablets. Kudu 用以每個 tablet 為一個單元來實現數據的 durability 。 Tablet 有多個副本,同時在多個節點上進行持久化。
Kudu 有兩種類型的組件, Master Server 和 Tablet Server 。 Master 負責管理元數據。這些元數據包括 talbet 的基本信息,位置信息。 Master 還作為負載均衡服務器,監聽 Tablet Server 的健康狀態。對於副本數過低的 Tablet , Master 會在起 replication 任務來提高其副本數。 Master 的所有信息都在內存中 cache ,因此速度非常快。每次查詢都在百毫秒級別。 Kudu 支持多個 Master ,不過只有一個 active Master ,其余只是作為災備,不提供服務。
Tablet Server 上存了 10~100 個 Tablets ,每個 Tablet 有 3 (或 5 )個副本存放在不同的 Tablet Server 上,每個 Tablet 同時只有一個 leader 副本,這個副本對用戶提供修改操作,然后將修改結果同步給 follower 。 Follower 只提供讀服務,不提供修改服務。副本之間使用 raft 協議來實現 High Availability ,當 leader 所在的節點發生故障時, followers 會重新選舉 leader 。根據官方的數據,其 MTTR 約為 5 秒,對 client 端幾乎沒有影響。 Raft 協議的另一個作用是實現 Consistency 。 Client 對 leader 的修改操作,需要同步到 N/2+1 個節點上,該操作才算成功。
Kudu 采用了類似 log-structured 存儲系統的方式,增刪改操作都放在內存中的 buffer ,然后才 merge 到持久化的列式存儲中。 Kudu 還是用了 WALs 來對內存中的 buffer 進行災備。
2. 列式存儲
持久化的列式存儲存儲,與 HBase 完全不同,而是使用了類似 Parquet 的方式,同一個列在磁盤上是作為一個連續的塊進行存放的。例如,圖中左邊是 twitter 保存推文的一張表,而圖中的右邊表示了表在磁盤中的的存儲方式,也就是將同一個列放在一起存放。這樣做的第一個好處是,對於一些聚合和 join 語句,我們可以盡可能地減少磁盤的訪問。例如,我們要用戶名為 newsycbot
的推文數量,使用查詢語句:
SELECT COUNT(*) FROM tweets WHERE user_name = ‘newsycbot’;
我們只需要查詢 User_name 這個 block 即可。同一個列的數據是集中的,而且是相同格式的, Kudu 可以對數據進行編碼,例如字典編碼,行長編碼, bitshuffle 等。通過這種方式可以很大的減少數據在磁盤上的大小,提高吞吐率。除此之外,用戶可以選擇使用通用的壓縮格式對數據進行壓縮,如 LZ4, gzip, 或 bzip2 。這是可選的,用戶可以根據業務場景,在數據大小和 CPU 效率上進行權衡。這一部分的實現上, Kudu 很大部分借鑒了 Parquet 的代碼。
HBase 支持 snappy 存儲,然而因為它的 LSM 的數據存儲方式,使得它很難對數據進行特殊編碼,這也是 Kudu 聲稱具有很快的 scan 速度的一個很重要的原因。不過,因為列式編碼后的數據很難再進行修改,因此當這寫數據寫入磁盤后,是不可變的,這部分數據稱之為 base 數據。 Kudu 用 MVCC (多版本並發控制)來實現數據的刪改功能。更新、刪除操作需要記錄到特殊的數據結構里,保存在內存中的 DeltaMemStore 或磁盤上的 DeltaFIle 里面。 DeltaMemStore 是 B-Tree 實現的,因此速度快,而且可修改。磁盤上的 DeltaFIle 是二進制的列式的塊,和 base 數據一樣都是不可修改的。因此當數據頻繁刪改的時候,磁盤上會有大量的 DeltaFiles 文件, Kudu 借鑒了 Hbase 的方式,會定期對這些文件進行合並。
3. 對外接口
Kudu 提供 C++ 和 JAVA API ,可以進行單條或批量的數據讀寫, schema 的創建修改。除此之外, Kudu 還將與 hadoop 生態圈的其它工具進行整合。目前, kudu beta 版本對 Impala 支持較為完善,支持用 Impala 進行創建表、刪改數據等大部分操作。 Kudu 還實現了 KuduTableInputFormat 和 KuduTableOutputFormat ,從而支持 Mapreduce 的讀寫操作。同時支持數據的 locality 。目前對 spark 的支持還不夠完善, spark 只能進行數據的讀操作。
使用案例——小米
小米是 Hbase 的重度用戶,他們每天有約 50 億條用戶記錄。小米目前使用的也是 HDFS + HBase 這樣的混合架構。可見該流水線相對比較復雜,其數據存儲分為 SequenceFile , Hbase 和 Parquet 。
在使用 Kudu 以后, Kudu 作為統一的數據倉庫,可以同時支持離線分析和實時交互分析。
性能測試
1. 和 parquet 的比較
圖是官方給出的用 Impala 跑 TPC-H 的測試,對比 Parquet 和 Kudu 的計算速度。從圖中我們可以發現, Kudu 的速度和 parquet 的速度差距不大,甚至有些 Query 比 parquet 還快。然而,由於這些數據都是在內存緩存過的,因此該測試結果不具備參考價值。
2. 和 Hbase 的比較
圖是官方給出的另一組測試結果,從圖中我們可以看出,在 scan 和 range 查詢上, kudu 和 parquet 比 HBase 快很多,而 random access 則比 HBase 稍慢。然而數據集只有 60 億行數據,所以很可能這些數據也是可以全部緩存在內存的。對於從內存查詢,除了 random access 比 HBase 慢之外, kudu 的速度基本要優於 HBase 。
3. 超大數據集的查詢性能
Kudu 的定位不是 in-memory database 。因為它希望 HDFS/Parquet 這種存儲,因此大量的數據都是存儲在磁盤上。如果我們想要拿它代替 HDFS/Parquet + HBase ,那么超大數據集的查詢性能就至關重要,這也是 Kudu 的最初目的。然而,官方沒有給出這方面的相關數據。由於條件限制,網易暫時未能完成該測試。下一步,我們將計划搭建 10 台 Kudu + Impala 服務器,並用 tpc-ds 生成超大數據,來完成該對比測驗。