HBase 詳細介紹及原理解析!

最近公司新項目用到 HBase,學習了一下,順便整理了筆記記錄一下!

覺得有收穫,希望幫忙點贊,轉發下哈,謝謝,謝謝

文章很長,所以筆記內容會同步到個人網站:http://hardyfish.top/,方便閱讀查看!

基本介紹

HBase 官網:https://hbase.apache.org/

Apache HBase 是 Hadoop 中一個支持分佈式的、可擴展的大數據存儲的數據庫。

當需要對大數據進行隨機、實時讀 / 寫訪問時,可以用 Apache HBase。

HBase 特點

列式存儲:

HBase 是面向列族的非關係型數據庫,每行數據列都可以不同,並且列可以按照需求進行動態增加。

因此在開始創建 HBase 表時,可以只創建列族,等需要時再創建相應的列。

數據壓縮:

列式存儲意味着數據往往類型相同,可以採用某種壓縮算法進行統一的壓縮存儲。

海量存儲:

HDFS 支持的海量存儲,存儲 PB 級數據仍能有百毫秒內的響應速度。

基本操作

Shell 操作

進入 HBase 客戶端命令操作界面:

hbase shell

查看幫助命令:

hbase(main):001:0> help

查看當前數據庫中有哪些表:

hbase(main):006:0> list

創建一張表:

創建 user 表, 包含 base_info、extra_info 兩個列族

hbase(main):007:0> create 'user''base_info''extra_info'
create 'user'{NAME ='base_info'VERSIONS ='3'}{NAME ='extra_info'}

添加數據操作:

向 user 表中插入信息,row key 爲 rk0001,列族 base_info 中添加 name 列標示符,值爲 zhangsan

hbase(main):008:0> put 'user''rk0001''base_info:name''zhangsan'

向 user 表中插入信息,row key 爲 rk0001,列族 base_info 中添加 age 列標示符,值爲 20

hbase(main):010:0>  put 'user''rk0001''base_info:age'20

查詢數據:

通過 rowkey 進行查詢:

  • 獲取 user 表中 row key 爲 rk0001 的所有信息
hbase(main):006:0> get 'user''rk0001'

查看 rowkey 下面的某個列族的信息:

  • 獲取 user 表中 row key 爲 rk0001,base_info 列族的所有信息
hbase(main):007:0> get 'user''rk0001''base_info'

查看 rowkey 指定列族指定字段的值:

  • 獲取 user 表中 row key 爲 rk0001,base_info 列族的 name、age 列標示符的信息
hbase(main):008:0> get 'user''rk0001''base_info:name''base_info:age'

查看 rowkey 指定多個列族的信息

  • 獲取 user 表中 row key 爲 rk0001,base_info、extra_info 列族的信息
hbase(main):010:0> get 'user''rk0001''base_info''extra_info'
hbase(main):011:0> get 'user''rk0001'{COLUMN =['base_info''extra_info']}
hbase(main):012:0> get 'user''rk0001'{COLUMN =['base_info:name''extra_info:address']}

指定 rowkey 與列值查詢:

  • 獲取 user 表中 row key 爲 rk0001,cell 的值爲 zhangsan 的信息
hbase(main):013:0> get 'user''rk0001'{FILTER ="ValueFilter(=, 'binary:zhangsan')"}

指定 rowkey 與列值模糊查詢:

  • 獲取 user 表中 row key 爲 rk0001,列標示符中含有 a 的信息
hbase(main):015:0> get 'user''rk0001'{FILTER ="(QualifierFilter(=,'substring:a'))"}

插入一批數據:

hbase(main):016:0> put 'user''rk0002''base_info:name''fanbingbing'

hbase(main):017:0> put 'user''rk0002''base_info:gender''female'

hbase(main):018:0> put 'user''rk0002''base_info:birthday''2000-06-06'

hbase(main):019:0> put 'user''rk0002''extra_info:address''Shanghai'

查詢所有數據:

  • 查詢 user 表中的所有信息
hbase(main):020:0> scan 'user'

列族查詢:

  • 查詢 user 表中列族爲 base_info 的信息

Scan:

  • 設置是否開啓 Raw 模式,開啓 Raw 模式會返回包括已添加刪除標記但是未實際刪除的數據。

  • VERSIONS 指定查詢的最大版本數。

hbase(main):021:0> scan 'user'{COLUMNS ='base_info'}

