HBase 之Rowkey设计

Rowkey的作用

  1. Rowkey用于标识唯一的行
  2. HBase中的数据都是根据Rowkey的字典序存储的,比如memstore中的数据和HFile中的数据
  3. 读写数据都需要通过Rowkey来定位Region

Rowkey的设计原则

长度原则

rowkey可以是任意字符串,最大长度64Kb,实际应用中一般为10-100bytes,以byte[]形式保存,一般设计成定长。且越短越好,一般不要超过16个字节,原因如下:

  1. 数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;
  2. MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。
  3. 目前操作系统都是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)

注意长度补齐

总结

  1. 建立预分区时,需结合自己的需求划分分区,否则分区不当,产生很多空的region,也会造成资源的浪费;一个region需要占用hbase.hregion.memstore.mslab.chunksize(2M)大小的memstore空间,可以根据小表1M、中表5M、大表20M来设置。
  2. 一个regionserver上的region数量一般在100-200个。
  3. Rowkey的设计,不可能十全十美,一般取决于自己的最大需求。
  4. 越重要越高频的字段在Rowkey越靠左边。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章