HBase 热点问题——rowkey散列和预分区设计

热点发生在大量的client直接访问集群的一个或极少数个节点(访问多是读,写或者其余操做)。大量访问会使热点region所在的单个机器超出自身承受能力,引发性能降低甚至region不可用,这也会影响同一个RegionServer上的其余region,因为主机没法服务其余region的请求,形成资源浪费。设计良好的数据访问模式以使集群被充分,均衡的利用。 
数据倾斜:Hbase能够被划分为多个Region,可是默认建立时只有一个Region分布在集群的一个节点上,数据一开始时都集中在这个Region,也就是集中在这一个节点上,就算region存储达到临界值时被划分,数据也是存储在少数节点上。这就是数据倾斜node

随机散列与预分区两者结合起来,是比较完美的。预分区一开始就预建好了一部分region,这些region都维护着本身的start-end keys,在配合上随机散列,写数据能均衡的命中这些预建的region,就能解决上面的那些缺点,大大提供性能。算法

1. 预分区

1.1 HBase的预分区概述

默认分区:shell

HBase表被建立时,只有1个Region,当一个Region过大达到默认的阀值时(默认10GB大小),HBase中该Region将会进行split,分裂为2个Region,以此类推。工具

缺点:性能

表在进行split的时候,会耗费大量的资源,频繁的分区对HBase的性能有巨大的影响。因此,HBase提供了预分区功能,即用户能够在建立表的时候对表按照必定的规则分区。编码

2. HBase预分区的做用

避免HBase常常split,产生没必要要的资源消耗,提升HBase的性能spa

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. 哈希(散列)code

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 点

相关文章
相关标签/搜索