1. 程式人生 > >14.大資料學習之旅——HBASE表設計&HBase優化

14.大資料學習之旅——HBASE表設計&HBase優化

HBASE表設計


Rowkey設計

Rowkey是不可分割的位元組數,按字典排序由低到高儲存在表中。
在設計HBase表時,Rowkey設計是最重要的事情,應該基於預期的訪問模式來為Rowkey建
模。Rowkey決定了訪問HBase表時可以得到的效能,原因有兩個:
1)Region基於Rowkey為一個區間的行提供服務,並且負責區間的每一行;
2)HFile在硬碟上儲存有序的行。
這兩個因素是相互關聯的。當Region將記憶體中資料刷寫為HFile時,這些行已經排過序,也會
有序地寫到硬碟上。Rowkey的有序特性和底層儲存格式可以保證HBase表在設計Rowkey之後
的良好效能。
關係型資料庫可以在多列上建立索引,但是HBase只能在Rowkey上建立索引。(可以通過ES
為Hbase的列建立索引) 而設計Rowkey有各種技巧,而且可以針對不同訪問模式進行優化,
我們接下來就研究一下。

1. 將Rowkey以字典順序從大到小排序
原生HBase只支援從小到大的排序,但是現在有個需求想展現影片熱度排行榜,這就要求實現
從大到小排列,針對這種情況可以採用Rowkey=Integer.MAX_VALUE-Rowkey的方式將
Rowkey進行轉換,最大的變最小,最小的變最大,在應用層再轉回來即可完成排序需求。

2.RowKey儘量雜湊設計
最重要的是要保證雜湊,這樣就會保證所有的資料都不是在一個Region上,從而避免讀寫的時
候負載會集中在個別Region上。比如ROWKEY_Random

3.RowKey的長度儘量短

如果Rowkey太長,第一儲存開銷會增加,影響儲存效率;第二記憶體中Rowkey欄位過長,會導
致記憶體的利用率降低,進而降低索引命中率。
Rowkey是一個二進位制碼流,Rowkey的長度被很多開發者建議說設計在10~100個位元組,不過
建議是越短越好,不要超過16個位元組。

原因如下:
1)資料的持久化檔案HFile中是按照KeyValue儲存的,如果Rowkey過長比如100個字
節,1000萬列資料光Rowkey就要佔用100*1000萬=10億個位元組,將近1G資料,這會極大影
響HFile的儲存效率;
2)MemStore將快取部分資料到記憶體,如果Rowkey欄位過長記憶體的有效利用率會降低,系統
將無法快取更多的資料,這會降低檢索效率。因此Rowkey的位元組長度越短越好。

4.RowKey唯一
5.RowKey建議用String型別
雖然行鍵在HBase中是以byte[]位元組陣列的形式儲存的,但是建議在系統開發過程中將其資料類
型設定為String型別,保證通用性。
常用的行鍵字串有以下幾種:
1)純數字字串,譬如9559820140512;
2)數字+特殊分隔符,譬如95598-20140512;
3)數字+英文字母,譬如city20140512;
4)數字+英文字母+特殊分隔符,譬如city_20140512

6.RowKey設計得最好有意義
RowKey的主要作用是為了進行資料記錄的唯一性標示,但是唯一性並不是其全部,具有明確
意義的行鍵對於應用開發、資料檢索等都具有特殊意義。
譬如數字字串:9559820140512,其實際意義是這樣:95598(電網客服電話)+
20140512(日期)。
行鍵往往由多個值組合而成,而各個值的位置順序將影響到資料儲存和檢索效率,所以在設計
行鍵時,需要對日後的業務應用開發有比較深入的瞭解和前瞻性預測,才能設計出可儘量高效
率檢索的行鍵。

7.具有定長性
行鍵具有有序性的基礎便是定長,譬如20140512080500、20140512083000,這兩個日期時
間形式的字串是遞增的,不管後面的秒數是多少,我們都將其設定為14位數字形式,如果我
們把後面的0去除了,那麼201405120805將大於20140512083,其有序性發生了變更。所以
我們建議,行鍵一定要設計成定長的。
此外,目前作業系統是都是64位系統,記憶體8位元組對齊。控制在16個位元組,8位元組的整數倍利用
作業系統的最佳特性。

列族的設計


在設計hbase表時候,列族不宜過多,儘量的要少使用列族。
經常要在一起查詢的資料最好放在一個列族中,儘量的減少跨列族的資料訪問。

HBase優化


硬體和作業系統調優

