建议使用以下浏览器,以获得最佳体验。 IE 9.0+以上版本 Chrome 31+ 谷歌浏览器 Firefox 30+ 火狐浏览器
请选择 进入手机版 | 继续访问电脑版
设置昵称

在此一键设置昵称,即可参与社区互动!

确定
我再想想
选择版块
1035213fnlks2fx6defuh6.jpg 云搜索服务CSS 钜惠专场 2020年华为云AI实战营 华为云普惠AI

Lettle whale

发帖: 22粉丝: 4

级别 : 版主

发消息 + 关注

发表于2020-5-7 11:08:02 756 3
直达本楼层的链接
楼主
显示全部楼层
[技术分享] HBase RowKey思路


1       概述   

HbaseNo SQL的列存储数据库,和关系型数据库不同,没有表连接操作,在操作上限制比较大。因此,如果Schema设计的不好,会大大降低数据访问的效率,无端增加系统的负载。

Schema设计需要综合考虑业务的需求和Hbase存储、访问的特点。设计好的Schema,能够充分享用Hbase大容量存储的优势同时,又能够获取较高的访问效率。

本文所阐述的原则基于Hbase的路由寻址原理和底层文件存储结构,因此对本文的理解需要首先掌握Hbase路由寻址原理和文件存储结构。

本文阐述HbaseSchema设计的经验原则,但需要注意的是Schema设计是根据场景综合考虑的结果,没有哪个准则能够适应所有的场景,因此在应用中需要综合考虑多个准则,平衡多种因素,才能设计出最符合业务需求的Schema

设计任何HBase业务的schema,都综合要考虑如下问题:

Ø  读数据

o    业务读数据是否方便?业务逻辑尽量简单

o    要读取的数据在存储上是否连续分布?连续分布的数据读取效率较高,系统IO的垃圾数据较少

Ø  写数据

o    一次RPC能够写入多少数据?IOhbase的瓶颈之一,需要考虑一次RPC能够写入更多的数据。

o    写数据是否能够均衡?一段时间之内写入的数据在分布上能够一定程度上分散开能够保证并发度,提高写数据速度。

o    写数据是否相对集中?短时间内写入的数据太分散的话会导致容易达到写操作内存警戒线,影响性能。

Ø  数据清理

