HBase Rowkey的设计原则

唯一原则

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

长度原则

Rowkey是一个二进制码流,Rowkey的长度建议设计在10~100个字节,不过越短越好,尽量不要超过16个字节

原因如下:

1.数据的持久化文件HFile中是按照Key-Value存储的,如果Rowkey过长比如100个字节,1000万列数据光Rowkey就要占用100*1000万=10亿个字节,将近1G数据,这会极大影响HFile的存储效率
2.MemStore将缓存部分数据到内存,如果Rowkey字段过长,内存的有效利用率就会降低,系统将无法缓存更多的数据,导致检索效率降低
3.目前操作系统是都是64位系统,内存8字节对齐。控制在16个字节,8字节的整数倍利用操作系统的最佳特性

需要指出的是不仅Rowkey的长度越短越好,而且列族名、列名等尽量都设计的短一些,因为HBase属于列式数据库,这些名字都是会写入到HBase的持久化文件HFile中去,过长的Rowkey、列族名、列名都会导致整体的存储量成倍增加

散列原则

Rowkey应均匀的分布在各个HBase节点上。拿常见的时间戳举例,假如Rowkey是按系统时间戳的方式递增的,Rowkey的第一部分如果是时间戳信息的话将造成所有新数据都在一个RegionServer上堆积的现象,也就是通常说的Region热点问题, 热点发生在大量的client访问集中在个别RegionServer上(访问可以是读,写或者其他操作),导致单个RegionServer机器自身负载过高,引起性能下降甚至Region不可用,常见的是发生jvm full gc或者显示region too busy异常情况,当然这也会影响同一个RegionServer上的其他Region

通常有3种办法来解决这个Region热点问题:

Reverse反转:针对固定长度的Rowkey,可以将其反转后存储,这样可以使Rowkey中经常改变的部分放在最前面,可以有效的打散Rowkey。反转Rowkey的例子通常以手机举例,可以将手机号反转后的字符串作为Rowkey,这样的就避免了以手机号那些比较固定的开头(137x、15x等)导致的热点问题,这样做的缺点是牺牲了Rowkey的有序性

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增加了写操作的吞吐量,不过缺点是同时增加了读操作的开销

Hash散列或者Mod:用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方法