HBASE中column family的设计,rowkey的设计,以及row key的设计原则问题

一、Hbase中的每条记录的结构

Hbase的表组成:一个表可以理解成是行的集合,行(记录)是列族的集合,列族是列的集合。

(1) 列族column family:它是column的集合,在创建表的时候就指定,不能频繁修改。值得注意的是,列族的数量越少越好,因为过多的列族相互之间会影响,生产环境中的列族一般是一个到两个。

数据的持久化文件HFile中是按照Key-Value存储的,同一个列族的所有列存储在同一个底层存储文件里。Hbase的数据在HDFS中的路径结构如下:

hdfs://h201:8020/hbase/data/${名字空间}/${表名}/${区域名称}/${列族名称}/${文件名}

举例:/hbase/data/ns1/t1/a4d63a61a8da24a863bff3c8d7cd20de/f1/c2a7fa8c41304b9e9b8b24b4a89171ce

其中{区域名称}是t1的region, 由每张表切割形成,一张表由若干个region组成,不同的region分到不同的region server以便均衡负载

 

(2) 列column:和列族的限制数量不同,列族可以包含很多个列,前面说的“几十亿行*百万列”就是这个意思。 

(3) 列的值value:存在单元格(cell)中。每一列的值允许有多个版本,由timestamp来区分不同版本。多个版本产生原因:向同一行下面的同一个列多次插入数据,每插入一次就有一个对应版本的value。

从以下示例中可以看出habse存储的数据格式

hbase(main):010:0>scan 'ns1:t1',{STARTROW => 'row1', LIMIT => 5}

ROW                           COLUMN+CELL                                                                                                                        

row10                         column=f1:age,timestamp=1490608685532,value=\x00\x00\x00\x0A

row10                         column=f1:id,timestamp=1490608685532,value=\x00\x00\x00\x0A

row10                         column=f1:name,timestamp=1490608685532,value=tonykidkid10

row100                        column=f1:age,timestamp=1490608685532,value=\x00\x00\x00\x00

row100                        column=f1:id,timestamp=1490608685532,value=\x00\x00\x00d

row100                        column=f1:name,timestamp=1490608685532,value=tonykidkid100

row1000                       column=f1:age,timestamp=1490608685532,value=\x00\x00\x00\x00

row1000                       column=f1:id,timestamp=1490608685532,value=\x00\x00\x03\xE8

row1000                       column=f1:name,timestamp=1490608685532,value=tonykidkid1000 

row1001                       column=f1:age,timestamp=1490608685532,value=\x00\x00\x00\x01

row1001                       column=f1:id,timestamp=1490608685532,value=\x00\x00\x03\xE9

row1001                       column=f1:name,timestamp=1490608685532,value=tonykidkid1001 

row1002                       column=f1:age,timestamp=1490608685532,value=\x00\x00\x00\x02

row1002                       column=f1:id,timestamp=1490608685532,value=\x00\x00\x03\xEA

row1002                       column=f1:name,timestamp=1490608685532,value=tonykidkid1002

5 row(s) in0.0550 seconds

以row1002这一条记录来说明——

         row1002是row key .row key在hbase里是唯一的,而且只出现一次,否则就是在更新同一行。也就是说有几个不同的row key就有几条不同的记录。我们可以通过不同的行健来增加多行记录。行健的唯一性这个特性类似于关系型数据库的主键。

column=f1:name, timestamp=1490608685532,value=tonykidkid1002表示列族f1包含name列,“列族+列名”决定了不同的列。

Timestamp是时间戳,表示此列对应值的版本, 默认VERSIONS=1,value就是列族f1下name的值了。

需要明确的一点,hbase是通过3个维度来对记录进行快速定位:行健 + (列族+列名) + 时间戳,即:

                   row key àcolumn family + qualifier à timestamp

结合上面的例子,t1表的每一行有3个column, 分别是age,id, name.  比如我想查rowkey为row1002的name的值,命令行下的查询语法:

hbase(main):021:0>get 'ns1:t1', 'row1002' ,'f1:name'

COLUMN                 CELL                                                          

 f1:name               timestamp=1490608685532,value=tonykidkid1002                 

1 row(s) in0.0420 seconds

         或者这样查也是对的:

hbase(main):022:0>get 'ns1:t1', 'row1002' , {COLUMN => 'f1:name'}

COLUMN                 CELL                                                           

 f1:name               timestamp=1490608685532,value=tonykidkid1002                 

1 row(s) in0.0300 seconds

二、Hbase中的行健Row Key

我们知道HBase是采用Key-Value格式来存储数据,那么Row key就是Key-Value中的Key了,key不能重复,所以表示唯一一行。