1)配置記憶體
HBase對於記憶體的消耗是非常大的,主要是其LSM樹狀結構、快取機制和日誌記錄機制決定的,所以實體記憶體當然
是越大越好。並且現在記憶體的價格已經降到可以批量配置的程度,例如一條三星DDR3的16GB記憶體,價格大約在
1000元左右。
在網際網路領域,伺服器記憶體方面的主流配置已經是64GB,所以一定要根據實際的需求和預算配備伺服器記憶體。如果
資源很緊張,推薦記憶體最小在32GB,如果再小會嚴重影響HBase叢集效能。
2)配置CPU
HBase給使用者的印象可能更偏向於“記憶體型”NoSQL資料庫,從而忽略了CPU方面的需求,其實HBase在某些應
用上對CPU的消耗非常大,例如頻繁使用過濾器,因為在過濾器中包含很多匹配、搜尋和過濾的操作;多條件組合
掃描的場景也是CPU密集型的;壓縮操作很頻繁等。如果伺服器CPU不夠強悍,會導致整個叢集的負載非常高,很
多執行緒都在阻塞狀態(非網路阻塞和死鎖的情況)。
一般CPU的品牌有Intel、AMD、IBM,Intel是主流。
現在的伺服器支援1、2、3、4、6、8、10路CPU,而每路CPU的核心有雙核、四核、六核、八核、十二核。CPU數
量和核心數之間可以互相搭配,當然值越大相應的價格越高。建議每臺物理節點至少使用雙路四核CPU(2×4),
主流是2~8路,一般單顆CPU至少四核。一顆四核心CPU,便宜的,價格在1500元左右,還是可以接受的。所以,
對於CPU密集型的叢集,當然是越多越好。
磁碟的配置
如果是機械盤,看轉速,14000轉,一般的是7000轉。
可以考慮用SSD固態硬碟,底層是通過電阻器原件構架的,速度接近於記憶體
3)垃圾回收器(GC)的選擇

對於執行HBase相關程序JVM的垃圾回收器,不僅僅關注吞吐量,還關注停頓時間,而且兩者之間停頓時間更為重
要,因為HBase設計的初衷就是解決大規模資料集下實時訪問的問題。那麼按照首位是停頓時間短,從這個方面
CMS和G1有著非常大的優勢。
而CMS作為JDK1.5已經出現的垃圾收集器,已經成熟應用在網際網路等各個行業。所以,選用CMS作為老年代的垃圾
回收器。與CMS搭配的新生代收集器有Serial和ParNew,而對比這兩個收集器,明顯ParNew具有更好的效能,所
以新生代選用ParNew作為垃圾收集器。那麼,最終選用的垃圾收集器搭配組合是CMS+ParNew。而且很多成熟應
用已經驗證了這種組合搭配的優勢。
與CMS收集器相關的幾個重要引數的具體含義、預設值和相關說明詳見表。
在這裡插入圖片描述
配置方式:需要新增到hbase-env.sh檔案中
export HBASE_OPTS="-XX:+UseConcMarkSweepGC" -XX:CMSInitiatingOccupancyFraction=70 -XX:
+UseCMSCompactAtFullCollection
4)JVM堆大小設定
堆記憶體大小引數hbase-env.sh檔案中設定,設定的程式碼如下:
export HBASE_HEAPSIZE=16384
在上面程式碼中指定堆記憶體大小是16284,單位是MB,即16GB。當然,這個值需要根據節點實際的實體記憶體來決
定。一般不超過實際實體記憶體的1/2。

伺服器記憶體的分配,比如伺服器記憶體64GB,為作業系統預留出8G16GB。此外給Yarn留出8G16GB,如果沒有其
他框架,把剩餘的留給HBase

Hbase調優

1)調節資料塊(data block)的大小
HFile資料塊大小可以在列族層次設定。這個資料塊不同於之前談到的HDFS資料塊,其預設值是65536位元組,或
64KB。資料塊索引儲存每個HFile資料塊的起始鍵。資料塊大小的設定影響資料塊索引的大小。資料塊越小,索引越
大,從而佔用更大記憶體空間。同時載入進記憶體的資料塊越小,隨機查詢效能更好。但是,如果需要更好的序列掃描
效能,那麼一次能夠載入更多HFile資料進入記憶體更為合理,這意味著應該將資料塊設定為更大的值。相應地,索引
變小,將在隨機讀效能上付出更多的代價。
可以在表例項化時設定資料塊大小,程式碼如下:
hbase(main):002:0> create ‘mytable’,{NAME => ‘colfam1’, BLOCKSIZE => ‘65536’}
如果mytable表在實際業務中,隨機查詢業務多,就調小。
如果範圍查詢(順序掃描)業務多,就調大。
2)適當時機關閉資料塊快取
把資料放進讀快取,並不是一定能夠提升效能。
如果一個表或表的列族只被順序化掃描訪問或很少被訪問,
則Get或Scan操作花費時間長一點是可以接受的。在這種情況下,
可以選擇關閉列族的快取。
關閉快取的原因在於:如果只是執行很多順序化掃描,會多次使用快取,
並且可能會濫用快取,從而把應該放進快取獲得性能提升的資料給排擠出去。
所以如果關閉快取,不僅可以避免上述情況發生,而且可以讓出更多快取給其他表和同一表的其他列族使用。資料
塊快取預設是開啟的。

