目录

HBase中的rowkey唯一的决定了一行数据,使用HBase的场景多种多样, rowkey设计的好坏很大程度上决定了应用场景中的执行效率。

通过具体的一个场景样例,简单的研究一下在Hbase的rowkey设计上的一些原则。

样例场景

假设当前有一个网上商城系统, 需要实现用户拉取历史订单的需求。当然, 在历史订单量比较少的时候,简单的使用mysql就能很好的支持需求,但是当数据量到一定量级,尤其是对于写多读少的场景的时候,HBase也会成为我们的一个选择。

如果要使用HBase来实现用户历史订单的这么一个需求(据我所知,京东和滴滴的历史订单也是用HBase来支撑的),我们该如何设计rowkey呢。

最直观的, 当然是将用户的ID直接当作rowkey的一部分。当然,只有用户的ID是不够的, 为了表示多个订单, 一般可以将时间戳作为rowkey的一部分,比如{{user_id}}_{{timestamp}},这样一个订单作为一样插入到HBase中,拉取的时间将用户的ID当作前缀,可以将所有当前用户的订单拉取出来。

但是这样肯定是有问题的, 我们将其中存在的问题一一列出来并给出相应的解决办法。

热点问题

第一个首先是热点问题,一般用户的ID会存在一定的规律性, 比如自增性。考虑一种场景, 如果商城推出了一项新活动, 吸引了大量新用户来下单。那么这波新用户的用户ID很大可能上是相似的,这样的话,大量的单子就会落到HBase中一个或极少数节点。这就是所谓的热点问题,大量的访问会使得热点region所在的机器承受了大部分压力,引起性能的下降,但是同时其他region有可能是比较闲置的状态。

那么如何尽量的避免这种不均衡的状态呢,当然可选择的方式也是多样的。

加盐

加盐就是所谓的salt值,通过在rowkey中加入一个随机值,使得即使用户ID相近,加盐之后仍旧能够分配到不同的region。

哈希

哈希也是一个常用的打散数据的方式,比如在我们的例子中, 固定一种哈希算法,生成rowkey的时候不是直接用用户的ID,而是将用户的ID经过哈希之后的值当作rowkey的一部分,这样即使短时间内用户的ID都是相邻的,他们得到的哈希值也是分散的。在拉取的时候,因为哈希算法固定,能够很快的通过用户的ID找到哈希之后的值。

翻转

用户的ID如果具有自增性, 这个就表示很多用户的ID其实前面部分是相同的,只是后半部分在一直变化。那么对用户的ID进行反转之后,就是前半部分在一直变化了, 这样也能达到打散分布的效果。

访问顺序问题

如果用翻转的方式解决热点问题, 现在用户历史订单的rowkey的格式就是{{reverse(user_id)}}_{{timestamp}}。不过这样也会有问题, 一般查看历史订单的时候,都是要求最近的单子显示在最前边, 如果按照这种rowkey的方式去存放数据,由于HBase中的数据都是按照字典序排序的,没有办法按照时间的逆序取数据。

翻转时间戳

所以通常的做法是翻转时间戳,在上边的的rowkey例子中, 将timestamp替换成Long.Max_Value - timestamp,即rowkey的格式是{{reverse(user_id)}}_{{Long.Max_Value - timestamp}}。

这么做有什么好处呢, 我们可以想见时间越近的单子,Long.Max_Value - timestamp会越小,也就是会排的越前面。 当取历史单子的时候,我们用Long.Max_Value减去当前时间戳得到的数值甚至比最近单子的后缀还要小,将“Long.Max_Value减去当前时间戳” 当作scan的startRow就能逆序取到最新的所有订单列表。

当然, 在设计的时候还有其他一些规则,比如col_family要进行短,rowkey也要尽量短等等, 不再赘述。