熱點發生在大量的client直接訪問集群的一個或極少數個節點(訪問可能是讀,寫或者其他操作)。大量訪問會使熱點region所在的單個機器超出自身承受能力,引起性能下降甚至region不可用,這也會影響同一個RegionServer上的其他region,由於主機無法服務其他region的請求,造成資源浪費。設計良好的數據訪問模式以使集群被充分,均衡的利用。
數據傾斜:Hbase可以被划分為多個Region,但是默認創建時只有一個Region分布在集群的一個節點上,數據一開始時都集中在這個Region,也就是集中在這一個節點上,就算region存儲達到臨界值時被划分,數據也是存儲在少數節點上。這就是數據傾斜
隨機散列與預分區二者結合起來,是比較完美的。預分區一開始就預建好了一部分region,這些region都維護着自己的start-end keys,在配合上隨機散列,寫數據能均衡的命中這些預建的region,就能解決上面的那些缺點,大大提供性能。
1. 預分區
1.1 HBase的預分區概述
默認分區:
HBase表被創建時,只有1個Region,當一個Region過大達到默認的閥值時(默認10GB大小),HBase中該Region將會進行split,分裂為2個Region,以此類推。
缺點:
表在進行split的時候,會耗費大量的資源,頻繁的分區對HBase的性能有巨大的影響。所以,HBase提供了預分區功能,即用戶可以在創建表的時候對表按照一定的規則分區。
2. HBase預分區的作用
避免HBase經常split,產生不必要的資源消耗,提高HBase的性能
3. HBase預分區的方法
- HBase Shell
create 'user1',{NAME=>'f'},{NAME=>'d'},SPLITS=>['0|','1|','3|','4|']
create 'user1', 'f', SPLITS => ['1|', '2|', '3|', '4|']
- HBase Shell(通過讀取split文件)
create 'user2',{NAME=>'f'},{NAME=>'d'},SPLITS_FILE=>'/data/hbaseSplit.txt'
hbaseSplit.txt內容
> cat hbaseSplit.txt 1| 2| 3| 4|
- HBase Java API
object HbaseUtil { def main(args: Array[String]): Unit = { val conf = HBaseConfiguration.create()
conf.set("hbase.zookeeper.quorum","192.168.1.11,192.168.1.12,192.168.1.13") conf.set("hbase.zookeeper.property.clientPort", "2181") conf.set("zookeeper.znode.parent", "/hbase") conf.set("hbase.master", "192.168.1.11:16010") val connection = ConnectionFactory.createConnection(conf) val admin = connection.getAdmin val colFamily = List("info", "desc") val tableName = "user3" val splitKeys = Array( Bytes.toBytes("0|"), Bytes.toBytes("1|"), Bytes.toBytes("2|"), Bytes.toBytes("3|"), Bytes.toBytes("4|") ) if (admin.tableExists(TableName.valueOf(tableName))) { println("表已存在!") } else { val descriptor = new HTableDescriptor(TableName.valueOf(tableName)) colFamily.foreach(x => descriptor.addFamily(new HColumnDescriptor(x))) admin.createTable(descriptor, splitKeys) } admin.close() connection.close() } }
2.隨機散列
1. 固定散列值
- HBase Shell
create 'user1',{NAME=>'f'},{NAME=>'d'},SPLITS=>['0|','1|','3|','4|','5|','6|','7|','8|','9|']
說明:固定值散列,后面添加"|",因為|編碼值最大
2. 哈希(散列)
Hbase自帶了兩種pre-split的算法,分別是 HexStringSplit 和 UniformSplit 。
- HexStringSplit
如果我們的row key是十六進制的字符串作為前綴的,稱之為HexStringSplit就比較適合用HexStringSplit,作為pre-split的算法。例如,我們使用HexHash(prefix)作為row key的前綴,其中Hexhash為最終得到十六進制字符串的hash算法,我們通常手動指定SPLITS來指定預分區,我們也可以用我們自己的split算法。
create 'test',{NAME=>'f',COMPRESSION=>'SNAPPY'},{NUMREGIONS => 30, SPLITALGO => 'HexStringSplit'}
put時可以使用
MD5Hash.getMD5AsHex(Bytes.toBytes(str));
-
UniformSplit
如果我們的row key使用byte來作為前綴,稱之為UniformSplit,如果某個hbase的表查詢只是以隨機查詢為主,可以用UniformSplit的方式進行,它是按照原始byte值(從0x00~0xFF)右邊以00填充。以這種方式分區的表在插入的時候需要對rowkey進行一個技巧性的改造, 比如原來的rowkey為rawStr,則需要對其取hashCode,然后進行按照比特位反轉后放在最初rowkey串的前面。可以充分利用Bytes這個工具類來做。
create 'test', { NAME => 'f', TTL => 5184000, DATA_BLOCK_ENCODING => 'PREFIX' }, {NUMREGIONS => 128, SPLITALGO => 'UniformSplit'}
使用SPLITALGO => 'UniformSplit'方式來建表是沒有指定startKey和endKey的,也就是說采用這種方式建表就是基於ancil的256個值的范圍來平均切分10個預分區的(由於anscil一共256個值,因此采用這種方式做預分區建表最多支持256個預分區,不過在寫入數據后,256預分區可以再內部做二次切分),采用這種做法導致在Scan查詢的時候就需要開256個Scan線程取掃描數據並返回最終的結果,好處就是統一了整個rowkey的范圍,取名UniformSplit大概也是這個意思。
put數據時,可以充分利用Bytes這個工具類
byte[] rowKey = Bytes.add(Bytes.toBytes(Integer.reverse(Integer.valueOf(Integer.valueOf(i).hashCode()))), Bytes.toBytes(i));
3.強制split
HBase 允許客戶端強制執行split,在hbase shell中執行以下命令:
split 'forced_table', 'b'
其中:forced_table 為要split的table , ‘b’ 為split 點