前面和大家分享了一些HBase入门基础知识。HBase作为一个数据库,在使用中无外乎增删改查操作,这些操作在HBase中都是和RowKey紧密相关的,所以优秀的RowKey设计方案是非常重要的。今天就来说说如何进行HBase的RowKey的设计。
RowKey的作用
RowKey在查询中的作用
HBase中RowKey可以唯一标识一行记录,在HBase中检索数据有以下三种方式:
- 通过 get 方式,指定 RowKey 获取唯一一条记录
- 通过 scan 方式,设置 startRow 和 stopRow 参数进行范围匹配
- 全表扫描,即直接扫描整张表中所有行记录
当大量请求访问HBase集群的一个或少数几个节点,造成少数RegionServer的读写请求过多、负载过大,而其他RegionServer负载却很小,这样就造成热点现象。大量访问会使热点Region所在的主机负载过大,引起性能下降,甚至导致Region不可用。所以我们在向HBase中插入数据的时候,应尽量均衡地把记录分散到不同的Region里去,平衡每个Region的压力。
下面根据一个例子分别介绍下根据RowKey进行查询的时候支持的情况。
如果我们RowKey设计为uid
+phone
+name
,那么这种设计可以很好的支持一下的场景:
uid=873969725 AND phone=18900000000 AND name=zhangsan
uid= 873969725 AND phone=18900000000
uid= 873969725 AND phone=189?
uid= 873969725
复制代码
难以支持的场景:
phone=18900000000 AND name = zhangsan
phone=18900000000
name=zhangsan
复制代码
从上面的例子中可以看出,在进行查询的时候,根据RowKey从前向后匹配,所以我们在设计RowKey的时候选择好字段之后,还应该结合我们的实际的高频的查询场景来组合选择的字段,越高频的查询字段排列越靠左。
RowKey在Region中的作用
在 HBase 中,Region 相当于一个数据的分片,每个 Region 都有StartRowKey
和StopRowKey
,这是表示 Region 存储的 RowKey 的范围,HBase 表的数据时按照 RowKey 来分散到不同的 Region,要想将数据记录均衡的分散到不同的Region中去,因此需要 RowKey 满足这种散列的特点。此外,在数据读写过程中也是与RowKey 密切相关,RowKey在读写过程中的作用:
-
读写数据时通过 RowKey 找到对应的 Region;
-
MemStore 中的数据是按照 RowKey 的字典序排序;
-
HFile 中的数据是按照 RowKey 的字典序排序。
RowKey的设计
在HBase中RowKey在数据检索和数据存储方面都有重要的作用,一个好的RowKey设计会影响到数据在HBase中的分布,还会影响我们查询效率,所以一个好的RowKey的设计方案是多么重要。首先我们先来了解下RowKey的设计原则。
RowKey设计原则
长度原则
RowKey是一个二进制码流,可以是任意字符串,最大长度为64kb,实际应用中一般为10-100byte,以byte[]形式保存,一般设计成定长。建议越短越好,不要超过16个字节,原因如下:
- 数据的持久化文件HFile中时按照Key-Value存储的,如果RowKey过长,例如超过100byte,那么1000w行的记录,仅RowKey就需占用近1GB的空间。这样会极大影响HFile的存储效率。
- MemStore会缓存部分数据到内存中,若RowKey字段过长,内存的有效利用率就会降低,就不能缓存更多的数据,从而降低检索效率。
- 目前操作系统都是64位系统,内存8字节对齐,控制在16字节,8字节的整数倍利用了操作系统的最佳特性。
唯一原则
必须在设计上保证RowKey的唯一性。由于在HBase中数据存储是Key-Value形式,若向HBase中同一张表插入相同RowKey的数据,则原先存在的数据会被新的数据覆盖。
排序原则
HBase的RowKey是按照ASCII有序排序的,因此我们在设计RowKey的时候要充分利用这点。
散列原则
设计的RowKey应均匀的分布在各个HBase节点上。
RowKey字段选择
RowKey字段的选择,遵循的最基本原则是唯一性,RowKey必须能够唯一的识别一行数据。无论应用的负载特点是什么样,RowKey字段都应该参考最高频的查询场景。数据库通常都是以如何高效的读取和消费数据为目的,而不是数据存储本身。然后,结合具体的负载特点,再对选取的RowKey字段值进行改造,组合字段场景下需要重点考虑字段的顺序。
避免数据热点的方法
在对HBase的读写过程中,如何避免热点现象呢?主要有以下几种方法:
Reversing
如果经初步设计出的RowKey在数据分布上不均匀,但RowKey尾部的数据却呈现出了良好的随机性,此时,可以考虑将RowKey的信息翻转,或者直接将尾部的bytes提前到RowKey的开头。Reversing可以有效的使RowKey随机分布,但是牺牲了RowKey的有序性。
缺点:
利于Get操作,但不利于Scan操作,因为数据在原RowKey上的自然顺序已经被打乱。
Salting
Salting(加盐)的原理是在原RowKey的前面添加固定长度的随机数,也就是给RowKey分配一个随机前缀使它和之间的RowKey的开头不同。随机数能保障数据在所有Regions间的负载均衡。
缺点:
因为添加的是随机数,基于原RowKey查询时无法知道随机数是什么,那样在查询的时候就需要去各个可能的Regions中查找,Salting对于读取是利空的。并且加盐这种方式增加了读写时的吞吐量。
Hashing
基于 RowKey 的完整或部分数据进行 Hash,而后将Hashing后的值完整替换或部分替换原RowKey的前缀部分。这里说的 hash 包含 MD5、sha1、sha256 或 sha512 等算法。
缺点:
与 Reversing 类似,Hashing 也不利于 Scan,因为打乱了原RowKey的自然顺序。
RowKey设计案例剖析
1. 查询某用户在某应用中的操作记录
reverse(userid) + appid + timestamp
2. 查询某用户在某应用中的操作记录(优先展现最近的数据)
reverse(userid) + appid + (Long.Max_Value - timestamp)
3. 查询某用户在某段时间内所有应用的操作记录
reverse(userid) + timestamp + appid
4. 查询某用户的基本信息
reverse(userid)
5. 查询某eventid记录信息
salt + eventid + timestamp
如果 userid
是按数字递增的,并且长度不一,可以先预估 userid
最大长度,然后将userid
进行翻转,再在翻转之后的字符串后面补0(至最大长度);如果长度固定,直接进行翻转即可(如手机号码)。
在第5个例子中,加盐的目的是为了增加查询的并发性,加入Slat的范围是0~n,可以将数据分为n个split同时做scan操作,有利于提高查询效率。
总结
在HBase的使用过程,设计RowKey是一个很重要的一个环节。我们在进行RowKey设计的时候可参照如下步骤:
- 结合业务场景特点,选择合适的字段来做为RowKey,并且按照查询频次来放置字段顺序
- 通过设计的RowKey能尽可能的将数据打散到整个集群中,均衡负载,避免热点问题
- 设计的RowKey应尽量简短
好文推荐:
-
福利!入门HBase的正确姿势
-
Spark任务调度
-
Spark Streaming读取Kafka数据两种方式
-
Spark快速入门-Spark执行过程 |
-
Spark快速入门-RDD操作手册
-
Spark快速入门-RDD
-
Hive性能优化小结