o    如何清理数据?Hbase没有提供高效的数据清理手段,数据如何清理(Delete/dorptable/TTL/Versions?

o    如何保证没有大量过小或者空的region?空regionHBase中占用负载和内存,而且hbase没有有效手段来清理或者合并这些region,如何避免?

 

 

2       Schema设计思路   

2.1   总体思路   

本节讲述Schema设计总体的思路,RowkeyColumnFamilyValue的设计为本节讲述的原则的具体实现。

2.1.1   Denormalization——以空间换取效率

HBase具有大容量的特点,摆脱了Normalization的限制,可以采用冗余的方法,将常用的计算结果存储在一张数据表中,不需要在查询的时候适时计算。

例如,对于排名统计,受容量限制,传统数据库采用适时计算排序的方式。这样的做法在数据量比较大的时候,适时计算无法满足查询的性能要求。而在HBase中,可以将排名数据提前计算出来,存放在Hbase中。如下所示为topURLSchema设计:

1.jpg


2.1.2   业务需要同时访问的数据连续分布

Hbase是排序过的列存储形式。数据访问按照数据块来进行。一次业务中需要的数据连续分布可以让系统读到的一个数据块包含更多的有用信息。

另外,Hbase Scan的效率远高于random read,连续分布的业务数据可以使用scan操作。

HBase数据在存储上先按照rowkey进行排序,rowkey相同的按照column排序,rowkeycolumn都相同的按照时间戳排序。HBase存储的格式是byte类型,按照ascii编码大小进行排序。如下图所示:

2.gif


2.1.3   减少系统瓶颈的压力

Hbase系统性能的瓶颈在于IO,因此提高系统的性能关键在于减少IO上的压力。可以采用的方法有:

Ø  能够同时存取的数据压缩在一个cell中,如使用protobuf

Ø  能够同时存取的数据放在同一行的不同列中,减少RPC

2.2   数据放在同一个cell中还是同一行的不同列中需要根据数据量的大小和枚举值的多少进行权衡。表设计   

Hbase表可以定义一些表属性,这些属性的合理使用对业务有较大影响。这些属性包括TTLVersionscompressionbloomfilter等。另外,建表的方法以及region划分对系统并发能力、数据清理能力有影响。

2.2.1   按天建表

按天建表的主要作用有两个:

Ø  可以将数据以天为单位分开,这样当数据过期的时候,可以通过drop表的方法来清理。

Ø  在存入数据的时候,只涉及到当天的数据表,数据相对集中,在数据量非常大的情况下会有更好的性能表现。

按天建表是一个例子,实际应用中可以按照特定时间周期来建表,例如年、月等。

建表选择时间周期的原则有两个:

Ø  当数据过期以后,能够同时清理

Ø每张表的数据分配到每个regionserver上以后,能够划分成数目合适的region。每个regionserver的激活Region数大于1,小于(写操作内存/flushsize)为宜。如果激活region数过少,则系统并发度不好;否则如果过大,会导致写操作内存很容易达到警戒线,影响写操作性能。

2.2.2   预分region

HBase0.90以后的版本提供了建表预分region的功能。建表预分region可以让region在开始阶段就能够均衡到各个regionserver之间。因此需要根据数据的分布,识别出可能会造成热点的key的区域,在这些区域设置splitkey,进行预分region。

2.2.3   TTL

TTL是另外一种数据清理的方法。设定数据的生命周期,在数据过期以后,hbase自动清理这些数据。

和按天建表,过期后删表的方法相比,设置TTL的方法更为简单直观,但是也有一些缺点:

Ø  过期的数据需要一定时间才能够清理掉,这时候如果查询到了过期的数据可能会长时间没有响应,影响查询的性能。

Ø  使用TTL来清理数据主要是通过compact进行的,在特定场合下会增加compact的负担。

因此,对TTL的使用需要根据业务需求、数据量来综合考虑。需要尽量避免查询到过期的数据,如果系统负荷非常大,不建议使用TTL。

2.2.4   Versions

Hbase是个多维的数据表。每行每列可以存放多个版本的数据。多个版本之间除了内容不同以外,唯一的区别是时间戳。时间戳相同的版本会被覆盖掉。

Versions提供了查询历史版本的信息,但是版本个数是有限的,对于需要查询历史上不同时间段的内容的场景,可以通过设置多个版本来实现。

Versions的个数有限,如果需要存放的历史版本数量非常巨大或者数量不能确定在一定范围之内的场景,不适合使用versions。

另外,versions也是清理数据的一种方式。例如设置versions=3,如果在其中写入了4个版本的数据,则最老的版本数据会被清理掉。

2.2.5   使用压缩

创建表的时候,可以指定压缩算法。Hbase0.90系列版本支持lzogzip两种压缩方式。

Lzo的压缩率低一些,能够压缩到原始数据的40%左右,但是压缩速度比较快,适合于对于存取速度要求比较高,但对空间要求略低的场景。

Gzip的压缩率高,能够压缩到原始数据的30%以内,但是压缩速度比较慢,适合于存取速度要求低,但空间要求高的场景。

压缩率和数据的结构有关系,如果是随机程度比较高的无序数据,压缩率会比较低一些,如果是比较有规律的数据(如日志数据),压缩率会比较高。

另外,使用lzo需要用到具有gpl 授权的桥接程序,商用的化需要考虑授权的问题。

2.2.6   Bloomfilter

Hbase中,使用bloomfilter的列,写入数据的时候,要将该数据的key添加到bloomfilter中;读取数据之前,要使用该数据的keybloomfilter中进行测试,确定该key是否添加到了bloomfilter中。因此,bloomfilter可以用来在读取一个精确的key之前,预判该key是否在文件中实际存在,进而减少读取文件的消耗。由于bloomfilter是根据多个hash函数hash出来的bit值进行判断,可能会有重复,因此bloomfilter可以准确判断出一个key的不存在,而不能准确判断出key的存在。

BloomFilter的key指的是Hstorekey。HStorekey中包括row、column、timestamp成员变量。 在实际使用中,获取bloomfilterkey的返回值是Hstorekey的row。因此,hbase中,Bloomfilter真正使用的key是rowkey。

bloomfiltervectorsize、元素个数、hash函数个数之间的关系如下:

M表示vectorsizen表示元素个数,k表示hash函数个数,则:

M = (k*n)/ln2

hbase中,hash函数个数默认为4,因此,mn的关系为:

M=5.77n

因此,假定有1M条记录要插入bloomfilter,则需要的向量的大小为5.7M bit,约0.7Mb

如果每条记录在磁盘上存储的大小是256个字节,如果有1T的数据,则有4G条记录,约耗费2.7G内存用于存储bloomfilter

如果每条记录在磁盘上的存储大小为2K,如果有1T的数据,则有500M条记录,约耗费350M内存用于存储bloomfilter

因此,存储的每条记录越大,bloomfilter的空间利用效率越高。

 

综上所述,适合使用bloomfilter的场景需要同时满足如下条件:

Ø  访问数据使用精确的key。

Ø  数据是小key,大value的模式。(即使这样,也要根据数据量来计算消耗的内存,综合判断)

 

2.3   Rowkey结构设计   

在系统设计中,为了提高系统性能都需要考虑以下两个因素:

1  提高数据访问的速度

2  提高数据的利用率

结合Hbase的存储访问特点,要做到以上两点,在hbaserowkey设计时,一个总的原则是:需要同时访问的数据,rowkey尽量连续。

原因如下:

          i.            数据Scan操作必须使用StartrowEndrow来指定范围,避免或减少使用filter才能提高访问速度。因为:

Ø  Hbaseregioninfo中,有regionstartrowendrow信息,因此指定了scanrowkey范围的话,hbase能够快速定位出需要访问的数据在哪个region

Ø  Hbase在文件存储上是排序并有索引的存储。Hfilekeyvalue的排序优先考虑rowkey的排序。并且Hfile的索引使用storekey,在storekey中,rowkey处于最优先的位置。因此如果指定出startrowendrowhbase就能快速定位需要访问的数据在hfile中的位置。这样可以提高数据访问的速度。

根据这个原则,如果需要访问的数据rowkey差异很大,则没有办法指定Startrowendrow。访问可能会是一个全表扫描的过程,效率就非常低了。

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

 

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

下面以MD业务和scmartcare业务的schema设计为例,讲述为了保证业务需要的数据rowkey连续以及保证数据良好分布的一些原则。

2.3.1   可枚举属性值较少的属性放在rowkey前面

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

例如如下TraficGeorowkey的场景:

模型一:

3.gif


ServiceType可枚举,并且数量较少,分为httprtsp两种,放在前面;而cpid可能会比较多(假设有5cp),因此放在后面。这样的设计能够适应如下两种需求,复杂度都比较小:

1  查询2010-10,所有cphttp数据。这种需求设置scanstartrow=2010-10,http,’,endrow=2010-10,http,z’,即可。

2  查询2010-10cp001的所有协议的数据。这种需求下,根据scan rowkey连续的原则,需要将查询划分成两个scan,分别查询httpcp001的数据和rtspcp001的数据。

但是,如果将cp放在前面,如下所示,适应性就差一些,如下所示模型二:

4.jpg


1  查询2010-10cp001的所有协议的数据。这种需求下,设置scanstartrow=2010-10,cp001,’,endrow=2010-10,cp001,z’,即可。

2  查询2010-10,所有cphttp数据。这种需求下,根据scanrowkey连续原则,需要将查询分成cp001httpcp002httpcp003httpcp004httpcp005http五个查询进行,相对模型一复杂了一些。

 

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

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

5.jpg


但是在percontent表中,业务需求是LDS查询某个URL一个时间段内的访问数据,因此date需要放在URL后面,如下所示。

6.jpg


 

2.3.3   多业务共用rowkey导致数据访问矛盾采用折中取舍或冗余的方法解决

同一种key,在满足不同需求的时候可能权重不同。例如3.1.1中讲述的cpidServiceType,在遇到不同需求的时候权重不同。但cpidserviceType是可枚举,数量较少的属性,可以按照3.1.1的方法解决。在一些场景中,有些不可枚举,数量较大的key也存在类似的问题,本节给出几种解决思路。

2.3.3.1         寻求折中兼顾的方案

例如:Percontent业务,在LDS中,业务需要查询video001guangdong2010-10-5~2010-10-20的数据,则rowkey设计成如下形式更为方便:

7.gif


但是,在LDA中,需要聚合每个月的daily数据,声称monthly数据,按照上面的格式,则无法指定一个scan的范围,来启动MR程序将daily数据聚合。但是把时间属性放在前面的话,就非常容易了,如下所示:

8.gif


因此,将时间属性放在前面还是后面,是一对矛盾,在不同场景下,权重不同,各有其适用场景。但是将时间分段,分别放在前后,能够很好的解决这个矛盾,如下所示:

9.gif


2.3.3.2         构造冗余数据

例如,percontent表的数据包含了URL Records的数据,URL Records的数据是冗余存储的,区别在于percontentURL放在date前面,而URL Records表的URL放在date后面。这就是由于URL在满足不同需求的时候,权重不同,由于URL Records需要的数据量不大,因此采用冗余的机制解决该矛盾。

2.3.3.3         权衡需求的重要性和系统忍受度选择一种方案

当两种需求有矛盾,但其中一方属于次要需求,并且在系统忍受度范围之内的话,可以舍弃一种方案。优先满足需求更强的一方。

2.3.4   时间属性在rowkey中的使用

如果需要经常访问特定时间段的数据,将时间属性放在rowkey中是一个较好的选择。

和利用时间戳来访问特定时间段的数据方法相比,将时间属性放在rowkey中具有可控性,容易将能够同时访问的数据相对集中存放的优点。

时间属性放在rowkey中需要注意数据分布和并发度的问题:hbase数据是按照rowkey排序的,时间属性放在rowkey中容易造成数据总是在末尾写入的情况,这种情况下并发度很差。这种情况可以通过在时间属性前面增加prefix和提前预分region的方法解决。

2.3.5   循环key使用

如果rowkey中有时间属性,并且随着时间的增加,rowkey会不断的增大下去的话,会造成region数量不断地增加。如果使用TTL来控制数据的生命周期,一些老的数据就会过期,进而导致老的region数据量会逐渐减少甚至成为空的region。这样一方面region总数在不断增加,另外一方面老的region在不断的成为空的region,而空的region不会自动合并,进而造成过多空的region占用负载和内存消耗的情况。

这种情况下,可以使用循环key的方法来解决。思路是根据数据的生命周期设定rowkey的循环周期,当一个周期过去以后,通过时间映射的方法,继续使用老的过期数据的rowkey

例如,key的格式如下:

YY-MM-DD-URL。如果数据的生命周期是一年,则可以使用MM-DD-URL的格式。这样当前一年过去以后,数据已经老化,后一年的数据可以继续写入前一年的位置,使用前一年数据的rowkey。这样可以避免空的region占用资源的情况。

根据hbase的原理,key的周期需要至少比TTL2* hbase.hregion.majorcompaction(默认24小时)的时间,才能够保证过期的数据能够在key循环回来之前得到完全清理。

按照时间周期进行建表的方式也可以解决空region的问题,和循环key方法相比较,循环key的优点如下:

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

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

Ø  需要使用TTL来老化数据,可能会增加compact负担

Ø  需要保证查询操作不会查询到过期数据,否则会影响系统性能。

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

 

2.3.6   通过rowkey设计来控制并发度

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

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

2.4   Family&Column   

2.4.1   可枚举数量少扩展性弱的属性作为Family,不可枚举数量多扩展性强的属性作为column

由于Column Family的增、删、改都需要首先disable Table,影响其他Family的正常访问,因此作为Family的属性取值范围要尽量减少扩展。

例如ServiceType,当前的取值只有两种httprtsp,看似比较适合做为Family,但是ServiceType可能会扩展,并不局限于httprtsp两种,当扩展的时候会影响系统的访问,因此并不推荐作为Family。而是放在rowkey中。

而在userequipment表中,设备的纪录纬度为播放器、浏览器、终端三种,不需要扩展,因此适合作为family。如下所示:

10.jpg


相对的,具体的浏览器类型、终端类型由于种类比较多,可枚举性差,所以适合放在column中。

 

2.4.2   对于hbase需求不同分表或者分family

每个family有自己独立的属性,例如versionTTL。因此,如果数据对于这些属性需求不同的话,则可以放在不同的family中。例如,相同类型的数据,如traficpercontent等,时间纬度不同,如daily数据、monthly数据需要保存的时间不一样,因此TTL不同。这种情况下,daily数据、monthly数据可以放在不同的family中或者不同的数据表中。

2.4.3   尽量均衡各个store的大小

如上一节所述,dailymonthlyhourly数据的TTL不同,可以放在不同的family中。但是这样做的话将会导致同一个reagion不同family的大小差异比较大,出现很多小的或者空的store,导致store数量无端增加。如下所示:

11.gif


由于hbase中,很多内部操作(如flushcompact)的执行单位是storestore的增加会影响系统性能,因此需要尽量避免由于不同family大小失衡导致的大量小store的出现。因此,dailyhourlymonthly数据推荐存放在不同的table中。

2.4.4   需要同时读取但数量较多的数据放在同一行的不同column

有些数据,业务需要同时访问,但这些数据比较多,不适合全部打包存放的,可以放在同一个rowfamily的不同column中。

例如,业务需要查询2010-10-10URL001在中国所有省的访问情况。这种场景下,中国31个省的数据需要同时读取,但是31个省比较多,不适合打包在一个cell里面,可以放在同一行的不同column中,如下所示。这样做可以减少rpc,方便存取。

12.jpg


2.5   Value   

2.5.1   多数场景下同时存取的数据放在一个cell

一个table中,相同rowfamilycolumn的数据为一个cell。多数场景下都需要同时存取的数据可以放在一个cell中。

例如,用户访问纪录数据中的属性requestdelivedconnectionduration等,在LDS端多数场景下需要同时访问,并且这些数据在存储时也是同时写入hbase,因此可以放在一个cell中。这样可以减少rowkey的存放,节省存储空间,提高访问效率。

放入一个cell中的数据可以通过protobuf来进行压缩封装。Protobuf提供了数据压缩、存取的接口。例如percontent业务的数据protobuf如下:

package com.huawei.ngcdn.md.lda.hbase.protobuf;

option java_outer_classname =   "VisitorsInfoProtos";

message Visitors {

  optional int64 requests = 1;

  optional int64   delivered  = 2;

  optional int64   internal  = 3;

  optional int64   duration  = 4;

  optional int64   users  = 5 ; 

  enum ServiceType {

    ALL = 0;

    HTTP = 1;

    RTSP = 2;

  }

  optional ServiceType type = 6   [default = ALL];

}

message VisitorsInfo {

       repeated   Visitors visitor = 1;

}

 

RequestsdelivedinternaldurationusersServiceType等属性经过protobuf打包压缩后,在hbase中以Byte[]的形式存放,如下所示:

13.jpg


 

                                                                                                                                                      本文转载自华为内部博客,作者:bijieshan

举报
分享

分享文章到朋友圈

分享文章到微博

人生的旅途

发帖: 4粉丝: 0

级别 : 中级会员

发消息 + 关注

发表于2020-6-12 10:01:47
直达本楼层的链接
沙发
显示全部楼层

mark,很好的分享~

点赞 评论 引用 举报

whisper_chen

发帖: 1粉丝: 0

级别 : 新手上路

发消息 + 关注

发表于2020-6-16 17:48:40
直达本楼层的链接
板凳
显示全部楼层

get√

点赞 评论 引用 举报

一朵翔云

发帖: 3粉丝: 0

级别 : 新手上路

发消息 + 关注

发表于2020-6-16 17:55:09
直达本楼层的链接
地板
显示全部楼层

优秀的文章!

点赞 评论 引用 举报

游客

富文本
Markdown
您需要登录后才可以回帖 登录 | 立即注册