rowkey设计首先应当遵循三大原则:
rowkey是一个二进制码流,可以为任意字符串,最大长度为64kb,实际应用中一般为10-100bytes,它以byte[]形式保存,一般设定成定长。
一般越短越好,不要超过16个字节,注意原因如下:
1、目前操作系统都是64位系统,内存8字节对齐,控制在16字节,8字节的整数倍利用了操作系统的最佳特性。
2、hbase将部分数据加载到内存当中,如果rowkey过长,内存的有效利用率就会下降。
如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位字节采用散列字段处理,由程序随即生成。低位放时间字段,这样将提高数据均衡分布,各个regionServer负载均衡的几率。
如果不进行散列处理,首字段直接使用时间信息,所有该时段的数据都将集中到一个regionServer当中,这样当检索数据时,负载会集中到个别regionServer上,造成热点问题,会降低查询效率。
必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。但是这里的量不能太大,如果太大需要拆分到多个节点上去。
所以良好的rowkey设计,应当遵循三大原则,并且能让数据分散,从而避免热点问题。本节介绍几种常用的rowkey设计方法,以供同学们学习。
注意:本节理论知识较多,不过都是大数据岗位面试中常见问题,希望同学们认真研读。
这里所说的加盐并非密码学中的加盐,而是在rowkey的前面分配随机数,当给rowkey随机前缀后,它就能分布到不同的region中,这里的前缀应该和你想要数据分散的不同的region的数量有关。
为了让同学们更好的理解加盐(salting)这个rowkey设计方法。我们以电信公司为例。当我们去电信公司打印电话详单也就是通话记录。对于通话记录来说,每个人每月可能都有很多通话记录,而使用电信的用户也是亿计。这种信息,我们就能存入hbase当中。
对于通话记录,我们有什么信息需要保存呢?首先,肯定应该有主叫和被叫,然后有主叫被叫之间的通话时长,以及通话时间。除此之外,还应该有主叫的位置信息,和被叫的位置信息。
由此,我们的通话记录表需要记录的信息就出来了:主叫、被叫、时长、时间、主叫位置、被叫位置。
我们该如何来设计一张hbase表呢?
首先,hbase表是依靠rowkey来定位的,我们应该将尽可能多的将查询的信息编入rowkey当中。hbase的元数据表mate表就给我们了一个很好的示例。它包括了namespace,表名,startKey,时间戳,计算出来的码(用于分散数据)。
所以,当我们设计通话记录的rowkey时,需要将能唯一确定该条记录的数据编入rowkey当中。即是需要将主叫、被叫、时间编入。
如下所示:
17765657979 18688887777 201806121502 #主叫,被叫,时间
但是我们能否将我们设计的rowkey真正应用呢?
当然是可以的,但是热点问题便会随之而来。
例如你的电话是以177开头,电信的hbase集群有500台,你的数据就只可能被存入一台或者两台机器的region当中,当你需要打印自己的通话记录时,就只有一台机器为你服务。而若是你的数据均匀分散到500机器中,就是整个集群为你服务。两者之间效率速度差了不止一个数量级。
注意:由于我们的regionServer就只有一台,没有集群环境,所以我们只介绍方法和理论操作,不提供实际结果
因为我们设定整个hbase集群有500台,所以我们随机在0-499之间中随机数字,添加到rowkey首部。
如下所示:
12 17765657979 18688887777 201806121502 #随机数,主叫,被叫,时间
在插入数据时,判断首部随机数字,选择对应的region存入,由于rowkey首部数字随机,所以数据也将随机分布到不同的regionServer中。这样就能很好的避免热点问题了。
通常hbase会自动处理region拆分,当region的大小到达一定阈值后,region将被拆分成两个,之后在两个region都能继续增长数据。
然而在这个过程当中,会出现两个问题:
第一点,就是我们所说的热点问题,数据会继续往一个region中写,出现写热点问题;
第二点,则是拆分合并风暴
,当用户的region大小以恒定的速度增长,region的拆分会在同一时间发生,因为同时需要压缩
region中的存储文件,这个过程会重写拆分后的region,这将会引起磁盘I/O上升 。
压缩:hbase支持大量的压缩算法,而且通常开启压缩,因为cpu压缩和解压的时间比从磁盘读写数据的时间消耗的更短,所以压缩会带来性能的提升。
对于拆分合并风暴,通常我们需要关闭hbase的自动管理拆分。然后手动调用hbase的split(拆分)和major_compact(压缩),对其进行时间控制,来分散I/O负载。但是其中的split操作同样是高I/O的操作。
为了解决这些问题,预分区就是一种很好的方法,通常它和加盐
结合起来使用。
所谓预分区,就是预先创建hbase表分区。这需要我们明确rowkey的取值范围和构成逻辑。
比如前面我们所列举的电信电话详单表。通过加盐我们得到的 rowkey构成是:随机数+主叫+被叫+时间
,如果我们现在并没有500台机器,只有10台,但是按照我们的计划,未来将扩展到500台的规模。所以我们仍然设计0到499的随机数,但是将以主叫177开头的通话记录分配到十个region当中,所以我们将随机数均分成十个区域,范围如下:
-50,50-100,100-150,150-200,200-250,250-300,300-350,350-400,400-450,450-
然后我们将我们的预分区存入数组当中,当插入数据时,先根据插入数据的首部随机数,判断分区位置,再进行插入数据。同样,这样也能使得各台节点负载均衡。
细心的同学可能会发现,在我们刚刚提出的加盐
与预分区
rowkey设计方法中,并没有完整运用到rowkey设计的散列原则。
更一步思考下,我们会发现如果只运用加盐
与预分区
rowkey设计方法,数据会真正无序随即分布在hbase集群当中,这并没有让我们利用到hbase根据字典顺序排序的这一特点。
由此,哈希这一设计理念便顺理成章的出现在我们眼前。
同样以电信通话记录为例,我们想将某一天的通话记录存入同一region当中,所以我们利用哈希函数算出哈希值,再模以我们需要存入region数量,我们就能将相同输入的数据,存入同一region当中。
在主叫,被叫,时间
rowkey当中,我们将callerID(主叫)与 20180612(某一天的时间)作为参赛,传入哈希函数当中,将得到的哈希值模以500,余数添加到rowkey首部中,再结合预分区
设计方法,就能将数据均匀分布到regionServer当中。
同时,我们还能将相同rowkey的数据收集到一台节点上,在避免热点问题的情况下,充分利用hbase字典排序的优点。
对于以手机号码这样比较固定开头的rowkey(例如开头177,159,138),但是它的后几位都是随机的,没有规律的。我们可以将手机号反转之后作为rowkey,这样就避免了热点问题。
这就是rowkey设计的另一种方法反转
,通过反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。