Hbase 的rowkey的 设计


Rowkey就是行键,hbase是nosql数据库,即然是数据库,最多就是增删改查。RowKey的字典序(byte order)排序存储,设计rowkey时,要充分利用排序存储的这个特性,将经常一起读取的行存储在一起。
rowkey从字面意思看行键的意思,hbase可以理解为一个nosql数据库,那么日常最多的就是增删改查。其实在增删改查的过程中rowkey就充当了主键的作用,它和众多的nosql数据库一样,可以唯一的标识一行记录。
roweky行键可以是任意字符串,在hbase内部,rowkey保存为字节数组,存储时,数据按照rowkey的字典序排序存储。设计rowkey时,要充分利用排序存储这个特性,将经常一起读取的行存储放到一起。

rowkey的特点小结如下:
rowkey类似于主键,可以唯一标识一行记录。
由于数据按照rowkey的字典序(byte order)排序存储,因此hbase中的数据永远都是有序的。
rowkey可以由用户自定指定,只要保证这个字符串不重复就可以了:
rowkey的查询一共有三种方式:
    1)get 通过指定单个rowkey来获取对应的唯一一条记录。
    2)like:通过rowkey的range来进行匹配。
    4) scan:通过设置startRow和stopRow参数来进行范围匹配(注意:如果不设就是全表扫描)。

 

RowKey作为hbase的核心知识点,RowKey的设计会影响数据在Hbase的分布,还会影响查询效率,RowKey的设计质量决定了Hbase的质量。关系到后期的查询效率。
rowkey的作用:
    要了解rowkey的作用,首先我们需要知识hbase中,一个region就相当于一个数据分片,每个region都有startRowKey和stopRowKey(用来表示region存储的rowkey范围),hbase表里面的数据是按照rowkey来分散存储到不同的region里面的。
    为了避免热点现象,需要将数据记录均衡的分散到不同的Region中去,因此需要rowkey满足这种散列的特点,此外,在数据需要通过rowkey找到对应的region
    memstore和HFile中的数据都是按照rowkey的字典序排序。

    热点现象是怎么产生:
    hbase中的行是按照rowkey的字典排序的,这种设计优化scan操作,可以将相关的行以及会被一起读取的行存取在监控位置,便于scan读取。

四、热点现象
4.1、热点现象怎么产生
我们知道HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了 scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于 scan读取。

然而万事万物都有两面性,在咱们实际生产中,当大量请求访问HBase集群的一个或少数几个节点,造成少数RegionServer的读写请求过多,负载过大,而其他RegionServer负载却很小,这样就造成热点现象(吐槽:其实和数据倾斜类似,还整这么高大上的名字)。

掌握了热点现象的概念,我们就应该知道大量的访问会使热点Region所在的主机负载过大,引起性能下降,甚至导致Region不可用。所以我们在向HBase中插入数据的时候,应优化RowKey的设计,使数据被写入集群的多个region,而不是一个。尽量均衡地把记录分散到不同的Region中去,平衡每个Region的压力。

其实RowKey的优化主要就是在解决怎么避免热点现象。那么有哪些避免热点现象的方法呢?各有什么缺点?带着问题,接着往下看。

4.2、如何避免热点现象(RowKey的优化)
在日常使用中,主要有3个方法来避免热点现象,分别是反转,加盐和哈希。听起来很奇怪,下面咱们逐个举例详细分析:

4.2.1、反转(Reversing)
第一种咱们要分析的方法是反转,顾名思义它就是把固定长度或者数字格式的 rowkey进行反转,反转分为一般数据反转和时间戳反转,其中以时间戳反转较常见。

适用场景:

比如咱们初步设计出的RowKey在数据分布上不均匀,但RowKey尾部的数据却呈现出了良好的随机性(注意:随机性强代表经常改变,没意义,但分布较好),此时,可以考虑将RowKey的信息翻转,或者直接将尾部的bytes提前到RowKey的开头。反转可以有效的使RowKey随机分布,但是反转后有序性肯定就得不到保障了,因此它牺牲了RowKey的有序性。

缺点:

利于Get操作,但不利于Scan操作,因为数据在原RowKey上的自然顺序已经被打乱。

举例:

比如咱们通常会有需要快速获取数据的最近版本的数据处理需求,这时候就需要把时间戳作为RowKey来查询了,但是时间戳正常情况下是这样的:

1588610367373
1588610367396
1
2
前面这部分是相同的,在查询的时候就容易造成热点现象,因此需要使用时间戳反转的方式来处理。实际生产中可以用 Long.Max_Value - timestamp 追加到 key 的末尾,比如 [key][reverse_timestamp], [key] 的最新值可以通过 scan [key]获得[key]的第一条记录,因为HBase中RowKey是有序的,所以第一条记录是最后录入的数据。

