一、HBase簡介
HBase 是一種分布式、面向列的 NoSQL 數(shù)據(jù)庫,其設(shè)計思想來源于 Google 的 Big Table。HBase 能存儲并處理海量的數(shù)據(jù),僅需使用普通配置的硬件,就能夠處理大型數(shù)據(jù)。HBase 與傳統(tǒng)關(guān)系型數(shù)據(jù)庫的對比:

本文從 HBase 的邏輯結(jié)構(gòu)、數(shù)據(jù)結(jié)構(gòu)、物理架構(gòu)三個方面介紹 HBase 的原理與架構(gòu)。
二、邏輯結(jié)構(gòu)

- 行鍵(Rowkey):行鍵是 HBase 用來檢索記錄的主鍵。行鍵可以是任意字符串,存儲時按照行鍵的字典序排序存儲。訪問 HBase 中的行有三種方式:(1)通過單個行鍵訪問;(2)通過行鍵的范圍查詢訪問;(3)使用過濾器進(jìn)行全表掃描。
- 列族(Column Family):HBase中的每個列都?xì)w屬于某個列族,列族在使用表之前定義,后續(xù)修改成本較大。HBase中列族數(shù)量有限制。
- 列(Column):一般都是從屬于某個列族,跟列族不一樣,列的數(shù)量一般的沒有強(qiáng)限制的,一個列族當(dāng)中可以有數(shù)百萬個列,而且這些列都可以動態(tài)添加的。
- 時間戳(Time Stamp):HBase 中同一個數(shù)據(jù)可以被保存多個版本,版本通過時間戳來索引。
通過 {行鍵,列族,列,時間戳} 可以唯一確定一個列單元(Cell)并獲取數(shù)據(jù)。和關(guān)系型數(shù)據(jù)庫不同的是,HBase 中的數(shù)據(jù)是沒有類型的,都是以字節(jié)碼形式存儲。
Rowkey設(shè)計
一條數(shù)據(jù)的唯一標(biāo)識就是 rowkey。一個 HBase 數(shù)據(jù)庫是否高效,很大程度會和 rowkey 的設(shè)計有關(guān)。隨著數(shù)據(jù)訪問方式的不同,rowkey 的設(shè)計也會有所不同。不過概括起來的宗旨只有一個,那就是盡可能選擇一個 rowkey,可以使你的數(shù)據(jù)均勻的分布在集群中。具體的建議如下:
- 當(dāng)客戶端需要頻繁的寫一張表,隨機(jī)的 rowkey 會獲得更好的性能。
- 當(dāng)客戶端需要頻繁的讀一張表,有序的 rowkey 則會獲得更好的性能。
- 對于時間連續(xù)的數(shù)據(jù)(例如 log),有序的 rowkey 會很方便查詢一段時間的數(shù)據(jù)(Scan 操作)。
常用的打散 rowkey 的方案:
- 生成隨機(jī)數(shù)、hash、散列值
- 字符串反轉(zhuǎn)
- 字符串拼接
二級索引
HBase 不像 MySQL,它本身是沒有二級索引的,只能根據(jù) rowkey 來訪問行。二級索引表就是把需要作為索引的列,拼到 rowkey 中,對應(yīng)的列多了一個主表的 row key。通過上面的方法,確定出二級索引表的 start row key和 end row key,然后根據(jù) filter 和索引表的列,過濾出需要行,取出其中的主表 rowkey 值,再去主表通過單個 rowkey 訪問。
三、數(shù)據(jù)結(jié)構(gòu)
很多數(shù)據(jù)庫或文件系統(tǒng)都使用 B+ 樹作為存儲數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu),但是HBase 卻使用的是 LSM(Log-Structured Merge Tree)樹,這是為什么呢?

B+ 樹雖然適合在磁盤中存儲,并且從原理上來看它的讀速度很快。但是它并非總是順序讀寫磁盤,例如它的節(jié)點(diǎn)進(jìn)行分裂操作時在內(nèi)存中會拆成兩個新的頁表,存儲到磁盤上很可能就是不連續(xù)的;或者其他更新插入刪除等操作,需要循環(huán)利用磁盤快,也會造成不連續(xù)問題。這也是 HBase 不使用 B+ 樹的原因,不進(jìn)行優(yōu)化的話隨機(jī) I/O 太多,范圍查詢和大量隨機(jī)寫時尤其明顯。
LSM 樹在讀寫之間作出取舍,通過犧牲部分讀性能,使用順序?qū)憗泶蠓岣邔懶阅?,因此適合寫多讀少,以及大規(guī)模數(shù)據(jù)讀取的場景。

