返回

HBase Rowkey的设计和优化实现

发布时间:2023-11-19 18:05:01 87


大家都知道 HBase 由于它存储和读写的高性能,在 OLAP 即时分析中发挥着非常重要的作用,而 RowKey 作为 HBase 的核心知识点,其设计势必会影响到数据在 HBase 中的分布,甚至会影响我们查询的效率,可以说 RowKey 的设计质量关乎了 HBase 的质量。

Rowkey设计原则

Rowkey设计应遵循以下原则:
1.Rowkey的唯一原则
必须在设计上保证其唯一性。由于在HBase中数据存储是Key-Value形式,若HBase中同一表插入相同Rowkey,则原先的数据会被覆盖掉(如果表的version设置为1的话),所以务必保证Rowkey的唯一性
2. Rowkey的排序原则
HBase的Rowkey是按照ASCII有序设计的,我们在设计Rowkey时要充分利用这点。比如视频网站上对影片《泰坦尼克号》的弹幕信息,这个弹幕是按照时间倒排序展示视频里,这个时候我们设计的Rowkey要和时间顺序相关。可以使用"Long.MAX_VALUE - 弹幕发表时间"的 long 值作为 Rowkey 的前缀
3. Rowkey的散列原则
我们设计的Rowkey应均匀的分布在各个HBase节点上。拿常见的时间戳举例,假如Rowkey是按系统时间戳的方式递增,Rowkey的第一部分如果是时间戳信息的话将造成所有新数据都在一个RegionServer上堆积的热点现象,也就是通常说的Region热点问题, 热点发生在大量的client直接访问集中在个别RegionServer上(访问可能是读,写或者其他操作),导致单个RegionServer机器自身负载过高,引起性能下降甚至Region不可用,常见的是发生jvm full gc或者显示region too busy异常情况,当然这也会影响同一个RegionServer上的其他Region。

言归正传,对于关系型数据库,数据定位可以理解为“二维坐标”;但在 HBase 中,定位一条数据(即一个Cell)我们需要4个维度的限定:行键(RowKey)、列族(Column Family)、列限定符(Column Qualifier)、时间戳(Timestamp)。
RowKey 概念
HBase 中 RowKey 可以唯一标识一行记录,在 HBase 查询的时候有以下几种方式:
通过 get 方式,指定 RowKey 获取唯一一条记录;
通过 scan 方式,设置 startRow 和 stopRow 参数进行范围匹配;
全表扫描,即直接扫描整张表中所有行记录。

从字面意思来看,RowKey 就是行键的意思,在增删改查的过程中充当了主键的作用。它可以是任意字符串,在 HBase 内部 RowKey 保存为字节数组。

HBase 中的数据是按照 Rowkey 的 ASCII 字典顺序进行全局排序的,有伙伴可能对 ASCII 字典序印象不够深刻,下面举例说明:

假如有5个Rowkey:“012”, “0”, “123”, “234”, “3”,按ASCII字典排序后的结果为:“0”, “012”, “123”, “234”, “3”。(注:文末附常用ASCII码表)

Rowkey排序时会先比对两个Rowkey的第一个字节,如果相同,然后会比对第二个字节,依次类推… 对比到第X个字节时,已经超出了其中一个Rowkey的长度,短的Rowkey排在前面。

由于HBase是通过Rowkey查询的,一般Rowkey上都会存一些比较关键的检索信息,我们需要提前想好数据具体需要如何查询,根据查询方式进行数据存储格式的设计,要避免做全表扫描,因为效率特别低。

因此我们设计RowKey时,需要充分利用排序存储这个特性,将经常一起读取的行存储放到一起,要避免做全表扫描,因为效率特别低。 (适用于经常一起读取的数据的场景)

什么是数据热点

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

然而糟糕的 Rowkey 设计是热点的源头。热点发生在大量的 client 直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。

大量访问会使热点 region 所在的单个机器超出自身承受能力,引起性能下降甚至 region 不可用,这也会影响同一个 RegionServer 上的其他 region,由于主机无法服务其他 region 的请求,这样就造成 数据热点现象(这一点其实和数据倾斜类似)。

所以我们在向 HBase 中插入数据的时候,应优化 RowKey 的设计,使数据被写入集群的多个 Region,而不是一个。尽量均衡地把记录分散到不同的 Region 中去,平衡每个 Region 的压力。

2、避免数据热点的方法
在日常使用中,主要有 3 个方法来避免热点现象,分别是反转、加盐和哈希,下面咱们逐个举例分析:
(1)反转(Reversing)
第一种要分析的方法是反转,顾名思义,它就是把固定长度或者数字格式的 RowKey 进行反转,反转分为一般数据反转和时间戳反转,其中以时间戳反转较常见:

反转固定格式的数值以手机号为例,手机号的前缀变化比较少(如 152、185 等),但后半部分变化很多。如果将它反转过来,可以有效地避免热点。不过其缺点就是失去了有序性。

反转时间这个操作严格来讲不算“打散”,但可以调整数据的时间排序。如果将时间按照字典序排列,最近产生的数据会排在旧数据后面。如果用一个大值减去时间(比如用 99999999 减去 yyyyMMdd,或者 Long.MAX_VALUE 减去时间戳),最新的数据就可以排在前面了。

(2)加盐(Salting)
这里的“加盐”与密码学中的“加盐”不是一回事。它是指在 RowKey 的前面增加一些前缀,加盐的前缀种类越多,RowKey 就被打得越散。

需要注意的是,分配的随机前缀的种类数量应该和我们想把数据分散到的那些 region 的数量一致。只有这样,加盐之后的 Rowkey 才会根据随机生成的前缀分散到各个 region 中,避免了热点现象。

