一文了解 Hbase 列式數據庫
1. 什么是 Hbase ?
HBase 是一個高可靠性、高性能、面向列、可伸縮的分布式存儲系統,目標是存儲并處理大型的數據。
HBase 是 Google Bigtable 的開源實現,不同之處在于:Google Bigtable 使用 GFS 作為其文件存儲系統, HBase 利用 Hadoop HDFS 作為其文件存儲系統;Google Bigtable 利用 Chubby 作為協同服務, HBASE 利用 Zookeeper 作為協同服務。
Hbase 是一個面向列存儲的分布式存儲系統,它的優點在于可以實現高性能的并發讀寫操作,同時 Hbase 還會對數據進行透明的切分,這樣就使得存儲本身具有了水平伸縮性。
2. Hbase 的數據模型是什么樣的?
HBase 的數據存儲模型當中包含以下幾個概念:
- row :一行數據包含一個唯一標識 Row-Key 、多個 column 以及對應的值。在 HBase 中,一張表中所有 row 都按照 Row-Key 的字典序(二進制位移計算)由小到大排序。
- column :與關系型數據庫中的列不同, HBase 中的 column 由 column family (列簇)以及 qualifier (列名)兩部分組成。column family 在表創建的時候需要指定,用戶不能隨意增減。column family 下可以設置任意多個 qualifier ,因此可以理解為 HBase 中的列可以動態增加。
- cell :單元格,由五元組( row , column , timestamp , type , value )組成的結構,其中 type 表示 Put/Delete 這樣的操作類型, timestamp 代表這個 cell 的版本。這個結構在數據庫中實際是以 KV 結構存儲的,其中( row , column , timestamp , type )是 K , value 字段對應 KV 結構的 V 。
- timestamp :每個 cell 在寫入 HBase 的時候都會默認分配一個時間戳作為該 cell 的版本, HBase 支持多版本特性,即同一 Row-Key 、 column 下可以有多個 value 存在,這些 value 使用 timestamp 作為版本號。
- HBase 是一個面向列的數據庫,在表中它由行排序。表模式定義只能列族,也就是鍵值對。一個表有多個列族以及 每一個列族可以有任意數量的列 。后續列的值連續存儲在磁盤上。表中的每個單元格值都具有時間戳。總之,在一個 HBase :表是行的集合,行是列族的集合,列族是列的集合,列是鍵值對的集合。
表 1 邏輯存儲視圖
表 2 物理存儲視圖
HBase 的邏輯存儲視圖由行鍵、時間戳、列族組成一個類似二維表一樣的結構,但是在實際存儲的時候,數據庫存儲的數據是以列族為單位進行存儲的,是完全將行數據按列族的方式進行分列。
3. Hbase 的存儲架構是什么樣的?
圖 1 Hbase 物理存儲架構
Region 是 HBase 中分布式存儲和負載均衡的最小單元,不同的 Region 分布到不同的 RegionServer 上,如圖 Table1 、 Table2 中均有多個 Region ,這些 Region 分布在不同的 RegionServer 中。Region 雖然是分布式分布式存儲的最小單元,但并不是存儲的最小單元, Store 是存儲的最小單元。Region 由一個或者多個 Store 組成,每個 Store 會保存一個 Column Family ;每個 Store 又由一個 MemStore 或 0 至多個 Hfile 組成;MemStore 存儲在內存中, HFile 存儲在 HDFS 中。
Hbase 在數據存儲的過程當中,涉及到的物理對象分為如下:
- HMaster: 負責 DDL 創建或刪除 tables ,同一時間只能有一個 active 狀態的 master 存在。
- Zookeeper: 判定 HMaster 的狀態,記錄 Meta Table 的具體位置;
- Region: 一張 BigTable 的一個分片( Shard ),記錄著 key 的開始和結束;
- WAL: 預寫日志,持久化且順序存儲,一個 RegionServer 維護一套 WAL ;
- RegionServer: RegionServer 中維護多個 region , region 里包含 MemStore 以及多個 HFiles ;
- MemStore: 對應一個 BigTable 的 Column Family ,存在于文件緩存中,擁有文件句柄;
- BlockCache: 讀緩存,存于內存;(Row-Key – > row) ;
HFiles: 從 MemStore Flush 出來的文件,本身是持久化的,存儲于 HDFS 的 DataNode 之中,每次 Flush 生成一個新的 HFile 文件,文件包含有序的鍵值對序列。
4. Hbase 是如何進行數據的讀寫?
圖 2 Hbase 讀寫原理圖
數據寫入流程(如左圖):
- 客戶端首先從 Zookeeper 找到 meta 表的 region 位置,然后讀取 meta 表中的數據, meta 表中存儲了用戶表的 region 信息。
- 根據 namespace 、表名和 Row-Key 信息。找到寫入數據對應的 region 信息
- 找到這個 region 對應的 regionServer ,然后發送請求。
- 把數據分別寫到 HLog ( write ahead log )和 memstore 各一份。
- memstore 達到閾值后把數據刷到磁盤,生成 storeFile 文件。
- 刪除 HLog 中的歷史數據。
數據讀出流程(如右圖):
- 客戶端首先與 Zookeeper 進行連接;從 Zookeeper 找到 meta 表的 region 位置,即 meta 表的數據存儲在某一 HRegionServer 上;客戶端與此 HRegionServer 建立連接,然后讀取 meta 表中的數據;meta 表中存儲了所有用戶表的 region 信息,我們可以通過 scan 'hbase:meta' 來查看 meta 表信息。
- 根據要查詢的 namespace 、表名和 Row-Key 信息。找到寫入數據對應的 region 信息。
- 找到這個 region 對應的 regionServer 發送請求,并找到相應 region 。
- 先從 memstore 查找數據,如果沒有,再從 BlockCache 上讀取。
- 如果 BlockCache 中也沒有找到,再到 StoreFile 上進行讀取,從 storeFile 中讀取到數據之后,不是直接把結果數據返回給客戶端,而是把數據先寫入到 BlockCache 中,目的是為了加快后續的查詢;然后在返回結果給客戶端。
5. Hbase 的存儲引擎是什么類型的?
首先需要確定的是 Hbase 的存儲引擎是 LSM-Tree (可以參考之前的文章:DB 存儲引擎知識系列之三:LSM-Tree 存儲引擎詳細分解)。
通過之前文章對 LSM-Tree 的介紹,我們知道 LSM-Tree 相比較 B+Tree 而言,最大的特點就是在于通過犧牲部分讀性能,利用分層合并的思想,將小樹合并為大樹,將無序數據合并為有序數據,然后統一刷入磁盤,從而大大提高了寫的性能。那么 HBase 套用到 LSM 中, Memstore 就是 LSM 當中的 Memtable ,也就是 C0 層的小樹寫入, HFiles 就是 LSM 當中的 SSTables ,也就是 Cn 層的合并之后的樹的順序寫入。
除此之外 Hbase 在實現 Hbase 的時候,其實還是有自己獨到的地方:
- Minor vs Major Compaction :Minor Compaction ,根據配置策略,自動檢查小文件,合并到大文件,從而減少碎片文件,然而并不會立馬刪除掉舊 HFile 文件;Major Compaction ,每個 CF 中,不管有多少個 HFiles 文件,最終都是將 HFiles 合并到一個大的 HFile 中,并且把所有的舊 HFile 文件刪除,即 CF 與 HFile 最終變成一一對應的關系。
- BlockCache :除了 MemStore (也就是 MemTable ) 以外, HBase 還提供了另一種緩存結構, BlockCache 。BlockCache 本質上是將熱數據放到內存里維護起來,避免 Disk I/O ,當然即使 BlockCache 找不到數據還是可以去 MemStore 中找的,只有兩邊都不存在數據的時候,才會讀內存里的 HFile 索引尋址到硬盤,進行一次 I/O 操作。HBase 將 BucketCache 和 LRUBlockCache 搭配使用,稱之為 CombinedBlockCache 。系統在 LRUBlockCache 中主要存儲 Index Block ,而將 Data Block 存儲在 BucketCache 中。因此一次隨機讀需要首先在 LRUBlockCache 中查到對應的 Index Block ,然后再到 BucketCache 查找對應數據塊。
- HFile :HFile 的數據結構也是 Hbase 的重要改進之處。
圖示是 HFile 的數據結構,主要包含四個部分:數據塊、頭信息、索引信息、地址信息。索引就是 HFile 內置的一個 B+ 樹索引,當 RegionServer 啟動后并且 HFile 被打開的時候,這個索引會被加載到 Block Cache 即內存里;KeyValues 存儲在增長中的隊列中的數據塊里,數據塊可以指定大小,默認 64k ,數據塊越大,順序檢索能力越強;數據塊越小,隨機讀寫能力越強,需要權衡。
6. Hbase 與傳統的 RDBMS 有什么區別?
表 4 列式數據庫與關系型數據庫的區別
介紹了很多 HBase 與 RDBMS 的區別。那么什么時候最需要 HBase ,或者說 HBase 是否可以替代原有的 RDBMS ?對于這個問題,我們必須時刻謹記 HBase 并不適合所有場景,其最終目標并不是完全替代 RDBMS ,而是對 RDBMS 的一個重要補充。當需要考量 HBase 作為一個備選選型產品的時候,我們需要考慮以下幾個關鍵問題。
- 業務場景是否符合非 ACID 事務原則?
- 數據的業務特性上是否需要復雜查詢,例如 SQL 實現的復雜連接、排序、復雜條件等?
- 業務場景是不是可以通過讀取列族數據的方式更有效地實現,數據是否可以用字符型表示?
- 數據量是否足夠發揮 Hbase 列式數據庫的優勢?
- 是否可以找到合適的 Row-key ?隨機性的 Row-key 適合頻繁寫,有序的 Row-key 適合大量的讀。
7. Hbase 如何解決熱點的問題?
HBase 中的行是按照 Row-Key 的字典順序排序的,這種設計優化了掃描操作,可以將相關的行存放在臨近位置,便于掃描。然而糟糕的 Row-Key 設計是熱點的源頭。一旦由于 Row-Key 設計與業務場景不相符,大量訪問會使熱點 region 所在的單個機器超出自身承受能力,引起性能下降甚至不可用,這也會影響同一個 RegionServer 上的其他 region 。
那么如何避免這樣的問題發生呢?通常會有以下幾種設計思想可供參考:
- 反轉:將 Row-Key 的字符串可變的部分提到前面,相對固定的部分提到后面。這樣就會打亂 Row-Key 的有序性,在一定程度上降低了批量數據寫的性能,但是讀的時候就會減少熱點查詢,通過犧牲部分寫的性能而提升讀的性能。
- 前綴:將每一個 Row-Key 加一個隨機字符前綴,使得數據分散在多個不同的 Region ,達到 Region 負載均衡的目標。最終消除局部熱點,解決熱點讀寫的問題。
- 散列:通過哈希散列的方式將 Row-Key 重新設計,使得數據分散在不同的 Region ,同時效果要比前綴的方式更好,因為在讀的時候,它是可以通過哈希的計算減少讀性能的損耗。既解決了熱點問題,同時也不必消耗太多的讀性能。