LSM 樹首先在內(nèi)存中構(gòu)建一顆有序的小樹,隨著小樹的逐漸增大,達(dá)到一定閾值時會 flush 到磁盤上。所以 LSM 樹不像 B+ 樹一樣是一棵完整的大樹,一棵 LSM 樹就是一個個 B+ 樹合起來。多次flush之后會形成多個數(shù)據(jù)存儲文件,后臺線程會按照配置自動將多個文件合并成一個,此時多顆小樹就會被合并成一棵大樹。但是讀取時,由于不知道數(shù)據(jù)在哪棵小樹上,因此必須遍歷所有小樹(所以才說 LSM 犧牲了部分讀的性能),每棵小樹內(nèi)部數(shù)據(jù)是有序的。查詢是先查內(nèi)存中的部分,再去查磁盤上的部分。
四、物理架構(gòu)

HBase 是一個分布式數(shù)據(jù)庫,采用的是主從式架構(gòu),如上圖所示。一個HBase 集群中包括一個 HMaster 和多個 Region Server,通過 Zookeeper 做分布式管理服務(wù)。
4.1 主要組件介紹
Zookeeper
HBase 使用 Zookeeper 做分布式管理服務(wù),來維護(hù)集群中所有服務(wù)的狀態(tài)。Zookeeper 維護(hù)了哪些 servers 是健康可用的,并且在 server 故障時做出通知;Zookeeper 使用一致性協(xié)議來保證分布式狀態(tài)的一致性。
HMaster
- 管理用戶的 DDL 操作(創(chuàng)建、刪除、更新表)
- 統(tǒng)籌協(xié)調(diào)所有 Region Server:
(1)啟動時分配 Regions,在故障恢復(fù)和負(fù)載均衡時重分配 Regions
(2)監(jiān)控集群中所有 Region Server 實(shí)例(從 Zookeeper 獲取通知信息)
Region Server
Region Server 是管理一批 Region 的機(jī)器節(jié)點(diǎn),負(fù)責(zé)處理數(shù)據(jù)的讀寫請求??蛻舳苏埱髷?shù)據(jù)時和 Region Server 交互。Region Server 主要包含以下部分:
Region
HBase 表(Table)根據(jù) rowkey 的范圍被水平拆分成若干個 Region。每個 Region 都包含了這個 Region 的 start key 和 end key 之間的所有行(row)。Regions 被分配給集群中的某些節(jié)點(diǎn)來管理,即 Region Server,由它們來負(fù)責(zé)處理數(shù)據(jù)的讀寫請求。每個 Region Server 大約可以管理 1000 個 regions。
HLog
又叫 WAL(Write Ahead Log )是分布式文件系統(tǒng)上的一個文件,所有寫數(shù)據(jù)會首先被寫入該文件中(更新 WAL 是在文件尾部追加的方式,這種磁盤操作性能很高,不會太影響請求的整體響應(yīng)時間),它被用來做故障恢復(fù)。
MemStore
寫緩存,在內(nèi)存中存儲了新的還未被持久化到硬盤的數(shù)據(jù)。注意每個 Region 的每個 Column Family 都會有一個 MemStore。MemStore 中累積了足夠多的的數(shù)據(jù)后,整個有序數(shù)據(jù)集就會被寫入一個新的 HFile 文件到 HDFS 上。整個過程是一個順序?qū)懙牟僮?,速度非??欤驗(yàn)樗恍枰苿哟疟P頭。
HFile
HFile 在硬盤上(HDFS)存儲 HBase 數(shù)據(jù),以有序 KeyValue 的形式。HBase 為每個 Column Family 都創(chuàng)建一個 HFile,里面存儲了具體的 Cell,也即 KeyValue 數(shù)據(jù)。HFile 使用類似于 B+ 樹的索引來查詢數(shù)據(jù)。
下圖是數(shù)據(jù)的邏輯存儲與物理存儲的映射關(guān)系:

