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_info 和 extra_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