转载:https://blog.csdn.net/zhangvalue/article/details/100636697
什么是rowkey?
rowkey是HBase实现分布式的基础,HBase通过rowkey范围划分不同的region,分布式系统的基本要求就是在任何时候,系统的访问都不要出现明显的热点现象,所以rowkey的设计至关重要,一般我们建议rowkey的开始部分以hash或者MD5进行散列,尽量做到rowkey的头部是均匀分布的。禁止采用时间、用户id等明显有分段现象的标志直接当作rowkey来使用。
对于关系型数据库,数据定位可以理解为“二维坐标”;但是hbase中需要四维来定位一个单元格,即[行健、列族、列限定符、时间戳]
而 HBase中rowkey可以唯一标识一行记录,在HBase查询的时候,有以下几种方式:
通过get方式,指定rowkey获取唯一一条记录
通过scan方式,设置startRow和stopRow参数进行范围匹配
全表扫描,即直接扫描整张表中所有行记录
什么是热点?
HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于scan。然而糟糕的rowkey设计是热点的源头。 热点发生在大量的client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。大量访问会使热点region所在的单个机器超出自身承受能力,引起性能下降甚至region不可用,这也会影响同一个RegionServer上的其他region,由于主机无法服务其他region的请求。 设计良好的数据访问模式以使集群被充分,均衡的利用。
RowKey的三大设计原则 :
①、长度原则
②、散列原则
③、时间戳反转
④、唯一原则
1. RowKey长度原则
因为RowKey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100bytes,以 byte[] 形式保存,一般设计成定长建议越短越好,不要超过16个字节,原因如下:
1️⃣、目前操作系统都是64位系统,内存8字节对齐,控制在16字节,8字节的整数倍利用了操作系统的最佳特性。
2️⃣、hbase将部分数据加载到内存当中,如果rowkey过长,内存的有效利用率就会下降。
数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;
MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。
其他的如列族名、列名等属性名也是越短越好。value永远和它的key一起传输的。当具体的值在系统间传输时,它的rowkey,列名,时间戳也会一起传输。如果你的rowkey和列名和值相比较很大,那么你将会遇到一些有趣的问题。Hfile中的索引最终占据了HBase分配的大量内存。
2.RowKey散列原则
如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。具体方式有以下几种
Salting 加盐 散布
在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。散列之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点。
Hashing 哈希
哈希会使同一行永远用一个前缀散列。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据
RowKey Reverse 行健反转
反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。(例如手机号)
反转rowkey的例子以手机号为rowkey,可以将手机号反转后的字符串作为rowkey,这样的就避免了以手机号那样比较固定开头导致热点问题
(举例:写数据时行健是1到1万,这种情况如果不散列,就会出现写热点,总是往存储最大行健的Region里写入数据,十分影响性能。)
3.时间戳反转 Reversiong the Key
rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。
一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为rowkey的一部分对这个问题十分有用,可以用Long.Max_Value-timestamp 追加到key的末尾,例如 [key][reverse_timestamp] , [key] 的最新值可以通过scan [key]获得[key]的第一条记录,因为HBase中rowkey是有序的,第一条记录是最后录入的数据。
(由于这一项是违背散列原则的,有可能引起热点,所以要根据具体情境来看是否适合使用这种方法。大多数情境还是以行健散列为主。)
4. RowKey唯一原则
必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。但是这里的量不能太大,如果太大需要拆分到多个节点上去。
所以良好的rowkey设计,应当遵循四大原则,并且能让数据分散,从而避免热点问题。介绍几种常用的rowkey设计方法。
1.1 加盐
这里所说的加盐并非密码学中的加盐,而是在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中。这样就能很好的避免热点问题了。
1.2 预分区
通常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-
然后我们将我们的预分区存入数组当中,当插入数据时,先根据插入数据的首部随机数,判断分区位置,再进行插入数据。同样,这样也能使得各台节点负载均衡。
1.3 哈希
细心的同学可能会发现,在我们刚刚提出的加盐与预分区rowkey设计方法中,并没有完整运用到rowkey设计的散列原则。
更一步思考下,我们会发现如果只运用加盐与预分区rowkey设计方法,数据会真正无序随即分布在hbase集群当中,这并没有让我们利用到hbase根据字典顺序排序的这一特点。
由此,哈希这一设计理念便顺理成章的出现在我们眼前。
同样以电信通话记录为例,我们想将某一天的通话记录存入同一region当中,所以我们利用哈希函数算出哈希值,再模以我们需要存入region数量,我们就能将相同输入的数据,存入同一region当中。
在主叫,被叫,时间rowkey当中,我们将callerID(主叫)与 20180612(某一天的时间)作为参赛,传入哈希函数当中,将得到的哈希值模以500,余数添加到rowkey首部中,再结合预分区设计方法,就能将数据均匀分布到regionServer当中。
同时,我们还能将相同rowkey的数据收集到一台节点上,在避免热点问题的情况下,充分利用hbase字典排序的优点。
1.4 反转
对于以手机号码这样比较固定开头的rowkey(例如开头177,159,138),但是它的后几位都是随机的,没有规律的。我们可以将手机号反转之后作为rowkey,这样就避免了热点问题。
这就是rowkey设计的另一种方法反转,通过反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。