欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Hbase 的RowKey设计原则

程序员文章站 2022-05-27 15:55:14
...

hbase所谓的三维有序存储的三维是指:rowkey(行主键),column key(columnFamily+qualifier),timestamp(时间戳)三部分组成的三维有序存储。

rowkey是行的主键,而且hbase只能用个rowkey,或者一个rowkey范围即scan来查找数据。所以 rowkey的设计是至关重要的,关系到你应用层的查询效率。

rowkey是以字典顺序排序的,存储的是字节码。

根据rowkey范围查询的时候,一般是知道startRowkey,如果我们通过scan只传startRowKey : d开头的,那么查询的是所有比d大的都查了,而我们只需要d开头的数据,那就要通过endRowKey来限制。我们可以通过设定endRowKey为:d 开头,后面的根据你的rowkey组合来设定,一般是加比startKey大一位。比如说rowkey设计为:用户ID-日期,那么查某个用户某天的数 据,startKEY为3231-20121212,endKey为:3231+201213,那么你查到的就是用户为3231在20121212这一天 的数据。

需要访问的数据rowkey连续的话,scan到的有用数据比较多,利用率高,能够避免或减少反复内存倒换。

rowkey的设计和数据的分布有很大关系,rowkey设计的时候需要保证数据入库时的并发度,但又不能过于分散。

可枚举属性值较少的属性放在rowkey前面 
在rowkey中,需要放入多个属性,这多个属性的先后次序和访问的效率有直接的关系。一个普遍的规则是:数量较少,可控的属性放在rowkey前面(如ServiceType,CPID等);反之放在后面(如url,mxid等)。这样做的原因是可控属性放在前面,对各种不同查询需求的平衡性强一些,反之平衡性较差。

案例1:

201010-http-cp001-s-shanghai-xxx-1
201010-http-cp002-s-shenzhen-xxx-2
201010-rtsp-cp001-s-shanghai-xxx-1
201010-rtsp-cp002-s-shenzhen-xxx-2

ServiceType可枚举,并且数量较少,分为http和rtsp两种,放在前面; 
而cpid可能会比较多(假设有5个cp),因此放在后面。 
这样的设计能够适应如下两种需求,复杂度都比较小: 
1) 查询2010年10月所有cp的http数据。这种需求设置scan的startrow=‘201010-http-’,endrow=‘201010-http-z’,即可。 
2) 查询2010年10月cp001的所有协议的数据。这种需求下,根据scan rowkey连续的原则,需要将查询划分成两个scan,分别查询http类型cp001的数据和rtsp类型cp001的数据。

但是,如果将cp放在前面,如下所示,适应性就差一些,如下所示案例2:

201010-cp001-http-s-shanghai-xxx-1
201010-cp002-http-s-shenzhen-xxx-2
201010-cp001-rtsp-s-shanghai-xxx-1
201010-cp002-rtsp-s-shenzhen-xxx-2

1) 查询2010年10月cp001的所有协议的数据。 
这种需求下,设置scan的startrow=‘201010-cp001-’,endrow=‘201010-cp001-z’,即可。 
2) 查询2010年10月,所有cp的http数据。 
这种需求下,根据scan的rowkey连续原则,需要将查询分成cp001-http、cp002-http、cp003-http、cp004-http、cp005-http五个查询进行,相对模型一复杂了一些。

业务访问中权重高的key放在前面

例如URLRecords表的主要用途是用来计算当天的URL访问排名。根据业务需求,需要访问某天的所有URL,因此date是主键,权重更高,放在前面,而URL则放在后面。

构造冗余数据 
例如,percontent的数据包含了URL Records的数据,URL Records的数据是冗余存储的,区别在于percontent的URL放在date前面,而URL Records表的URL放在date后面。这就是由于URL在满足不同需求的时候,权重不同,由于URL Records需要的数据量不大,因此采用冗余的机制解决该矛盾。 
权衡需求的重要性和系统忍受度选择一种方案 
当两种需求有矛盾,但其中一方属于次要需求,并且在系统忍受度范围之内的话,可以舍弃一种方案。优先满足需求更强的一方

时间属性在rowkey中的使用 
如果需要经常访问特定时间段的数据,将时间属性放在rowkey中是一个较好的选择。 
和利用时间戳来访问特定时间段的数据方法相比,将时间属性放在rowkey中具有可控性,容易将能够同时访问的数据相对集中存放的优点。 
时间属性放在rowkey中需要注意数据分布和并发度的问题:hbase数据是按照rowkey排序的,时间属性放在rowkey中容易造成数据总是在末尾写入的情况,这种情况下并发度很差。这种情况可以通过在时间属性前面增加prefix和提前预分region的方法解决。

循环key使用 
(1)存在问题 
如果rowkey中有时间属性,并且随着时间的增加,rowkey会不断的增大下去的话,会造成region数量不断地增加。如果使用TTL来控制数据的生命周期,一些老的数据就会过期,进而导致老的region数据量会逐渐减少甚至成为空的region。这样一方面region总数在不断增加,另外一方面老的region在不断的成为空的region,而空的region不会自动合并,进而造成过多空的region占用负载和内存消耗的情况。 
(2)解决办法 
这种情况下,可以使用循环key的方法来解决。思路是根据数据的生命周期设定rowkey的循环周期,当一个周期过去以后,通过时间映射的方法,继续使用老的过期数据的rowkey。 
例如,key的格式如下: 
YY-MM-DD-URL。如果数据的生命周期是一年,则可以使用MM-DD-URL的格式。这样当前一年过去以后,数据已经老化,后一年的数据可以继续写入前一年的位置,使用前一年数据的rowkey。这样可以避免空的region占用资源的情况。

根据hbase的原理,key的周期需要至少比TTL大2* hbase.hregion.majorcompaction(默认24小时)的时间,才能够保证过期的数据能够在key循环回来之前得到完全清理。 
按照时间周期进行建表的方式也可以解决空region的问题,和循环key方法相比较,循环key的优点如下:

  • 操作简单,不需要重复建表,系统自动处理

同样,循环key具有如下劣势:

  • 需要使用TTL来老化数据,可能会增加compact负担
  • 需要保证查询操作不会查询到过期数据,否则会影响系统性能。

如果在系统压力不是特别大,需要长期运行,能够控制查询不会查询到过期数据的场景下,建议使用TTL+循环key的方式,否则建议使用按照时间周期进行建表的方式。

通过rowkey设计来控制并发度 
在相同业务模式下,不同的rowkey设计系统的并发度不一样。和按天建表的思路类似,通过rowkey控制并发度的原则是**的region总数适中,每个regionserver的**Region数大于1,小于(写操作内存/flushsize)为宜。

为了实现这一点,可以将可枚举、数量有限的属性放在rowkey的前面,时间放在后面的方式来提高并发度;通过将大粒度的时间属性(如天、小时等)放在rowkey前面,数量很大的可枚举属性(如电话号码、URL等)放在后面的方法来控制**的region数。