可以在新建表或更改表時關閉資料塊快取屬性:
hbase(main):002:0> create ‘mytable’, {NAME => ‘colfam1’, BLOCKCACHE => ‘false’}
如果預見到mytable的範圍查詢(順序查詢)業務較多,
這種場景可以將mytable的讀快取機制關掉。
如果不關掉,會導致此表大量的範圍資料都會載入到BlockCache裡,
會擠掉其他表有用的隨機查詢資料。
3)開啟布隆過濾器
資料塊索引提供了一個有效的方法getDataBlockIndexReader(),在訪問某個特定的行時用來查詢應該讀取的
HFile的資料塊。但是該方法的作用有限。HFile資料塊的預設大小是64KB,一般情況下不能調整太多。
如果要查詢一個很短的行,只在整個資料塊的起始行鍵上建立索引是無法給出更細粒度的索引資訊的。例如,某行
佔用100位元組儲存空間,一個64KB的資料塊包含(64×1024)/100=655.53,約700行,只能把起始行放在索引位
上。要查詢的行可能落在特定資料塊上的行區間,但也不能肯定存放在那個資料塊上,
這就導致多種可能性:該行在表中不存在,或者存放在另一個HFile中,甚至在MemStore中。這些情況下,從硬碟
讀取資料塊會帶來I/O開銷,也會濫用資料塊快取,這會影響效能,尤其是當面對一個巨大的資料集且有很多併發讀
使用者時。
布隆過濾器(Bloom Filter)允許對儲存在每個資料塊的資料做一個反向測驗。當查詢某行時,先檢查布隆過濾
器,看看該行是否不在這個資料塊。布隆過濾器要麼確定回答該行不在,要麼回答不知道。因此稱之為反向測驗。
布隆過濾器也可以應用到行內的單元格上,當訪問某列識別符號時先使用同樣的反向測驗。
使用布隆過濾器也不是沒有代價,相反,儲存這個額外的索引層次佔用額外的空間。布隆過濾器的佔用空間大小隨
著它們的索引物件資料增長而增長,所以行級布隆過濾器比列識別符號級布隆過濾器佔用空間要少。當空間不是問題
時,它們可以壓榨整個系統的效能潛力。
可以在列族上開啟布隆過濾器,程式碼如下:
hbase(main):007:0> create ‘mytable’, {NAME => ‘colfam1’, BLOOMFILTER => ‘ROWCOL’}
布隆過濾器引數的預設值是NONE。另外,還有兩個值:ROW表示行級布隆過濾器;ROWCOL表示列識別符號級布
隆過濾器。行級布隆過濾器在資料塊中檢查特定行鍵是否不存在,列識別符號級布隆過濾器檢查行和列識別符號聯合體

是否不存在。ROWCOL布隆過濾器的空間開銷高於ROW布隆過濾器。
4)開啟資料壓縮
HFile可以被壓縮並存放在HDFS上,這有助於節省硬碟I/O,此外,可以節省頻寬。
效能瓶頸:cpu,記憶體,磁碟,頻寬
但是讀寫資料時壓縮和解壓縮會擡高CPU利用率。壓縮是表定義的一部分,可以在建表或模式改變時設定。除非確
定壓縮不會提升系統的效能,否則推薦開啟表的壓縮。只有在資料不能被壓縮,或者因為某些原因伺服器的CPU利
用率有限制要求的情況下,有可能需要關閉壓縮特性。
HBase可以使用多種壓縮編碼,包括LZO、SNAPPY和GZIP,LZO和SNAPPY是其中最流行的兩種。
當建表時可以在列族上開啟壓縮,程式碼如下:
hbase(main):002:0>
create ‘mytable’, {NAME => ‘colfam1’, COMPRESSION => ‘SNAPPY’}
注意,資料只在硬碟上是壓縮的,在記憶體中(MemStore或BlockCache)或在網路傳輸時是沒有壓縮的。
5)設定Scan快取
HBase的Scan查詢中可以設定快取,定義一次互動從伺服器端傳輸到客戶端的行數,設定方法是使用Scan類中
setCaching()方法,這樣能有效地減少伺服器端和客戶端的互動,更好地提升掃描查詢的效能。
下面的程式碼展示瞭如何使用setCaching()方法。
6)顯式地指定列
當使用Scan或Get來處理大量的行時,最好確定一下所需要的列。因為伺服器端處理完的結果,需要通過網路傳輸
到客戶端,而且此時,傳輸的資料量成為瓶頸,如果能有效地過濾部分資料,使用更精確的需求,能夠很大程度上
減少網路I/O的花費,否則會造成很大的資源浪費。如果在查詢中指定某列或者某幾列,能夠有效地減少網路傳輸
量,在一定程度上提升查詢效能。下面程式碼是使用Scan類中指定列的addColumn()方法。

