Rowkey的作用
- Rowkey用于标识唯一的行
- HBase中的数据都是根据Rowkey的字典序存储的,比如memstore中的数据和HFile中的数据
- 读写数据都需要通过Rowkey来定位Region
Rowkey的设计原则
长度原则
rowkey可以是任意字符串,最大长度64Kb,实际应用中一般为10-100bytes,以byte[]形式保存,一般设计成定长。且越短越好,一般不要超过16个字节,原因如下:
- 数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;
- MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。
- 目前操作系统都是64位系统,内存8字节对齐,控制在16个字节,8字节的整数倍利用了操作系统的最佳特性。
唯一原则
保证RowKey的唯一性,若向HBase中同一张表插入相同RowKey的数据,则原先存在的数据会被新的数据覆盖
排序原则
RowKey是按照字典序排序的
散列原则
设计的RowKey应均匀的分布在各个HBase节点上
如何避免热点
一、加盐(随机数)
并不等同于密码学中的加盐
具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点。
优点: 可以提高吞吐量,打散之后不同的前缀分配到不同的region。
缺点: 添加随机数之后无法根据原来的Rowkey查询,需要到多个region中查找,增加了读的开销;同一个Rowkey,但是两次加随机数生成的新的Rowkey不一样;可能存在开始和结尾的区域没有值的情况,但是也要占用内存;
查询数据的时候,如果知道Rowkey,可以使用get
精准快速的得到数据,反之,只能使用scan
进行模糊匹配查询,是非常慢的,这里也可以衍生出索引的概念,进而我们可以使用Phoenix创建盐表 + 二级索引,增加读的性能;
hbase 语法创建盐表:
create 'tablename','CF' SPLIT['a','b','c','d','e']
// 会产生6个region,每个region的Rowkey范围左闭又开 eg: [,a) 、 [a,b)
Phoenix:
CREATE TABLE SALT_TEST (a_key VARCHAR PRIMARY KEY, a_col VARCHAR) SALT_BUCKETS = 3;
// SALT_BUCKETS取值为1-256,表示预分区的个数
二、hash
create 'hash_table', 'order', {NUMREGIONS => 10, SPLITALGO => 'HexStringSplit'}
使用如上语法在HBase中创建一张10个分区的hash表,需要注意的是:put数据的时候,并不会自动对Rowkey进行hash,需要自己事先计算组装好新的Rowkey。一般取hash(Rowkey)的前几位拼接上Rowkey作为新的Rowkey。因为Rowkey太长也会增加维护开销。
优点: 每一个Rowkey的hash之后的值都是一样的,不会像盐表一样,将同一个Rowkey放到不同的region中。但是同样可以实现负载均衡。
缺点: 和盐表一样,将原来连续的数据打散了,增加了读的开销
三、反转Rowkey
原生数据rk在尾部出现良好的随机数,比如手机号码等,就可以考虑翻转使得随机分布,提高吞吐, 但是也打破了数据的自然顺序
案例
现有一张用户订单表,需要根据用户查询最新的订单记录
userid orderno skuname skuprice skunum skusum ordercretime
user1 0001 西瓜1 10 5 50 2019-07-07 12:00:00
user2 0002 西瓜2 10 50 500 2019-07-08 12:00:00
需求一 根据用户查询订单最新记录
where userid=user1 order by ordercretime desc limit 1
需求二 根据用户查询某个时间段xxx,xxx的订单记录
where userid=user1 and (ordercretime>=‘xxx’ and ordercretime<=‘xxxx’)
需求三 根据时间段查询订单记录
where (ordercretime>=‘xxx’ and ordercretime<=‘xxxx’)
需求四 根据用户买了西瓜的订单记录
where userid=user1 and skuname=‘西瓜’
在一个region中,越新put的数据在越后面。这样,如果不考虑rowkey,那么我们只能使用scan
来扫描到最新的数据,这是非常慢的且开销很大。那么如何来设计Rowkey?
我们取一个足够大的时间,比如设计的系统准备运行的时间(系统的生命周期)。假设我们设计的系统准备用十年,那么我们取十年后的时间戳来减去当前的时间戳作为我们的Rowkey,这样越新put的数据存储就会越靠前,这里需要注意的是将获得的差值位数用零补齐。eg:
最大的时间戳是:1000
当前时间戳是:565
差值为:435
那么我们需要将其补齐为:0435
Rowkey设计为
RowKey=hash(userid).substring(0, 4)+userid+ (Long.Max_Value - timestamp)
注意长度补齐
总结
- 建立预分区时,需结合自己的需求划分分区,否则分区不当,产生很多空的region,也会造成资源的浪费;一个region需要占用
hbase.hregion.memstore.mslab.chunksize
(2M)大小的memstore空间,可以根据小表1M、中表5M、大表20M来设置。 - 一个regionserver上的region数量一般在100-200个。
- Rowkey的设计,不可能十全十美,一般取决于自己的最大需求。
- 越重要越高频的字段在Rowkey越靠左边。