1 在創建表的時候指定分區
默認情況下,在創建HBase表的時候會自動創建一個region分區,當導入數據的時候,所有的HBase客戶端都向這一個region寫數據,直到這個region足夠大了才進行切分。一種可以加快批量寫入速度的方法是通過預先創建一些空的regions,這樣當數據寫入HBase時,會按照region分區情況,在集羣內做數據的負載均衡。
- 實現方式是使用admin對象的切分策略
byte[] startKey = ...; // your lowest key
byte[] endKey = ...; // your highest key
int numberOfRegions = ...; // number of regions to create
admin.createTable(table, startKey, endKey, numberOfRegions);
- 用戶自定義切片
byte[][] splits = ...; // create your own splits
/*
byte[][] splits = new byte[][] { Bytes.toBytes("100"),
Bytes.toBytes("200"), Bytes.toBytes("400"),
Bytes.toBytes("500") };
*/
admin.createTable(table, splits);
2 Rowkey設計
HBase中rowkey用來檢索表中的記錄,支持以下三種方式:
1. 通過單個rowkey訪問:即按照某個rowkey鍵值進行get操作;
2. 通過rowkey的range進行scan:即通過設置startRowKey和endRowKey,在這個範圍內進行掃描;
3. 全表掃描:即直接掃描整張表中所有行記錄
在HBase
中,rowkey
可以是任意字符串,最大長度64KB
,實際應用中一般爲10~100
字節,存爲byte[]
字節數組,一般設計成定長的。
rowkey
是按照字典序存儲,因此,設計rowkey
時,要充分利用這個排序特點,將經常一起讀取的數據存儲到一塊,將最近可能會被訪問的數據放在一塊。
Rowkey設計原則:
1. 越短越好,提高效率
* 數據的持久化文件HFile
中是按照KeyValue
存儲的,如果rowkey
過長,比如超過100
字節,1000
萬行數據,單單是存儲rowkey
的數據就要佔用10
億個字節,將近1G
數據,這樣會影響HFile
的存儲效率。
* HBase
中包含緩存機制,每次會將查詢的結果暫時緩存到HBase
的內存中,如果rowkey
字段過長,內存的利用率就會降低,系統不能緩存更多的數據,這樣會降低檢索效率。
2. 散列原則——實現負載均衡
如果rowkey
是按時間戳的方式遞增,不要將時間放在二進制碼的前面,建議將rowkey
的高位作爲散列字段,由程序循環生成,低位放時間字段,這樣將提高數據均衡分佈在每個regionserver
實現負載均衡的機率。如果沒有散列字段,首字段直接是時間信息將產生所有新數據都在一個 regionServer
上堆積的熱點現象,這樣在做數據檢索的時候負載將會集中在個別regionServer
,降低查詢效率。
* 加鹽:添加隨機值
* hash:採用md5散列算法取前4位做前綴
* 反轉:將手機號反轉(儘量將具有隨機性的字符串放在前面,這樣便於Hash
)
3. 唯一原則–字典序排序存儲
必須在設計上保證其唯一性,rowkey
是按照字典順序排序存儲的,因此,設計rowkey
的時候,要充分利用這個排序的特點,將經常讀取的數據存儲到一塊,將最近可能會被訪問的數據放到一塊。
3 列族的設計
不要在一張表裏定義太多的column family
。目前Hbase
並不能很好的處理超過2~3
個column family
的表。因爲某個column family
在flush
的時候,它鄰近的column family
也會因關聯效應被觸發flush
,最終導致系統產生更多的I/O
。
原因:
1. 當開始向hbase
中插入數據的時候,數據會首先寫入到memstore
,而memstore
是一個內存結構,每個列族對應一個memstore
,當包含更多的列族的時候,會導致存在多個memstore
,每一個memstore
在flush
的時候會對應一個hfile
的文件,因此會產生很多的hfile
文件,更加嚴重的是,flush
操作時region
級別,當region
中的某個memstore
被flush
的時候,同一個region
的其他memstore
也會進行flush
操作,當某一張表擁有很多列族的時候,且列族之間的數據分佈不均勻的時候,會產生更多的磁盤文件;
2. 當hbase
表的某個region
過大,會被拆分成兩個,如果我們有多個列族,且這些列族之間的數據量相差懸殊的時候,region
的split
操作會導致原本數據量小的文件被進一步的拆分,而產生更多的小文件;
3. 與Flush
操作一樣,目前HBase
的Compaction
操作也是Region
級別的,過多的列族也會產生不必要的I/O
;
4. HDFS
其實對一個目錄下的文件數有限制的(dfs.namenode.fs-limits.max-directory-items
)。如果我們有N
個列族,M
個Region
,那麼我們持久化到HDFS
至少會產生N*M
個文件,而每個列族對應底層的HFile
文件往往不止一個,我們假設爲K
個,那麼最終表在HDFS
目錄下的文件數將是N*M*K
,這可能會操作HDFS
的限制。
4 in memory
hbase
在LRU
緩存基礎之上採用了分層設計,整個blockcache
分成了三個部分,分別是single
、multi
和inMemory
。
三者區別如下:
single
:如果一個block
第一次被訪問,放在該優先隊列中;
multi
:如果一個block
被多次訪問,則從single
隊列轉移到multi
隊列
inMemory
:優先級最高,常駐cache
,因此一般只有hbase
系統的元數據,如meta
表之類的纔會放到inMemory
隊列中。
5 保存版本數
創建表的時候,可以通過ColumnFamilyDescriptorBuilder.setMaxVersions(int maxVersions)
設置表中數據的最大版本,如果只需要保存最新版本的數據,那麼可以設置setMaxVersions(1)
,保留更多的版本信息會佔用更多的存儲空間。
6 設置最大保存時間
創建表的時候,可以通過ColumnFamilyDescriptorBuilder.setTimeToLive(int timeToLive)
設置表中數據的存儲生命期,過期數據將自動被刪除,例如如果只需要存儲最近兩天的數據,那麼可以設置setTimeToLive(2 * 24 * 60 * 60)
。
7 合併操作
在HBase
中,數據在更新時首先寫入WAL
日誌(HLog
)和內存(MemStore
)中,MemStore
中的數據是排序的,當MemStore
累計到一定閾值(64MB
)時,就會創建一個新的MemStore
,並且將老的MemStore
添加到flush
隊列,由單獨的線程flush
到磁盤上,成爲一個StoreFile
。於此同時, 系統會在zookeeper
中記錄一個redo point
,表示這個時刻之前的變更已經持久化了。
StoreFile
是隻讀的,一旦創建後就不可以再修改。因此Hbase
的更新其實是不斷追加的操作。當一個Store
中的StoreFile
達到一定的閾值後,就會進行一次合併,將對同一個key
的修改合併到一起,形成一個大的StoreFile
,當StoreFile
的大小達到一定閾值後,又會對StoreFile
進行分割,等分爲兩個StoreFile
。
由於對錶的更新是不斷追加的,處理讀請求時,需要訪問Store
中全部的StoreFile
和MemStore
,將它們按照row key
進行合併,由於StoreFile
和MemStore
都是經過排序的,並且StoreFile
帶有內存中索引,通常合併過程還是比較快的。
實際應用中,可以考慮必要時手動進行major compact
,將同一個row key
的修改進行合併形成一個大的StoreFile
。同時,可以將StoreFile
設置大些,減少split
的發生。
hbase
爲了防止小文件(被刷到磁盤的menstore
)過多,以保證保證查詢效率,hbase
需要在必要的時候將這些小的store file
合併成相對較大的store file
,這個過程就稱之爲compaction
。在hbase
中,主要存在兩種類型的compaction
:minor compaction
和major compaction
。
1. minor compaction
: 的是較小、很少文件的合併。minor compaction
的運行機制要複雜一些,它由一下幾個參數共同決定:
hbase.hstore.compaction.min
: 默認值爲3
,表示至少需要三個滿足條件的store file
時,minor compaction
纔會啓動
hbase.hstore.compaction.max
: 默認值爲10
,表示一次minor compaction
中最多選取10
個store file
hbase.hstore.compaction.min.size
: 表示文件大小小於該值的store file
一定會加入到minor compaction
的store file
中
hbase.hstore.compaction.max.size
: 表示文件大小大於該值的store file
一定不會被添加到minor compaction
hbase.hstore.compaction.ratio
: 將StoreFile
按照文件年齡排序,minor compaction
總是從older store file
開始選擇,如果該文件的size
小於後面hbase.hstore.compaction.max
個store file size
之和乘以ratio
的值,那麼該store file
將加入到minor compaction
中。如果滿足minor compaction
條件的文件數量大於hbase.hstore.compaction.min
,纔會啓動。
2. major compaction
的功能是將所有的store file
合併成一個,觸發major compaction
的可能條件有:
major_compact
命令
majorCompact() API
region server
運行
hbase.hregion.majorcompaction
默認爲24
小時
hbase.hregion.majorcompaction.jetter
默認值爲0.2
爲防止region server
在同一時間進行major compaction
。
hbase.hregion.majorcompaction.jetter
參數的作用是:對參數hbase.hregion.majorcompaction
規定的值起到浮動的作用,假如兩個參數都爲默認值24
和0.2
,那麼major compact
最終使用的數值爲19.2~28.8
這個範圍。