Hbase熱點問題

@Author  : Spinach | GHB
@Link    : http://blog.csdn.net/bocai8058

Hbase結構及rowkey

Hbase結構

Hbase的表組成:一個表可以理解成是行的集合,行(記錄)是列族的集合,列族是列的集合。

  1. 列族column family:它是column的集合,在創建表的時候就指定,不能頻繁修改。值得注意的是,列族的數量越少越好,因爲過多的列族相互之間會影響,生產環境中的列族一般是一個到兩個。

數據的持久化文件HFile中是按照Key-Value存儲的,同一個列族的所有列存儲在同一個底層存儲文件裏。Hbase的數據在HDFS中的路徑結構如下:

hdfs://h201:8020/hbase/data/${名字空間}/${表名}/${區域名稱}/${列族名稱}/${文件名}
# 舉例:/hbase/data/ns1/t1/a4d63a61a8da24a863bff3c8d7cd20de/f1/c2a7fa8c41304b9e9b8b24b4a89171ce

其中{區域名稱}是t1的region, 由每張表切割形成,一張表由若干個region組成,不同的region分到不同的region server以便均衡負載
  1. 列column:和列族的限制數量不同,列族可以包含很多個列,前面說的“幾十億行*百萬列”就是這個意思。
  2. 列的值value:存在單元格(cell)中。每一列的值允許有多個版本,由timestamp來區分不同版本。多個版本產生原因:向同一行下面的同一個列多次插入數據,每插入一次就有一個對應版本的value。
# 從以下示例中可以看出habse存儲的數據格式
# hbase(main):010:0>scan 'ns1:t1',{STARTROW => 'row1', LIMIT => 5}
ROW                           COLUMN+CELL                           
row10                         column=f1:age,timestamp=1490608685532,value=\x00\x00\x00\x0A
row10                         column=f1:id,timestamp=1490608685532,value=\x00\x00\x00\x0A
row10                         column=f1:name,timestamp=1490608685532,value=tonykidkid10
row100                        column=f1:age,timestamp=1490608685532,value=\x00\x00\x00\x00
row100                        column=f1:id,timestamp=1490608685532,value=\x00\x00\x00d
row100                        column=f1:name,timestamp=1490608685532,value=tonykidkid100
row1000                       column=f1:age,timestamp=1490608685532,value=\x00\x00\x00\x00
row1000                       column=f1:id,timestamp=1490608685532,value=\x00\x00\x03\xE8
row1000                       column=f1:name,timestamp=1490608685532,value=tonykidkid1000 
row1001                       column=f1:age,timestamp=1490608685532,value=\x00\x00\x00\x01
row1001                       column=f1:id,timestamp=1490608685532,value=\x00\x00\x03\xE9
row1001                       column=f1:name,timestamp=1490608685532,value=tonykidkid1001 
row1002                       column=f1:age,timestamp=1490608685532,value=\x00\x00\x00\x02
row1002                       column=f1:id,timestamp=1490608685532,value=\x00\x00\x03\xEA
row1002                       column=f1:name,timestamp=1490608685532,value=tonykidkid1002
5 row(s) in0.0550 seconds

以row1002這一條記錄來說明——

row1002是row key   .row key在hbase裏是唯一的,而且只出現一次,否則就是在更新同一行。
也就是說有幾個不同的row key就有幾條不同的記錄。
我們可以通過不同的行健來增加多行記錄。行健的唯一性這個特性類似於關係型數據庫的主鍵。

column=f1:name, timestamp=1490608685532,value=tonykidkid1002
// 表示列族f1包含name列,“列族+列名”決定了不同的列。
// Timestamp是時間戳,表示此列對應值的版本,默認VERSIONS=1,value就是列族f1下name的值了。

需要明確的一點,hbase是通過3個維度來對記錄進行快速定位:行健 + (列族+列名) + 時間戳,即:
            row key àcolumn family + qualifier à timestamp

結合上面的例子,t1表的每一行有3個column, 分別是age,id, name.
比如我想查rowkey爲row1002的name的值,命令行下的查詢語法:

# hbase(main):021:0>get 'ns1:t1', 'row1002' ,'f1:name'
COLUMN                 CELL                                 
f1:name               timestamp=1490608685532,value=tonykidkid1002 
1 row(s) in0.0420 seconds

# 或者這樣查也是對的:
# hbase(main):022:0>get 'ns1:t1', 'row1002' , {COLUMN => 'f1:name'}
COLUMN                 CELL                                         
f1:name               timestamp=1490608685532,value=tonykidkid1002  
1 row(s) in0.0300 seconds

行健RowKey

HBase是採用Key-Value格式來存儲數據,那麼Row key就是Key-Value中的Key。

Rowkey是表記錄在hbase表中的唯一標識,作爲檢索表記錄的唯一“主鍵”。hbase加載數據時,也是根據row key的二進制順序由小到大進行的。