hbase(main):022:0> scan 'user'{COLUMNS ='base_info'RAW => true, VERSIONS => 5}

多列族查詢:

  • 查詢 user 表中列族爲 info 和 data 的信息。
hbase(main):023:0> scan 'user'{COLUMNS =['base_info''extra_info']}
hbase(main):024:0> scan 'user'{COLUMNS =['base_info:name''extra_info:address']}

指定列族與某個列名查詢:

  • 查詢 user 表中列族爲 base_info、列標示符爲 name 的信息。
hbase(main):025:0> scan 'user'{COLUMNS ='base_info:name'}

指定列族與列名以及限定版本查詢:

  • 查詢 user 表中列族爲 base_info、列標示符爲 name 的信息,並且版本最新的 5 個
hbase(main):026:0> scan 'user'{COLUMNS ='base_info:name'VERSIONS => 5}

指定多個列族與按照數據值模糊查詢:

  • 查詢 user 表中列族爲 base_infoextra_info 且列標示符中含有 a 字符的信息
hbase(main):027:0> scan 'user'{COLUMNS =['base_info''extra_info']FILTER ="(QualifierFilter(=,'substring:a'))"}

rowkey 的範圍值查詢:

  • 查詢 user 表中列族爲 info,rk 範圍是 [rk0001, rk0003) 的數據
hbase(main):028:0> scan 'user'{COLUMNS ='base_info'STARTROW ='rk0001'ENDROW ='rk0003'}

指定 rowkey 模糊查詢:

  • 查詢 user 表中 row key 以 rk 字符開頭的
hbase(main):029:0> scan 'user',{FILTER=>"PrefixFilter('rk')"}

更新數據值:

  • 把 user 表中 rowkey 爲 rk0001 的 base_info 列族下的列 name 修改爲 zhangsansan
hbase(main):030:0> put 'user''rk0001''base_info:name''zhangsansan'

指定 rowkey 以及列名進行刪除:

  • 刪除 user 表 row key 爲 rk0001,列標示符爲 base_info:name 的數據
hbase(main):032:0>  delete 'user''rk0001''base_info:name'

指定 rowkey,列名以及字段值進行刪除:

  • 刪除 user 表 row key 爲 rk0001,列標示符爲 base_info:name,timestamp 爲 1392383705316 的數據
hbase(main):033:0> delete 'user''rk0001''base_info:age'1564745324798

刪除 base_info 列族

hbase(main):034:0> alter 'user', NAME => 'base_info', METHOD => 'delete'
hbase(main):035:0> alter 'user''delete' ='base_info'

刪除 user 表數據:

hbase(main):036:0> truncate 'user'

刪除 user 表:

#先disable  再drop

hbase(main):036:0> disable 'user'
hbase(main):037:0> drop 'user'

#如果不進行disable,直接drop會報錯
ERROR: Table user is enabled. Disable it first.

數據模型

邏輯結構:

物理架構:

Rowkey(行鍵):

  • Table 的主鍵,Table 中的記錄按照 Rowkey 的字典序進行排序。

Column Family(列族):

  • 表中的每個列,都歸屬與某個列族。

  • 列族是表的 Schema 的一部分,必須在使用表之前定義。

Timestamp(時間戳):

  • 每次數據操作對應的時間戳,可以看作是數據的 Version 版本號。

Column(列):

  • 列族下面的具體列。

  • 屬於某一個 ColumnFamily,類似於 MySQL 當中創建的具體的列。

Cell(單元格):

  • {rowkey, column, version} 唯一確定的單元。

  • Cell 中的數據沒有類型,全部是以字節數組進行存儲。

基本原理

如何支持海量數據的隨機存取

1、利用了 HDFS 的分佈式存儲和 Hadoop 的分佈式計算能力:

  • 將數據存儲在 HDFS 上,並利用 Hadoop 的 MapReduce 框架進行分佈式計算,從而實現了高可擴展性和高併發性。

2、將數據按照行和列族的方式存儲在 HDFS 上:

  • 這種數據存儲方式使得 HBase 能夠實現高速的隨機讀寫功能。

3、利用了 LSM(Log-Structured Merge-Tree)算法:

  • 該算法通過內存和順序寫磁盤的方式,使得隨機寫入成爲可能,同時還能保證讀取效率。

4、支持數據的自動分片和負載均衡:

  • 可以支持 PB 級別的數據存儲和處理,從而滿足大規模數據的實時處理需求。