7)關閉ResultScanner
ResultScanner類用於儲存服務端掃描的最終結果,可以通過遍歷該類獲取查詢結果。但是,如果不關閉該類,可能
會出現服務端在一段時間內一直儲存連線,資源無法釋放,從而導致伺服器端某些資源的不可用,還有可能引發
RegionServer的其他問題。所以在使用完該類之後,需要執行關閉操作。這一點與JDBC操作MySQL類似,需要關
閉連線。程式碼的最後一行rsScanner.close()就是執行關閉ResultScanner。
8)使用批量讀
通過呼叫HTable.get(Get)方法可以根據一個指定的行鍵獲取HBase表中的一行記錄。同樣HBase提供了另一個方
法,通過呼叫HTable.get(List)方法可以根據一個指定的行鍵列表,批量獲取多行記錄。使用該方法可以
在伺服器端執行完批量查詢後返回結果,降低網路傳輸的速度,節省網路I/O開銷,對於資料實時性要求高且網路傳
輸RTT高的場景,能帶來明顯的效能提升。

9)使用批量寫
通過呼叫HTable.put(Put)方法可以將一個指定的行鍵記錄寫入HBase,同樣HBase提供了另一個方法,通過呼叫
HTable.put(List)方法可以將指定的多個行鍵批量寫入。這樣做的好處是批量執行,減少網路I/O開銷。

10)關閉寫WAL日誌
在預設情況下,為了保證系統的高可用性,寫WAL日誌是開啟狀態。寫WAL開啟或者關閉,在一定程度上確實會對
系統性能產生很大影響,根據HBase內部設計,WAL是規避資料丟失風險的一種補償機制,如果應用可以容忍一定
的資料丟失的風險,可以嘗試在更新資料時,關閉寫WAL。該方法存在的風險是,當RegionServer宕機時,可能寫
入的資料會出現丟失的情況,且無法恢復。關閉寫WAL操作通過Put類中的writeToWAL()設定。

11)設定AutoFlush
HTable有一個屬性是AutoFlush,該屬性用於支援客戶端的批量更新。
該屬性預設值是true,即客戶端每收到一條資料,立刻傳送到服務端。
如果將該屬性設定為false,當客戶端提交Put請求時,將該請求在客戶端快取,
直到資料達到某個閾值的容量時(該容量由引數hbase.client.write.buffer決定)
或執行hbase.flushcommits()時,才向RegionServer提交請求。
這種方式避免了每次跟服務端互動,採用批量提交的方式,所以更高效。
但是,如果還沒有達到該快取而客戶端崩潰,該部分資料將由於未傳送到RegionServer而丟失。這對於有些零容忍
的線上服務是不可接受的。所以,設定該引數的時候要慎重。

12)預建立Region
在HBase中建立表時,該表開始只有一個Region,插入該表的所有資料會儲存在該Region中。隨著資料量不斷增
加,當該Region大小達到一定閾值時,就會發生分裂(Region Splitting)操作。並且在這個表建立後相當長的一
段時間內,針對該表的所有寫操作總是集中在某一臺或者少數幾臺機器上,這不僅僅造成區域性磁碟和網路資源緊
張,同時也是對整個叢集資源的浪費。這個問題在初始化表,即批量匯入原始資料的時候,特別明顯。為了解決這
個問題,可以使用預建立Region的方法。

13)調整ZooKeeper Session的有效時長
引數zookeeper.session.timeout用於定義連線ZooKeeper的Session的有效時長,這個預設值是180秒。這意味著
一旦某個RegionServer宕機,HMaster至少需要180秒才能察覺到宕機,然後開始恢復。或者客戶端讀寫過程中,
如果服務端不能提供服務,客戶端直到180秒後才能覺察到。
在某些場景中,這樣的時長可能對生產線業務來講不能容忍,需要調整這個值。
此引數在HBase-site.xml中,通過<property></property>

上一篇 13.大資料學習之旅——HBase第三天