Row key的最大長度爲64KB,它是一段二進制碼流(byte[ ]),所以任何數據類型都可以用來做row key,內容可以由我們用戶自定義、自設計。

HBase根據Row key來進行檢索,系統通過找到某個Row key (或者某個 Row key 範圍)所在的Region,然後將查詢數據的請求路由到該Region獲取此條記錄。HBase的檢索有3種方式:

  • 通過get方式,指定rowkey獲取唯一一條記錄
  • 通過scan方式,設置起始行和結束行參數進行範圍匹配
  • 全表掃描,即直接掃描整張表中所有行記錄

row key按照字典順序排序的規則:在字典順序中按照二進制逐個字節、從左到右對比每一個row key,例如row1001小於row1002,rowxxa小於rowxxb等等。這種設計優化了scan,可以將相關的行以及會被一起讀取的行存在相近位置,便於scan。

熱點/數據傾斜問題

熱點:檢索habse的記錄首先要通過row key來定位數據行。當大量的client訪問hbase集羣的一個或少數幾個節點,造成少數region server的讀/寫請求過多、負載過大,而其他region server負載卻很小,就造成了“熱點”現象。

數據傾斜:Hbase可以被劃分爲多個Region,但是默認創建時只有一個Region分佈在集羣的一個節點上,數據一開始時都集中在這個Region,也就是集中在這一個節點上,就算region存儲達到臨界值時被劃分,數據也是存儲在少數節點上。這就是數據傾斜。

HBase中的行是按照rowkey的字典順序排序的,這種設計優化了scan操作,可以將相關的行以及會被一起讀取的行存取在臨近位置,便於scan。

rowkey設計是熱點的源頭。

HBase中,表會被劃分爲1…n個Region,被託管在RegionServer中。Region有二個重要的屬性:StartKey與EndKey表示這個Region維護的rowKey範圍,當我們要讀/寫數據時,如果rowKey落在某個start-end key範圍內,那麼就會定位到目標region並且讀/寫到相關的數據。

默認的情況下,創建一張表是,只有1個region,start-end key沒有邊界,所有數據都在這個region裏裝,然而,當數據越來越多,region的size越來越大時,大到一定的閥值,hbase認爲再往這個region裏塞數據已經不合適了,就會找到一個midKey將region一分爲二,成爲2個region,這個過程稱爲分裂(region-split)。而midKey則爲這二個region的臨界(這個中間值這裏不作討論是如何被選取的)。

此時,我們假設假設rowkey小於midKey則爲會被塞到1區,大於等於midKey則會被塞到2區,如果rowkey還是順序增大的,那數據就總會往2區裏面寫數據,而1區現在處於一個被冷落的狀態,而且是半滿的。2區的數據滿了會被再次分裂成2個區,如此不斷產生被冷落而且不滿的Region,當然,這些region有提供數據查詢的功能。

這種設計是分佈式系統一個很大的弊端,而且這樣導致數據傾斜和熱點問題,從而導致集羣的資源得不到很好的利用。

數據傾斜的解決方法

  • 預分區

預分區,讓表的數據可以均衡的分散在集羣中,而不是默認只有一個region分佈在集羣的一個節點上。(預分區個數=節點的倍數,看數據量估算,region不足了會被分列,預分區後每個region的rowkey還是有序的)

一個RegionServer能管理10-1000個Region,0.92.x版本後,默認的Region大小爲10G,向下可以支持256MB,向上可以支持到20G,也就是說,每個RegionServer能管理的數據量爲2.5GB-20TB。

如果有5個節點,3年內數據量爲5T,那麼分區數可以預設爲:
5000G/10G=500個region

這500個Region就會被均衡的分佈在集羣各個節點上(具體分佈看機器的性能和存儲空間而定),機器硬盤不足可以添加硬盤,性能不足可以添加新節點(添加新機器)。

  • 加鹽

這裏所說的加鹽不是密碼學中的加鹽,而是在rowkey的前面增加隨機數,具體就是給rowkey分配一個隨機前綴以使得它和之前的rowkey的開頭不同。給多少個前綴?這個數量應該和我們想要分散數據到不同的region的數量一致(類似hive裏面的分桶)。加鹽之後的rowkey就會根據隨機生成的前綴分散到各個region上,以避免熱點。

  • 哈希

哈希會使同一行永遠用一個前綴加鹽。哈希也可以使負載分散到整個集羣,但是讀卻是可以預測的。使用確定的哈希可以讓客戶端重構完整的rowkey,可以使用get操作準確獲取某一個行數據。

  • 反轉

第三種防止熱點的方法是反轉固定長度或者數字格式的rowkey。這樣可以使得rowkey中經常改變的部分(最沒有意義的部分)放在前面。這樣可以有效的隨機rowkey,但是犧牲了rowkey的有序性。

反轉rowkey的例子:以手機號爲rowkey,可以將手機號反轉後的字符串作爲rowkey,從而避免諸如139、158之類的固定號碼開頭導致的熱點問題。

  • 時間戳反轉