整體結構

HMaster:

  • HBase 集羣的主節點,負責監控 RegionServer,處理 Region 分配和負載均衡。

HRegionServer:

  • 管理 Region,處理對所分配 Region 的 IO 請求,Region 是表的分片, 由多個 Store 組成。

Zookeeper:

  • 維護 HBase 的運行狀態信息,如 Region 分佈信息等。

  • HMaster 和 RegionServer 都依賴 Zookeeper。

HRegion:

  • HBase 表的分片,由一個或者多個 Store 組成,存儲實際的表數據。

Store:

  • Store 以 Column Family 爲單位存儲數據,主要組成是 MemStore 和 StoreFile(HFile)。

  • 1 個 Column Family 的數據存放在一個 Store 中,一個 Region 包含多個 Store。

MemStore:

  • 內存存儲,用於臨時存放寫數據,達到閾值後刷入 StoreFile。

  • 數據會先寫入到 MemStore 進行緩衝,然後再把數據刷到磁盤。

  • 通過內存,也加快了讀寫速度。

StoreFile(HFile):

  • 磁盤上面真正存放數據的文件。

HDFS:

  • 用來持久化存儲 HFiles。

一個列族就劃分成一個 Store,如果一個表中只有 1 個列族,那麼每一個 Region 中只有一個 Store。

一個 Store 裏面只有一個 MemStore。

一個 Store 裏面有很多個 StoreFile, 最後數據是以很多個 HFile 文件保存在 HDFS 上。

  • StoreFile 是 HFile 的抽象對象。

  • 每次 MemStore 刷寫數據到磁盤,就生成對應的一個新的 HFile 文件出來。

負載均衡

HBase 官方目前支持兩種負載均衡策略:

  • SimpleLoadBalancer 策略和 StochasticLoadBalancer 策略。

SimpleLoadBalancer 策略:

這種策略能夠保證每個 RegionServer 的 Region 個數基本相等。

假設集羣中一共有 n 個 RegionServer,m 個 Region ,那麼集羣的平均負載就是 average = m/n。

  • 這種策略能夠保證所有 RegionServer 上的 Region 個數都在 [floor(average),ceil(average)]之間。

因此, SimpleLoadBalancer 策略中負載就是 Region 個數,集羣負載遷移計劃就是 Region 從個數較多的 RegionServer 上遷移到個數較少的 RegionServer 上。

雖然集羣中每個 RegionServer 的 Region 個數都基本相同,但如果某臺 RegionServer 上的 Region 全部都是熱點數據,導致 90 % 的讀寫請求還是落在了這臺 RegionServer 上,這樣沒有達到負載均衡的目的。

StochasticLoadBalancer 策略:

它對於負載的定義不再是 Region 個數這麼簡單,而是由多種獨立負載加權計算的複合值,這些獨立負載包括:

  • Region 個數,Region 負載,讀請求數,寫請求數,Storefile 大小,MemStore 大小,數據本地率,移動代價。

這些獨立負載經過加權計算會得到一個代價值,系統使用這個代價值來評估當前 Region 分佈是否均衡,越均衡代價值越低。

  • HBase 通過不斷隨機挑選迭代來找到一組 Region 遷移計劃,使得代價值最小。

Flush 機制

MemStore 的大小超過某個值的時候,會 Flush 到磁盤,默認爲 128M。

MemStore 中的數據時間超過 1 小時,會 Flush 到磁盤。

HRegionServer 的全局 MemStore 的大小超過某大小會觸發 Flush 到磁盤,默認是堆大小的 40%。

Compact 機制

HBase 需要在必要的時候將小的 Store File 合併成相對較大的 Store File,這個過程爲 Compaction。

  • 爲了防止小文件過多,以保證查詢效率。

在 HBase 中主要存在兩種類型的 Compaction 合併。

Minor Compaction 小合併:

  • 在將 Store 中多個 HFile 合併爲一個 HFile。

  • 這個過程中,達到 TTL(記錄保留時間)會被移除,刪除和更新的數據僅僅只是做了標記,並沒有物理移除。

  • 這種合併的觸發頻率很高。

Major Compaction 大合併:

  • 合併 Store 中所有的 HFile 爲一個 HFile。

  • 這個過程有刪除標記的數據會被真正移除,同時超過單元格 maxVersion 的版本記錄也會被刪除。

  • 合併頻率比較低,默認 7 天執行一次,並且性能消耗非常大,建議生產關閉 (設置爲 0),在應用空閒時間手動觸發。

  • 一般可以是手動控制進行合併,防止出現在業務高峯期。

