Hbase生产实践

                                     Hbase生产实践

背景

HBase是一个分布式的、面向列的开源数据库,它是hadoop生态圈的一员,有海量数据存储能力,对资源的消耗也相对较小,但同时查询能力也有局限,因此如何正确的使用hbase非常关键。

关于hbase

一个表可以有上亿行,上百万列

面向列(族)的存储和权限控制,列(族)独立检索。

对于为空(null)的列,并不占用存储空间,因此,表可以设计的非常稀疏。

选择完成时,被选择的列要重新组装

INSERT/UPDATE比较麻烦

Hbase基本概念

Region是HBase中分布式存储和负载均衡的最小单元。不同Region分布到不同RegionServer上,但并不是存储的最小单元。

Region由一个或者多个Store组成,每个store保存一个columns

family,每个Strore又由一个memStore和0至多个StoreFile 组成。memStore存储在内存中, StoreFile存储在HDFS上。

HBase通过将region切分在许多机器上实现分布式。也就是说,你如果有16GB的数据,只分了2个region, 你却有20台机器,有18台就浪费了。

数目太多就会造成性能下降,现在比以前好多了。但是对于同样大小的数据,300个region比1000个要好。

数目太少就会妨碍可扩展性,降低并行能力。有的时候导致压力不够分散。这就是为什么,你向一个10节点的HBase集群导入200MB的数据,大部分的节点是idle的。

RegionServer中1个region和10个region索引需要的内存量没有太多的差别。

关于family和column

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

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

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

结合车联网业务,family的设计一般按照不同的业务数据和获取频次进行设计,总的family个数不超过3个为最好,同时将经常读取的数据归类到一个family。

关于rowkey

rowkey是hbase的唯一id,也是hbase查询的主要途径,既然HBase是采用KeyValue的列存储,那Rowkey就是KeyValue的Key了,表示唯一一行。Rowkey也是一段二进制码流,最大长度为64KB,内容可以由使用的用户自定义。数据加载时,一般也是根据Rowkey的二进制序由小到大进行的。

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

(1) 通过单个Rowkey访问,即按照某个Rowkey键值进行get操作,这样获取唯一一条记录;

(2) 通过Rowkey的range进行scan,即通过设置startRowKey和endRowKey,在这个范围内进行扫描。这样可以按指定的条件获取一批记录;

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

HBASE按单个Rowkey检索的效率是很高的,耗时在1毫秒以下,每秒钟可获取1000~2000条记录,不过非key列的查询很慢。

那么rowkey该如何设计?

Hbase是按ASCII码排序的,其排序规则类似于字典序,例如一个AAA_BBB_CCC的rowkey可以支持以AAA,AAA_BBB,AAA_BBB_CCC的范围进行查询,但是不能以AAA__CCC或者_*_CCC这样的顺序进行查询(*代表缺失)。

结合我们的场景,如果要查询一个用户一段时间内的数据,其rowkey应该这样设计,即USER_(Long.Max-TIME)_EVENT,某个USER的数据会按时间倒序插入hbase,最新的数据会排在最前面,能很容易的找到最新的数据,rowkey上的EVENT是为了避免同一时间会有不同的事件上报被覆盖,但EVENT不能用于rowkey查询,因为TIME是在不断变化的。

这种rowkey的设计其查询方式可以有:

(1)  查询USER的所有数据,即rowkey范围为{USER_,USER`}

(2)  查询USER一段时间内的数据,即rowkey范围为{USER_( Long.Max-ENDTIME), USER_( Long.Max-STARTRIME)

(3)  查询USER一段时间内的符合条件的数据,rowkey按照(2)的方式生成,结合filter

region预分区和写热点

上面提到rowkey设计,我们把USER放在rowkey的首位,假设采用hbase默认的策略,即建表初始化一个region,默认达到10G进行拆分,在region分裂之后,不同的USER就会分别写到不同的region里面去,达到并行写的目的。但是如果采用(Long.Max-TIME)_USER_EVENT的rowkey方式,region拆分后,后续的数据由于时间越来越来,(Long.Max-TIME)越来越小,数据就只会写到范围小的region里面,出现写热点问题,所以必须合理的设计rowkey以达到提高写性能。

Hbase默认是一个表一个region,在region未拆分之前,所有的数据都会往一个region里面写,即使第一次拆分之后也还是只有2个region,这样会导致region分布不均,有些节点没有工作,浪费资源。这时候我们需要考虑region预分区,即一开始就创建足够的region,每个region划分一个rowkey范围。一般我们会按照10进制00000000-FFFFFFFF划分16个region,如果服务器较少,可以自己制定预分区策略。

Hbase1.x之后,默认的region拆分策略是按照region大小拆分的,早期版本是按照128*(2的n次方)进行拆分的,region拆分的太多不利于regionserver管理,拆的太少不利于数据写入,具体要根据业务量来制定。

关于filter

顾名思义,filter就是过滤器,filter的作用域是单个region,也就是说filter会在每个region上面独立生效,当一个用户的数据跨了几个region之后,而查询的范围又包含这几个region,如果使用pagefilter分页,就会返回region个数*分页条数的数据量。

Filter可以减少io开销,返回我们所需要的数据,但是如果和scan结合使用需要指定rowkey范围,scan是扫描表,没有rowkey范围就会扫描所有的region,性能非常差。

关于二级索引

由于hbase的rowkey有一定的局限,当查询条件不在rowkey上,或者不是按照rowkey的组装顺序时,无法通过rowkey来快速找到对应的region和Strore。这时候需要考虑二级索引,二级索引的方式有多种,以下介绍2种方式:

(1)  通过多张hbase表,一张表存数据,另一张表建立索引和数据表进行关联,写数据的时候同时写两张表。

(2)  通过协处理器在put之前建立二级索引,同时写索引表。

(3)  结合elasticsearch,hbase存原始数据,elasticsearch建立索引。

关于protobuf

  在实际业务中,数据通常会比较复杂,一个用户或者设备会有各种嵌套的属性,数据在写入hbase的时候可以以json的方式,也可以以protobuf的方式,这里建议以protobuf的方式,protobuf在序列化和反序列化性能方面比json效率高,同时占用空间也比json要少。

应用实践规范和注意事项

设计合理的表名,表名不要太长,自注释的表名最好。

创建表时指定压缩方式,建议采用LZO,snappy压缩。

合理安排family,个数不要超过3个,访问频繁和访问关联的数据放在相同的family中。

合理的设计rowkey,避免以自增长的数据作为rowkey开头,如果需要,可以hash之后再保存,但hash之后rowkey的顺序(比如时间序)已经无法保证了。

尽量简化封装,避免使用反射等操作,采用原生api最佳。

数据结构为一对多场景,即一个user对应多个属性,采用protobuf进行编码,将多个信号序列化为一个字段。

Hbase连接采用长连接方式。

由于数据上报频次高,数据量大,采用批量异步保存方式进行保存。利用kafka的特性,一次性拉取一定条数的数据,将这一批数据提交到线程池,批量保存到hbase。

注意不同的hbase版本,zookpeer中的hbase的根节点不同(原生的hbase节点为/hbase,hdp中为/hbase_unsecure)。

你可能感兴趣的:(Hbase生产实践)