一個常見的數據處理問題是快速獲取數據的最近版本,使用反轉的時間戳作爲rowkey的一部分對這個問題十分有用,可以用Long.Max_Value - timestamp追加到key的末尾,例如[key][reverse_timestamp] ,[key] 的最新值可以通過scan [key]獲得[key]的第一條記錄,因爲HBase中rowkey是有序的,第一條記錄是最後錄入的數據。

整個rowkey(timestamp並不是必要的,視業務而定)
rowkey=哈希(主鍵<遞增的id\手機號碼等>)+Long.Max_Value - timestamp

rowkey設計原則

  • rowkey唯一原則

必須在設計上保證其唯一性,rowkey是按照二進制字節數組排序存儲的,因此,設計rowkey的時候,要充分利用這個排序的特點,將經常讀取的數據存儲到一塊,將最近可能會被訪問的數據放到一塊。所以設計rwo key時儘量把體現業務特徵的信息、業務上有唯一性的信息編進row key。

  • rowkey長度原則

rowkey是一個二進制碼流,可以是任意字符串,最大長度 64kb ,實際應用中一般爲10-100byte,以byte[] 形式保存,一般設計成定長。建議越短越好,不要超過16個字節,2個原因:

  1. 原因1:數據的持久化文件HFile中是按照(Key,Value)存儲的,如果rowkey過長,例如超過100byte,那麼1000萬行的記錄計算,僅row key就需佔用100*1000萬=10億byte,近1Gb。這樣會極大影響HFile的存儲效率!

  2. 原因2:MemStore將緩存部分數據到內存,若 rowkey字段過長,內存的有效利用率就會降低,就不能緩存更多的數據,從而降低檢索效率。

目前操作系統都是64位系統,內存8字節對齊,控制在16個字節,8字節的整數倍利用了操作系統的最佳特性。

  • rowkey散列原則

如果rowkey按照時間戳的方式遞增,不要將時間放在二進制碼的前面,建議將rowkey的高位作爲散列字段,由程序隨機生成,低位放時間字段,這樣將提高數據均衡分佈在每個RegionServer,以實現負載均衡的機率。如果沒有散列字段,首字段直接是時間信息,所有的數據都會集中在一個RegionServer上,這樣在數據檢索的時候負載會集中在個別的RegionServer上,造成熱點問題,會降低查詢效率。

預分區splitkeys選取

  1. 取樣,先隨機生成一定數量的rowkey(10萬、100萬),將取樣數據按升序排序放到一個集合裏。
  2. 根據預分區的region個數,對整個集合平均分割,即是相關的splitkeys。
  3. HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][] splitkeys)可以指定預分區的splitkey,即指定region間的rowkey臨界值。

Column Family列族的設計數量不宜過多(建議不設置多個)

  1. 這裏必須先知道Hbase的架構設計
    HBase的表是由一到多個Region組成的;
    Region是由一到多個HStore組成的,HStore對應列族,也就是表中有多個CF,就會有多個個HStore;而分列的時候是根據Region的大小切分的。
  2. 現在已經知道必須要先做預分區和key的散列了,那麼,假設表中有多個列族,也就是多個CF,對應也就有多個HStore,而此時,假設多個列族的數據分配不均衡就會出現下面情況:
    如果某個Region裏面的A HStore有1000萬條數據,而B HStore裏面只有100條數據。那麼,這100條數據會被分到多個region中,讀取B HStore的數據時,跨了多個region,導致查詢效率降低。
  3. Hbase的列族設計是爲了加快讀取速度的,同一個表的數據,按列族把數據劃分後,數據查詢時能縮小數據的範圍(查詢數據時指定列族),查詢效率會加快,然而,如果數據分配不均衡就會導致效率降低,所以並不建議多個列族,可以建多個表,數據量小的表Region數量也可以設置小一點。

一對多設計和寬表

假設,現在有用戶表和銀行卡表,一個用戶對應多張銀行卡
傳統的關係型數據(RMDB),我們會設計成兩張表,通過關聯查詢獲取數據;
如果Hbase也設計成兩張表,那麼如果想獲取用戶和銀行卡的數據,就得查詢兩次才能獲取到數據。
如果設計成一張寬表,把用戶數據放到銀行卡的表上,也就是用戶的數據被存放了多次,但是獲取數據的時候只需要查詢一次就能把用戶和用戶銀行卡的數據查詢出來。

寬表的缺點:浪費存儲空間,如果修改用戶數據,那麼是覆蓋多條數據,操作繁瑣,但是並不影響性能。

寬表的優點:查詢效率提高。

兩種設計都有優點和缺點,浪費性能還是浪費存儲空間,這需要視具體情況而定,需要作出取捨。

引用:https://blog.csdn.net/qq_31598113/article/details/71278857 | https://blog.csdn.net/weixin_41279060/article/details/78855679 | https://gitee.com/boat824109722/hbase-api-demo


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章