改变HBase的Rowkey设计和scan脏数据问题

Changing Rowkey in HBase is Not Easy

Posted by S.L on September 4, 2020

Rowkey的设计,一般都会考虑热点问题,但是很少考虑可能会scan到脏数据的问题,这里记录一个线上业务扩展Rowkey后遇到的坑。

我们都知道设计Rowkey时需要注意的一些点:

  • 单调递增,按照字典顺序排序,更好的利用scan
  • scan操作依赖start和end的Rowkey,保证数据的独立和业务属性的可便利
  • 热点问题,如果设计不合理,可能会造成Rowkey大量存在相同前缀导致读写相同Region Server的情况,所以一般业务上都会进行哈希散列一下或者使用关键词倒序,如uid倒序后排列

如果修改Rowkey的规则一定要注意不要影响之前的数据,否则在scan的时候可能会扫描到「脏数据」。

之前的设计规则是{2位hash值}_{k1}_{k2}_{seqId},其中:
- seqId是业务的递增long数据
- k1_k2为业务固定规则的两个数据,如两个用户聊天时的大小uid的排列-
- 前面的hash是k1_k2哈希后的最后两位的值,保证唯一性,同时起到了打散数据的目的,防止某个用户的聊天会话过多造成热点。

上面的规则是点对点私聊的场景下的规则,因为业务的扩展,需要在一个业务下增加子业务标记,这里使用s(sub,可以理解为一个int)来标记。所以后续的子业务的数据变成:

{2位hash值}{k1}{k2}{s}{seqId}

期待可以各个子业务的数据独立的查询,但是却发现了一个业务可以查到另一个业务的数据的情况。

原因就是设计的Rowkey规则没有完全将两个业务的数据从字典顺序上完全隔离开来。由于使用了数字导致可能出现startRow和endRow之间可能包含另外一个业务的Rowkey, 举个例子:同样的两个uid的用户聊天,比如1001和1002,由于uid是固定的,所以2位hash也是固定的,这里假设为14,所以前缀就变成了14_1001_1002_,如果父业务和子业务 刚好有完全相同的用户uid,那么父业务的scan操作会包含到子业务的Rowkey,如:

假设要遍历父业务的从
14_1001_1002_0到14_1001_1002_324的数据
由于子业务的规则同样是在相同前缀后扩展的数字,如2,14_1001_1002_2_0、14_1001_1002_2_1...
由于Rowkey按照字典顺序排列,所以14_1001_1002_2_{x}的`14_1001_1002_2`是排列在14_1001_1002_324的`14_1001_1002_3`前面的

所以一种比较好的方式是在已有的Rowkey的可变部分的第一个位置的字符处直接通过一个跨度很大的值给隔离开来,如使用英文字母是比较好的方式,因为数字不管范围 是多少,最终都不会和第一个位置是字母的相同的。如:

{2位hash值}{k1}{k2}A{s}_{seqId}

由于老的Rowkey后面都是数字,不管是0-9的任何一个,都不可能包含和字母A开头的范围,这样就彻底将Rowkey隔离开了,保证了业务的scan的安全。

小误区:设计Rowkey时不需要在意相同前缀的后缀部分的长度不同问题,因为长短不同并不影响排序,字典是根据相同位置的ASCII进行排列的,越靠前的位置 的字母排序越靠后则排序越靠后,可能它的后缀更短,比如1233213肯定是排在133前面的,不要和数字比较混淆。

Reference

本文首次发布于 S.L’s Blog, 作者 @stuartlau , 转载请保留原文链接.