Rowkey是表记录在hbase表中的唯一标识,作为检索表记录的唯一“主键”。hbase加载数据时,也是根据row key的二进制顺序由小到大进行的。

Row key的最大长度为64KB,它是一段二进制码流(byte[ ]),所以任何数据类型都可以用来做row key,内容可以由我们用户自定义、自设计。

HBase根据Row key来进行检索,系统通过找到某个Row key (或者某个 Row key 范围)所在的Region,然后将查询数据的请求路由到该Region获取此条记录。HBase的检索有3种方式:

1,通过get方式,指定rowkey获取唯一一条记录

2,通过scan方式,设置起始行和结束行参数进行范围匹配

3,全表扫描,即直接扫描整张表中所有行记录

Hbase对记录的排序row key可以是任意的字节数组byte []

row key按照字典顺序排序的规则:在字典顺序中按照二进制逐个字节、从左到右对比每一个row key,例如row1001小于row1002,rowxxa小于rowxxb等等。这种设计优化了scan,可以将相关的行以及会被一起读取的行存在相近位置,便于scan。

三、Hbase中的 “热点”问题

hbase中的热点现象:

我们知道,检索habse的记录首先要通过row key来定位数据行。当大量的client访问hbase集群的一个或少数几个节点,造成少数region server的读/写请求过多、负载过大,而其他region server负载却很小,就造成了“热点”现象。

热点的危害:

大量访问会使热点region所在的单个主机负载过大,引起性能下降甚至region不可用。

热点产生原因:

有大量连续编号的row key  ==>  大量row key相近的记录集中在个别region

 ==>  client检索记录时,对个别region访问过多  ==>  此region所在的主机过载

 ==>  热点

明白了热点原因就可以从row key着手解决,下面几个方法可以使用,目的就一个:尽量均衡地把每一条记录分散到不同的region里去!

下面是一些常见的避免热点的方法以及它们的优缺点:

加盐

       这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。给多少个前缀?这个数量应该和我们想要分散数据到不同的region的数量一致(类似hive里面的分桶)。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点。

哈希

       哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据。

反转

       第三种防止热点的方法是反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。

反转rowkey的例子:以手机号为rowkey,可以将手机号反转后的字符串作为rowkey,从而避免诸如139、158之类的固定号码开头导致的热点问题。

时间戳反转

       一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为rowkey的一部分对这个问题十分有用,可以用Long.Max_Value - timestamp追加到key的末尾,例如[key][reverse_timestamp] ,[key] 的最新值可以通过scan [key]获得[key]的第一条记录,因为HBase中rowkey是有序的,第一条记录是最后录入的数据。

 

尽量减少行和列的大小

       在HBase中,value永远和它的key一起传输的。当具体的值在系统间传输时,它的rowkey,列名,时间戳也会一起传输。如果你的rowkey和列名很大,HBase storefiles中的索引(有助于随机访问)会占据HBase分配的大量内存,因为具体的值和它的key很大。可以增加block大小使得storefiles索引再更大的时间间隔增加,或者修改表的模式以减小rowkey和列名的大小。压缩也有助于更大的索引。

其他办法    

列族名的长度尽可能小,最好是只有一个字符。冗长的属性名虽然可读性好,但是更短的属性名存储在HBase中会更好。也可以在建表时预估数据规模,预留region数量,例如create 'myspace:mytable’, SPLITS => [01,02,03,,...99]

总结一下,row key的设计原则应当遵循以下几点

(1)rowkey唯一原则

必须在设计上保证其唯一性,rowkey是按照二进制字节数组排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。所以设计rwo key时尽量把体现业务特征的信息、业务上有唯一性的信息编进row key。

(2)rowkey长度原则

rowkey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100byte,以byte[] 形式保存,一般设计成定长。建议越短越好,不要超过16个字节,2个原因——

原因1:

数据的持久化文件HFile中是按照(Key,Value)存储的,如果rowkey过长,例如超过100byte,那么1000万行的记录计算,仅row key就需占用100*1000万=10亿byte,近1Gb。这样会极大影响HFile的存储效率!

原因2:

MemStore将缓存部分数据到内存,若 rowkey字段过长,内存的有效利用率就会降低,就不能缓存更多的数据,从而降低检索效率。

目前操作系统都是64位系统,内存8字节对齐,控制在16个字节,8字节的整数倍利用了操作系统的最佳特性。

(3)rowkey散列原则

如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。

猜你喜欢

转载自blog.csdn.net/wyqwilliam/article/details/82504184
今日推荐