【HBase教程】(十三)HBase rowkey的设计和预分区
热点问题
我们知道,检索hbase的记录首先要通过row key来定位数据行。有三种方式:
- 通过get方式,指定rowkey获取唯一一条记录
- 通过scan方式,设置起始行和结束行参数进行范围匹配
- 全表扫描,即直接扫描整张表中所有行记录
当大量的client访问hbase集群的一个或少数几个节点,造成少数region server的读/写请求过多、负载过大,而其他region server负载却很小,就造成了“热点”现象。大量访问会使热点region所在的单个主机负载过大,引起性能下降甚至region不可用。
即热点产生原因:有大量连续编号的row key ==> 大量row key相近的记录集中在个别region ==>
client检索记录时,对个别region访问过多 ==> 此region所在的主机过载 ==> 热点问题
既然是因为rowkey访问过多,负载过重而导致,那么我们的切入点就是:尽量均衡地把每一条记录分散到不同的region里去!
RowKey的设计
HBase出现热点问题的主要原因就是rowkey设计的不合理,举个不合理的设计案例:用时间戳生成rowkey,由于时间戳在一段时间内都是连续的,导致在不同的时间段,访问都集中在不同的RegionServer上,比如我要查询1:00的数据,全部集中在了RegionServer1上,我要查询2:00的数据,又全部集中在了RegionServer2上。从而造成热点问题。
所以我们应该合理制定rowkey规则,使rowkey数据随机均匀分布。从以下几个方面设计RowKey:
rowkey散列原则
散列原则非常重要,目的就是让数据均匀分配到每个RegionServer上。
- 加盐
这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。给多少个前缀?这个数量应该和我们想要分散数据到不同的region的数量一致(类似hive里面的分桶)。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点。 - 哈希
尽量要以hash等开头来组织rowkey。因为哈希可以使负载分散到整个集群。hash就是rowkey前面由一串随机字符串组成,随机字符串生成方式可以由SHA或者MD5方式生成,只要region所管理的start-end keys范围比较随机,那么就可以解决写热点问题。例如:
long currentId = 1L;
byte [] rowkey = Bytes.add(MD5Hash.getMD5AsHex(Bytes.toBytes(currentId))
.substring(0, 8).getBytes(),Bytes.toBytes(currentId));
- 反转
第三种防止热点的方法是反转固定长度或者数字格式的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唯一原则
必须在设计上保证其唯一性,rowkey是按照二进制字节数组排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。所以设计rwo key时尽量把体现业务特征的信息、业务上有唯一性的信息编进row key。
rowkey长度原则
rowkey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100byte,以byte[] 形式保存,一般设计成定长。建议越短越好,不要超过16个字节,2个原因:
- 数据的持久化文件HFile中是按照(Key,Value)存储的,如果rowkey过长,例如超过100byte,那么1000万行的记录计算,仅row key就需占用近1Gb空间。这样会极大影响HFile的存储效率!
- MemStore将缓存部分数据到内存,若 rowkey字段过长,内存的有效利用率就会降低,就不能缓存更多的数据,从而降低检索效率。
预分区
我们知道,HBASE在创建表的时候,会自动为表分配一个Region。
但是实际生产环境中,创建新表后,我们往往是需要往表里导入大量的数据。
这样大量的操作就同时集中在了一个RegionServer上,也有了热点问题。而且我们知道,Region达到一定阈值会进行Split,原始只有一个Region,却导入那么多的数据,这样就会导致大量的Split操作,RegionServer可能会出问题。
如何解决这个问题呢?那就是创建表的时候我们就多创建一些Region。这就是预分区。
假设我们初始给它10个Region,那么导入大量数据的时候,就会均衡到10个里面,显然比1个Region要好很多。
可是我们应该创建多少个Region呢?显然没有具体答案,要结合业务,根据表的rowkey进行设计。
创建表时设置预分区
通过SPLITS 或者 SPLITS_FILE 可以在创建表的时候指定分区。
hbase(main):005:0> create 'test','info',SPLITS => ['20180201000','20180401000','20180601000']
=> Hbase::Table - test
//如果分区很多,写在命令行就不合适了,那么就写在文件中。
[root@master ~]# cat /home/split.txt
20180201000
20180401000
20180601000
hbase(main):007:0> create 'test1','info',SPLITS_FILE => '/home/split.txt'
=> Hbase::Table - test1
通过webUI查看我们对应的region信息
我们发现,有4个region,可我们不是创建时只写了3个?没有问题,就是4个。指定x个,最终会有x+1个,第一个没有startkey,最后一个没有endkey。
如何最好的设置预分区
知道了如何设置预分区之后,我们就要思考如何最好的最合理的设置预分区。我们知道,Region的划分是依赖rowkey的,由[Startkey,Endkey)来确定Region。那么我们肯定要先对rowkey进行最优设计。
设计好了rowkey之后,就要想明白数据的key是如何分布的,然后根据数据量、集群规模等因素,规划一下要分成多少region,每个region的startkey和endkey是多少,然后将规划的key写到一个文件中。
总结:合理的Rowkey设计(随机散列)与预分区二者结合起来,是比较完美的。预分区一开始就预建好了一部分region,这些region都维护着自己的start-end keys,在配合上随机散列,写数据能均衡的命中这些预建的region,就能解决上面的那些缺点,大大提供性能。
BDStar原创文章。发布者:Liuyanling,转载请注明出处:http://bigdata-star.com/archives/1256