Region 拆分機制

Region 中存儲的是大量的 Rowkey 數據,當 Region 中的數據條數過多的時候,直接影響查詢效率。

  • 當 Region 過大的時候,HBase 會拆分 Region。

HBase 的 Region Split 策略一共有以下幾種。

ConstantSizeRegionSplitPolicy:

0.94 版本前默認切分策略。

當 Region 大小大於某個閾值之後就會觸發切分,一個 Region 等分爲 2 個 Region。

  • 在生產線上這種切分策略有相當大的弊端:切分策略對於大表和小表沒有明顯的區分。

閾值設置較大對大表比較友好,但是小表就有可能不會觸發分裂,極端情況下可能就 1 個。

如果設置較小則對小表友好,但一個大表就會在整個集羣產生大量的 Region,這對於集羣的管理、資源使用、Failover 不好。

IncreasingToUpperBoundRegionSplitPolicy:

0.94 版本~ 2.0 版本默認切分策略。

總體看和 ConstantSizeRegionSplitPolicy 思路相同,一個 Region 大小大於設置閾值就會觸發切分。

  • 但這個閾值並不是一個固定的值。

  • 而是會在一定條件下不斷調整,調整規則和 Region 所屬表在當前 RegionServer 上的 Region 個數有關係。

Region Split 的計算公式是:

  • RegionCount^3 * 128M * 2,當 Region 達到該 size 的時候進行 split。

例如:

  • 第一次 split:1^3 * 256 = 256MB

  • 第二次 split:2^3 * 256 = 2048MB

  • 第三次 split:3^3 * 256 = 6912MB

  • 第四次 split:4^3 * 256 = 16384MB > 10GB,取較小的值 10GB

後面每次 split 的 size 都是 10GB 了。

SteppingSplitPolicy:

2.0 版本默認切分策略。

依然和待分裂 Region 所屬表在當前 RegionServer 上的 Region 個數有關係。

如果 Region 個數等於 1,切分閾值爲flush size * 2,否則爲 MaxRegionFileSize。

這種切分策略對於大集羣中的大表。

小表會比 IncreasingToUpperBoundRegionSplitPolicy 更加友好,小表不會再產生大量的小 Region,而是適可而止。

KeyPrefixRegionSplitPolicy:

根據 RowKey 的前綴對數據進行分組,這裏是指定 RowKey 的前多少位作爲前綴,比如 RowKey 都是 16 位的,指定前 5 位是前綴。

那麼前 5 位相同的 RowKey 在進行 region split 的時候會分到相同的 Region 中。

DelimitedKeyPrefixRegionSplitPolicy:

保證相同前綴的數據在同一個 Region 中,例如 RowKey 的格式爲:userid_eventtype_eventid,指定的 delimiter 爲_。

則 split 的的時候會確保 userid 相同的數據在同一個 Region 中。

DisabledRegionSplitPolicy:

不啓用自動拆分,需要指定手動拆分。

預分區

當一個 table 剛被創建的時候,HBase 默認的分配一個 Region 給 table。

  • 這時所有的讀寫請求都會訪問到同一個 RegionServer 的同一個 Region 中。

  • 這個時候就達不到負載均衡的效果了,集羣中的其他 RegionServer 就可能會處於比較空閒的狀態。

解決辦法:

  • 可以用預分區(pre-splitting),在創建 table 的時候就配置好,生成多個 Region。

如何預分區?

  • 每一個 Region 維護着 startRow 與 endRowKey。

  • 如果加入的數據符合某個 Region 維護的 RowKey 範圍,則該數據交給這個 Region 維護。

手動指定預分區:

create 'person','info1','info2',SPLITS =['1000','2000','3000','4000']

Region 定位

HBase 支持 put , get , delete 和 scan 等基礎操作,所有這些操作的基礎是 region 定位。

region 定位基本步驟:

客戶端與 ZooKeeper 交互,查找 hbase:meta 系統表所在的 Regionserver。

hbase:meta 表維護了每個用戶表中 rowkey 區間與 Region 存放位置的映射關係,具體如下:

  • rowkey : table name,start key,region id。

  • value : RegionServer 對象 (保存了 RegionServer 位置信息等)。