4.2 HBase讀流程
- Client 先訪問 zookeeper,獲取是哪一臺 Region Server 負(fù)責(zé)管理 meta table
- 訪問該Region Server,根據(jù) namespace、表名和 rowkey 在 meta table 中找到對應(yīng)的 Region 信息,及其 Region Server??蛻舳藭彺孢@個信息,以及 meta table 的位置信息本身
- 先從 MemStore 找數(shù)據(jù),如果沒有再到 StoreFile 上讀(StoreFile 是對 HFile 的封裝)
對于以后的的讀請求,客戶端從可以緩存中直接獲取 meta table 的位置信息,以及之前訪問過的 rowkey 的位置信息。除非因?yàn)?Region 被遷移了導(dǎo)致緩存失效,這時客戶端會重復(fù)上面的步驟,重新獲取相關(guān)位置信息并更新緩存。
讀合并
我們已經(jīng)發(fā)現(xiàn),每行(row)的 KeyValue cells 可能位于不同的地方,這些 cell 可能被寫入了 HFile;可能是最近剛更新的,還在 MemStore 中;也可能最近剛讀過,緩存在 Region Server 的讀緩存中。所以,當(dāng)讀一行 row 時,一次 read 操作會將緩存、MemStore 和 HFile 中的 cell 進(jìn)行合并。
由于每個 MemStore 可能會有多個 HFile,所以一次 read 請求可能需要多讀個文件,這可能會影響性能,這被稱為讀放大(Read Amplification)
4.3 HBase寫流程
寫數(shù)據(jù)流程
- Client 向 Region Server 發(fā)送寫請求(找到 Region Server 的過程與上文相同)
- Region Server 將數(shù)據(jù)寫到 HLog,為了數(shù)據(jù)的持久化和恢復(fù)
- Region Server 將數(shù)據(jù)寫到內(nèi)存(MemStore),并反饋 Client 寫成功
- 數(shù)據(jù)Flush:當(dāng) MemStore 數(shù)據(jù)達(dá)到閾值,將數(shù)據(jù)有序?qū)懭胍粋€新的 HFile 文件到 HDFS 上,將內(nèi)存中的數(shù)據(jù)刪除,同時刪除 HLog 中的歷史數(shù)據(jù)。然后記錄最后寫入的數(shù)據(jù)的最大序列號(sequence number)
Minor Compaction
HBase 會自動合并一些小的 HFile,重寫成少量更大的 HFiles。這個過程被稱為 minor compaction。它使用歸并排序算法,將小文件合并成大文件,有效減少 HFile 的數(shù)量。
Major Compaction
Major Compaction 合并重寫每個 Column Family 下的所有的 HFiles,成為一個單獨(dú)的大 HFile,在這個過程中,被刪除的和過期的 cell 會被真正從物理上刪除,這能提高讀的性能。但是因?yàn)?major compaction 會重寫所有的 HFile,會產(chǎn)生大量的硬盤 I/O 和網(wǎng)絡(luò)開銷。這被稱為寫放大(Write Amplification)。
Major compaction 可以被設(shè)定為自動調(diào)度。因?yàn)榇嬖趯懛糯蟮膯栴},major compaction 一般都安排在周末和半夜。
Region 分裂
一開始每個 table 默認(rèn)只有一個 Region。當(dāng)一個 Region 逐漸變得很大時,它會分裂(split)成兩個子 Region,每個子 Region 都包含了原來 一半的數(shù)據(jù),這兩個子 Region 并行地在原來這個 Region Server 上創(chuàng)建,這個分裂動作會被報告給 HMaster。出于負(fù)載均衡的目的,HMaster 可能會將新的 Region 遷移給其它 Region Server。
Bulkload
除了通過 api 寫入數(shù)據(jù)之外,HBase 還支持 bulkload 將大量數(shù)據(jù)進(jìn)行批量導(dǎo)入,其過程是通過啟動 MapReduce 任務(wù)直接生成 HFile 文件,再將 HFile 文件注冊到 HBase。Bulkload 適合如下場景:
- 大量數(shù)據(jù)一次性加載到 HBase
- 每天定時產(chǎn)出的離線數(shù)據(jù)
- 使用 put 加載大量數(shù)據(jù)到HBase速度變慢,且查詢速度變慢時
4.4 小結(jié)
上文 HBase 的架構(gòu),保證了 HBase 的如下優(yōu)點(diǎn):
- 強(qiáng)一致性: 當(dāng) write 返回時,所有的 reader 都會讀到同樣的值
- 擴(kuò)展性:數(shù)據(jù)變大時 Region 會分裂;使用 HDFS 存儲備份數(shù)據(jù)
- 故障恢復(fù): 使用 Write Ahead Log (類似于文件系統(tǒng)中的日志)
- 與 Hadoop 結(jié)合:使用 MapReduce 處理 HBase 數(shù)據(jù)會非常有效率
但是,它也存在一些不足之處,即業(yè)務(wù)持續(xù)可靠性:
- WAL 回放很慢
- 故障恢復(fù)很慢
- Major Compaction 時候 I/O 會飆升