网上查了很多资料,关于hbase rowkey到底应该怎么设计。总结下来就是4点。

1.唯一原则。
必须在设计上保证其唯一性。由于在HBase中数据存储是Key-Value形式,若HBase中同一表插入相同Rowkey,
则原先的数据会被覆盖掉(如果表的version设置为1的话),所以务必保证Rowkey的唯一性。

2.排序原则。
HBase的Rowkey是按照ASCII有序设计的,我们在设计Rowkey时要充分利用这点

3.散列原则。
设计的Rowkey应均匀的分布在各个HBase节点上。拿常见的时间戳举例,如果Rowkey是按系统时间戳的方式递增,
Rowkey的第一部分如果是时间戳信息的话将造成所有新数据都在一个RegionServer上堆积的热点现象,也就是通常说的Region热点问题。
热点发生在大量的client直接访问集中在个别RegionServer上(访问可能是读,写或者其他操作),
导致单个RegionServer机器自身负载过高,引起性能下降甚至Region不可用。

解决散列的方法包括:反转,加盐,hash散列。

4.长度原则。
Rowkey是一个二进制,Rowkey的长度被很多开发者建议说设计在10~100个字节,建议是越短越好。

看完这4点后都能明白rowkey该怎么设计了,但是当你真正设计时又不知所措,我现在结合具体的一个业务场景来说说rowkey是怎么设计的。

业务需求:实现一个类似百度云盘的存储系统。
1.有非常多的文件和文件夹需要存储,以图片、文档为主。
2.能快速查找到某个所需要的文件。
3.需要查找某个时间段内的所有文件。

-------------------------------------------------------

放在关系型数据库中设计如下:
列名        注释
Id            表主键
Name        目录名称
ParentId    父目录Id,根目录Id为0
IsFile        true为文件,false为文件夹
createor    文件创建者
size        文件大小
type        文件类型

----------------------------------------

如果转化为hbase数据库表如下:

Rowkey        Column

rowkey_id    cf:name
                    cf:parentId
                    cf:isFile
                    cf:createor
                    cf:size
                    cf:type
------------------------------------------------------

上面的表设计好了,现在如果想查找a.txt的文件夹怎么办?只通过查找rowkey_id根本做不到,难道扫描全表列族吗,这是不现实的,所以需要改进。
改进后的表如下:
Rowkey      Column
name          cf:parentId
                   cf:isFile
                   cf:createor
                   cf:size
                   cf:type

------------------------------------------------------

这里出现了新问题,如果有两个叫a.txt的文件怎么办?不满足rowkey的唯一性原则。继续改进。
继续改进:
Rowkey        Column
fullpath    cf:parentId
               cf:isFile
               cf:createor
               cf:size
               cf:type

------------------------------------------------------

现在基本满足需求了,可是如果在某个目录比如/home下面存储着大量的文件,大量客户端短时间内的请求操作又会造成热点问题。
改进如下,设计两张表,分别是目录表和文件表。

目录表只存储目录,rowkey存储的是全路径,列族sub存储的是子目录名称,单纯赋值为1,无实际意义,
cf列族包括目录创建者,以及当前目录的唯一id。

Rowkey                Column
/dir1                     sub:dir2=1    sub:dir3=1
                            cf:createor=tom    cf:id=0001
/dir1/dir2              sub:dir4=1
                            cf:createor=tom cf:id=0002
/dir1/dir2/dir4       sub:dir5=1
                            cf:createor=tom cf:id=0003
                    
文件表:rowkey是目录表中的唯一id+文件名的组合,c列族是内容的字节数组,cf列族包括文件创建者,文件大小和文件类型。
Rowkey                Column
0001_file1            c:content=bytes
                            cf:createor=mary cf:size cf:type
0002_file2            c:content=bytes
                            cf:createor=mary cf:size cf:type
0003_file3            c:content=bytes
                            cf:createor=mary cf:size cf:type
 ------------------------------------------------------

上述的设计解决了部分热点问题,id+name的组合保证文件表大概是均匀分布的,而且rowkey是有序的方便查找。而且根据rowkey可以进行目录下的文件进行前缀过滤。

到这里基本上就可以了,但是我们还可以继续优化如下:
目录表去掉了唯一id,改进了sub列族,如果是目录,以d_dir命名,如果是文件,以f_file命名,赋值为随机的uuid
Rowkey                Column
/dir1                      sub:d_dir2=1    sub:f_file1=uuid
                             cf:createor=tom
/dir1/dir2               sub:dir4=1    sub:f_file2=uuid
                             cf:createor=tom
/dir1/dir2/dir4        sub:dir5=1    sub:f_file3=uuid
                             cf:createor=tom
                    
文件表的rowkey设计成了随机的uuid
Rowkey                Column
uuid              c:content=bytes
                     cf:createor=mary cf:size cf:type
uuid              c:content=bytes
                     cf:createor=mary cf:size cf:type
uuid              c:content=bytes
                     cf:createor=mary cf:size cf:type

uuid的设计保证了文件表是均匀分布的,而且比刚才的好处是当你只想知道某个文件夹下所有目录以及文件名称还不想知道文件具体内容的时候直接查询目录表就行了,
不用再查询一次文件表。不过坏处就是文件表不支持过滤查询了。

以上就是rowkey的一种设计思路,希望能给大家一定启发。