常见的场景,比如需要保存一个用户的操作记录,就可以按照操作时间倒序排序,在设计rowkey的时候,可以这样设计 [反转后的userId][Long.Max_Value - timestamp],在查询用户的所有操作记录数据的时候,直接指定反转后的userId,startRow 是 [反转后的userId][000000000000],stopRow 是 [反转后的userId][Long.Max_Value - timestamp]。如果需要查询某段时间的操作记录,startRow 是[反转后的userId[Long.Max_Value - 起始时间], stopRow 是[反转后的userId][Long.Max_Value - 结束时间]。

4.2.2、加盐(Salting)
第二种咱们要介绍的方法是加盐,玩过密码学的可能知道密码学里也有加盐的方法,但是咱们RowKey的加盐和密码学不一样,它的原理是在原RowKey的前面添加固定长度的随机数,也就是给RowKey分配一个随机前缀使它和之前的RowKey的开头不同。

适用场景:

比如咱们设计的RowKey是有意义的,但是数据类似,随机性比较低,反转也没法保证随机性,这样就没法根据RowKey分配到不同的Region里,这时候就可以使用加盐的方式了。

需要注意随机数要能保障数据在所有Regions间的负载均衡,也就是说分配的随机前缀的种类数量应该和你想把数据分散到的那些region的数量一致。只有这样,加盐之后的rowkey才会根据随机生成的前缀分散到各个region中,避免了热点现象。

缺点:

大白话来理解就是加了盐就尝不到原有的味道了。因为添加的是随机数,添加后如果还基于原RowKey查询,就无法知道随机数是什么,那样在查询的时候就需要去各个可能的Region中查找,同时加盐对于读取是利空的。并且加盐这种方式增加了读写时的吞吐量。

4.2.3、哈希(Hashing)
最后介绍大家最熟悉的哈希方法,不管是学的啥技术,都会涉及到哈希,也都大同小异,比较简单。

这里的哈希是基于RowKey的完整或部分数据进行Hash,而后将哈希后的值完整替换或部分替换原RowKey的前缀部分。这里说的hash常用的有MD5、sha1、sha256 或 sha512 等算法。

适用场景:

其实哈希和加盐的适用场景类似,但是由于加盐方法的前缀是随机数,用原rowkey查询时不方便,因此出现了哈希方法,由于哈希是使用各种常见的算法来计算出的前缀,因此哈希既可以使负载分散到整个集群,又可以轻松读取数据。

缺点:

与反转类似,哈希也打乱了RowKey的自然顺序,因此也不利于Scan。

五、RowKey设计原则
通过前面的分析我们应该知道了HBase中RowKey设计的重要性了,为了帮助我们设计出完美的RowKey,HBase提出了RowKey的设计原则,一共有四点:长度原则、唯一原则、排序原则,散列原则。

RowKey在字段的选择上,需要遵循的最基本原则是唯一原则,因为RowKey必须能够唯一的识别一行数据。无论应用的负载特点是什么样,RowKey字段都应该首先考虑最高频的查询场景。数据库通常都是以如何高效的读取和消费数据为目的,而不仅仅是数据存储本身。然后再结合具体的负载特点,再对选取的RowKey字段值进行改造,结合RowKey的优化,也就是避免热点现象的那些方法来优化就可以了。

5.1、长度原则
RowKey本质上是一个二进制码的流,可以是任意字符串,最大长度为64kb,实际应用中一般为10-100byte,以byte[]数组形式保存,一般设计成定长。官方建议越短越好,不要超过16个字节,原因可以概括为如下几点:

**影响HFile的存储效率:**HBase里的数据在持久化文件HFile中其实是按照Key-Value对形式存储的。这时候如果RowKey很长,比如达到了200byte,那么仅仅1000w行的记录,只考虑RowKey就需占用近2GB的空间,极大的影响了HFile的存储效率。
**降低检索效率:**由于MemStore会缓存部分数据到内存中,如果RowKey比较长,就会导致内存的有效利用率降低,也就不能缓存更多的数据,从而降低检索效率。
**16字节是64位操作系统的最佳选择:**64位系统,内存8字节对齐,控制在16字节,8字节的整数倍利用了操作系统的最佳特性。
5.2、唯一原则
其实唯一原则咱们可以结合HashMap的源码设计或者主键的概念来理解,由于RowKey用来唯一标识一行记录,所以必须在设计上保证RowKey的唯一性。

需要注意:由于HBase中数据存储的格式是Key-Value对格式,所以如果向HBase中同一张表插入相同RowKey的数据,则原先存在的数据会被新的数据给覆盖掉(和HashMap效果相同)。

5.3、排序原则
HBase会把RowKey按照ASCII进行自然有序排序,所以反过来我们在设计RowKey的时候可以根据这个特点来设计完美的RowKey,好好的利用这个特性就是排序原则。

5.4、散列原则
散列原则用大白话来讲就是咱们设计出的RowKey需要能够均匀的分布到各个RegionServer上。

比如设计RowKey的时候,当Rowkey 是按时间戳的方式递增,就不要将时间放在二进制码的前面,可以将 Rowkey 的高位作为散列字段,由程序循环生成,可以在低位放时间字段,这样就可以提高数据均衡分布在每个Regionserver实现负载均衡的几率。

结合前面分析的热点现象的起因,思考:

如果没有散列字段,首字段只有时间信息,那就会出现所有新数据都在一个 RegionServer上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别RegionServer上,不分散,就会降低查询效率。

HBase里的RowKey是按照字典序存储,因此在设计RowKey时,咱们要充分利用这个排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。如果最近写入HBase表中的数据是最可能被访问的,可以考虑将时间戳作为row key的一部分,由于是字典序排序,所以可以使用Long.MAX_VALUE - timestamp作为row key,这样能保证新写入的数据在读取时可以被快速找到。

六、总结
看到这里RowKey的各个方面应该都已经搞懂了,本文从RowKey的原理,可能出现的问题,如何优化及各个优化措施对应的缺点和适用的场景,设计原则等角度对RowKey进行了详细全面的解析,相信一定能对你有所帮助。
 

你可能感兴趣的:(Hbase 的rowkey的 设计)