客戶端與 hbase:meta 系統表所在 RegionServer 交互,獲取 rowkey 所在的 RegionServer。

客戶端與 rowkey 所在的 RegionServer 交互,執行該 rowkey 相關操作。

需要注意:

  • 客戶端首次執行讀寫操作時才需要定位 hbase:meta 表的位置。

  • 之後會將其緩存到本地,除非因 region 移動導致緩存失效,客戶端纔會重新讀取 hbase:meta 表位置,並更新緩存。

讀寫流程

讀操作:

首先從 ZooKeeper 找到 meta 表的 region 位置,然後讀取hbase:meta 表中的數據,hbase:meta 表中存儲了用戶表的 region 信息。

根據要查詢的 namespace 、表名和 rowkey 信息,找到寫入數據對應的 Region 信息。

找到這個 Region 對應的 RegionServer ,然後發送請求。

查找對應的 Region。

先從 MemStore 查找數據,如果沒有,再從 BlockCache 上讀取。

  • HBase 上 RegionServer 的內存分爲兩個部分:

  • 一部分作爲 MemStore,主要用來寫。

  • 另外一部分作爲 BlockCache,主要用於讀數據。

如果 BlockCache 中也沒有找到,再到 StoreFile(HFile) 上進行讀取。

  • 從 StoreFile 中讀取到數據之後,不是直接把結果數據返回給客戶端,而是把數據先寫入到 BlockCache 中,目的是爲了加快後續的查詢,然後在返回結果給客戶端。

寫操作:

首先從 ZooKeeper 找到 hbase:meta 表的 Region 位置,然後讀取 hbase:meta表中的數據,hbase:meta 表中存儲了用戶表的 Region 信息。

根據 namespace 、表名和 rowkey 信息找到寫入數據對應的 Region 信息。

找到這個 Region 對應的 RegionServer ,然後發送請求。

把數據分別寫到 HLog (WriteAheadLog) 和 MemStore 各一份。

MemStore 達到閾值後把數據刷到磁盤,生成 StoreFile 文件。

刪除 HLog 中的歷史數據。

BulkLoad 機制

用戶數據位於 HDFS 中,業務需要定期將這部分海量數據導入 HBase 系統,以執行隨機查詢更新操作。

這種場景如果調用寫入 API 進行處理,極有可能會給 RegionServer 帶來較大的寫人壓力。

  • 引起 RegionServer 頻繁 flush,進而不斷 compact、split,影響集羣穩定性。

  • 引起 RegionServer 頻繁 GC, 影響集羣穩定性。

  • 消耗大量 CPU 資源、帶寬資源、內存資源以及 IO 資源,與其他業務產生資源競爭。

  • 在某些場景下,比如平均 KV 大小比較大的場景,會耗盡 RegionServer 的處理線程, 導致集羣阻塞。

所以 HBase 提供了另一種將數據寫入 HBase 集羣的方法:BulkLoad。


BulkLoad 首先使用 MapReduce 將待寫入集羣數據轉換爲 HFile 文件,再直接將這些 HFile 文件加載到在線集羣中。

BulkLoad 沒有將寫請求發送給 RegionServer 處理,可以有效避免上述一系列問題。

常見問題

熱點問題

什麼是熱點?

檢索 HBase 的記錄首先要通過 Row Key 來定位數據行。

當大量的 Client 訪問 HBase 集羣的一個或少數幾個節點,造成少數 Region Server 的讀 / 寫請求過多、負載過大,而其他 Region Server 負載卻很小,就造成了 熱點 現象。

解決方案:

預分區:

  • 目的讓表的數據可以均衡的分散在集羣中,而不是默認只有一個 Region 分佈在集羣的一個節點上。

加鹽:

  • 在 Rowkey 的前面增加隨機數,具體就是給 Rowkey 分配一個隨機前綴以使得它和之前的 Rowkey 的開頭不同。

哈希:

  • 哈希會使同一行永遠用一個前綴加鹽。

  • 也可以使負載分散到整個集羣,但是讀是可以預測的。

  • 使用確定的哈希可以讓客戶端重構完整的 Rowkey,可以使用 get 操作準確獲取某一個行數據。

反轉:

  • 反轉固定長度或者數字格式的 Rowkey。

  • 這樣可以使得 Rowkey 中經常改變的部分放在前面。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/VKZbRbXeA6MfvZQYnEX3zQ