HBase系列--RowKey的设计原则

1. RowKey是什么?

与nosql数据库们一样,RowKey是用来检索记录的主键。访问HBASE table中的行,只有三种方式:

  • 通过单个RowKey访问(get)
  • 通过RowKey的range(正则)(like)
  • 全表扫描(scan)

RowKey行键 (RowKey)可以是任意字符串(最大长度是64KB,实际应用中长度一般为 10-100bytes),在HBASE内部,RowKey保存为字节数组。存储时,数据按照RowKey的字典序(byte order)排序存储。设计RowKey时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)

2. RowKey在HBase中的作用

在读写流程中:

  • 通过RowKey路由到对应的Region:在HBase中,一个Region就相当于一个数据分片,每个Region都有StartRowKey和StopRowKey(用来表示 Region存储的RowKey的范围),HBase表里面的数据是按照RowKey来分散存储到不同的Region里面的。
  • MemStore中的数据按RowKey排序
  • HFile中的数据按RowKey排序

3. RowKey设计不均会带来什么问题?

热点问题:HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了 scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于 scan读取。在咱们实际生产中,当大量请求访问HBase集群的一个或少数几个节点,造成少数RegionServer的读写请求过多,负载过大,而其他RegionServer负载却很小,这样就造成热点现象,也就是类似于数据倾斜。

4. RowKey与索引设计

遵循的最基本的最基本原则:唯一性。RowKey必须能够唯一的识别一行数据。

4.1 避免热点问题RowKey设计:

4.1.1 反转(Reversing)

反转,顾名思义它就是把固定长度或者数字格式的 rowkey进行反转,反转分为一般数据反转和时间戳反转,其中以时间戳反转较常见。

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

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

  • 举例:
    比如咱们通常会有需要快速获取数据的最近版本的数据处理需求,这时候就需要把时间戳作为RowKey来查询了,但是时间戳正常情况下是这样的:
    在这里插入图片描述
    前面这部分是相同的,在查询的时候就容易造成热点现象,因此需要使用时间戳反转的方式来处理。实际生产中可以用 Long.Max_Value - timestamp 追加到 key 的末尾,比如 key, [key] 的最新值可以通过 scan [key]获得[key]的第一条记录,因为HBase中RowKey是有序的,所以第一条记录是最后录入的数据。

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

4.1.2 加盐(Salting)

原理是在原RowKey的前面添加固定长度的随机数,也就是给RowKey分配一个随机前缀使它和之前的RowKey的开头不同。随机数能保障数据在所有Region间的负载均衡。

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

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

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

4.1.3 哈希(Hashing)

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

  • 缺点:
    与反转类似,哈希也打乱了RowKey的自然顺序,因此也不利于Scan。
  • 适用场景:
    其实哈希和加盐的适用场景类似,但是由于加盐方法的前缀是随机数,用原rowkey查询时不方便,因此出现了哈希方法,由于哈希是使用各种常见的算法来计算出的前缀,因此哈希既可以使负载分散到整个集群,又可以轻松读取数据。

4.2 二级索引RowKey设计

4.2.1 设计模式

4.2.1.1 无Schema模式

下图是常见设计思路,如果原数据RowKey中已经包含了索引列的信息,该设计容易导致数据冗余。
在这里插入图片描述

4.2.1.2有Schema模式

当原数据RowKey中的列与索引列有重叠时,该设计能避免一个列在索引列中被重复存储。但该设计需要事先支持Schema,也就是需要事先定义原数据的RowKey结构以及索引的结构信息。
在这里插入图片描述

4.2.2 二级索引字段的选取

对所有的价值查询场景进行详细分析,基于确实能够缩小查询范围的一部分列来构建二级索引。即我们应该基于离散度较好的一些列来构建索引。如下图所示,字段ID,PHONE的离散度为1,基于这些字段构建索引是最佳的,而字段PROVINCE与GENDER,AGE的离散度较差,不适合用来构建二级索引。
在这里插入图片描述

5. 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,这样能保证新写入的数据在读取时可以被快速找到。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值