Salt是将每一个Rowkey加一个前缀,前缀使用一些随机字符,使得数据分散在多个不同的Region,达到Region负载均衡的目标。

比如在一个有4个Region(注:以 [ ,a)、[a,b)、[b,c)、[c, )为Region起至)的HBase表中,

加Salt前的Rowkey:abc001、abc002、abc003

我们分别加上a、b、c前缀,加Salt后Rowkey为:a-abc001、b-abc002、c-abc003

可以看到,加盐前的Rowkey默认会在第2个region中,加盐后的Rowkey数据会分布在3个region中,理论上处理后的吞吐量应是之前的3倍。由于前缀是随机的,读这些数据时需要耗费更多的时间,所以Salt增加了写操作的吞吐量,不过缺点是同时增加了读操作的开销。

(3)哈希(Hashing)
其实哈希和加盐的适用场景类似,但我们前缀不可以是随机的,因为必须要让客户端能够完整地重构 RowKey。所以一般会拿原 RowKey 或其一部分计算 Hash 值,然后再对 Hash 值做运算作为前缀。

用Hash散列来替代随机Salt前缀的好处是能让一个给定的行有相同的前缀,这在分散了Region负载的同时,使读操作也能够推断。确定性Hash(比如md5后取前4位做前缀)能让客户端重建完整的RowKey,可以使用get操作直接get想要的行。

例如将上述的原始Rowkey经过hash处理,此处我们采用md5散列算法取前4位做前缀,结果如下

9bf0-abc001 (abc001在md5后是9bf049097142c168c38a94c626eddf3d,取前4位是9bf0)

7006-abc002

95e6-abc003

若以前4个字符作为不同分区的起止,上面几个Rowkey数据会分布在3个region中。实际应用场景是当数据量越来越大的时候,这种设计会使得分区之间更加均衡。

如果Rowkey是数字类型的,也可以考虑Mod方法

或者这样理解:
加盐(salting)+哈希(hashing)
这里的“加盐”与密码学中的“加盐”不是一回事。它是指在RowKey的前面增加一些前缀。加盐的前缀种类越多,RowKey就被打得越散。前缀不可以是随机的,因为必须要让客户端能够完整地重构RowKey。我们一般会拿原RowKey或其一部分计算hash值,然后再对hash值做运算作为前缀。

- RowKey 的设计原则

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

1、RowKey 长度原则
RowKey 是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为 10-100bytes,以 byte[] 形式保存,一般设计成定长。

建议越短越好,不要超过 16 个字节,原因如下:

在 HBase 的底层存储 HFile 中,RowKey 是 KeyValue 结构中的一个域。假设 RowKey 长度 100B,那么 1000 万条数据中,光 RowKey 就占用掉 100*1000w=10 亿个字节 将近 1G 空间,这样会极大影响 HFile 的存储效率。

HBase Rowkey的设计和优化实现_时间戳


HFile 简单结构示意

HBase 中设计有 MemStore 和 BlockCache,分别对应列族 /Store 级别的写入缓存,和 RegionServer 级别的读取缓存。如果 RowKey 字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。

HBase Rowkey的设计和优化实现_数据_02


同样,列族、列名的命名在保证可读的情况下也应尽量短。value 永远和它的 key 一起传输的。当具体的值在系统间传输时,它的 RowKey,列名,时间戳也会一起传输(因此实际上列族命名几乎都用一个字母,比如‘c’或‘f’)。如果你的 RowKey 和列名和值相比较很大,那么你将会遇到一些有趣的问题。Hfile 中的索引最终占据了 HBase 分配的大量内存。

2、唯一原则
其实唯一原则咱们可以结合 HashMap 的源码设计或者主键的概念来理解,由于 RowKey 用来唯一标识一行记录,所以必须在设计上保证 RowKey 的唯一性。

3、排序原则
RowKey 是按照字典顺序排序存储的,因此,设计 RowKey 的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。(疑问)

一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为 RowKey 的一部分对这个问题十分有用,可以用 Long.Max_Value-timestamp 追加到 key 的末尾。

例如 [key][reverse_timestamp],[key] 的最新值可以通过 scan [key]获得[key] 的第一条记录,因为 HBase 中 RowKey 是有序的,第一条记录是最后录入的数据。

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

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

结合前面分析的热点现象的起因思考:如果没有散列字段,首字段只有时间信息,那就会出现所有新数据都在一个RegionServer上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别RegionServer上,降低查询效率。
实际场景     -

实际业务中,有一部分是用户在日历上记录自己的行为。需要储存在 RowKey 中的维度有:用户 ID(uuid,不会超过十亿)、日历上的日期(date,yyyyMMdd格式)、记录行为的类型(type,0~99之间)。

记录的详细数据则存储在列 f:data 中。根据查询逻辑,我们可以设计的 RowKey 格式如下:

9~79809782~05~0008839540

长度正好是 24B。以字符‘~’为分界(‘~’的ASCII码是最大的,方便),各个部分的含义如下:
uuid.toString().hashCode() % 10
99999999 - date
StringUtils.leftPad(type, 2, "0")
StringUtils.leftPad(uuid, 10, "0")

参考这:https://mp.weixin.qq.com/s/cgbUqKo6Kjnkc9M_PBVjCg


特别声明:以上内容(图片及文字)均为互联网收集或者用户上传发布,本站仅提供信息存储服务!如有侵权或有涉及法律问题请联系我们。
举报
评论区(0)
按点赞数排序
用户头像
精选文章
thumb 中国研究员首次曝光美国国安局顶级后门—“方程式组织”
thumb 俄乌线上战争,网络攻击弥漫着数字硝烟
thumb 从网络安全角度了解俄罗斯入侵乌克兰的相关事件时间线
下一篇
keras_新闻多分类问题 2023-11-19 14:54:26