HBase是Apache Hadoop生态系统中的重要一员,它的海量数据存储能力,超高的数据读写性能,以及优秀的可扩展性使之成为最受欢迎的NoSQL数据库之一。它超强的插入和读取性能与它的数据组织方式有着密切的关系,在逻辑上,HBase的表数据按RowKey进行字典排序, RowKey实际上是数据表的一级索引(Primary Index),由于HBase本身没有二级索引(Secondary Index)机制,基于索引检索数据只能单纯地依靠RowKey。也只有使用RowKey查询数据才能得到非常高的效率。当然,HBase也支持使用其他的字段进行查询,但是只要没有RowKey,那么都是全表扫描。试想一下,在数十亿数据中全表扫描是一种什么样的体验,查询几乎不可用。而作为数据库使用,在数据表上的多条件查询是必然的需求,本文将结合使用经验,介绍一些常规的HBase的多条件查询实现方式。

 

RowKey + Filter的方式

RowKey一般是必不可少的,但是如果数据量少,几十万数据,就问题不大。很多时候查询都会选择时间,如果能把时间放在RowKey里面,会极大的提升查询的效率。这里有个小技巧:如果Rowkey是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将Rowkey的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个Regionserver实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息将产生所有新数据都在一个RegionServer上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别RegionServer,降低查询效率。

HBase的Scan可以通过setFilter方法添加过滤器(Filter),这也是分页、多条件查询的基础。HBase为筛选数据提供了一组过滤器,通过这个过滤器可以在HBase中的数据的多个维度(行,列,数据版本)上进行对数据的筛选操作。通常来说,通过行键,值来筛选数据的应用场景较多。这里简单举个例子,使用SingleColumnValueFilter过滤行,查找数据库中vehicle_speed列是77的数据:

FilterList filterList = new FilterList();
SingleColumnValueFilter scvf = new SingleColumnValueFilter(Bytes.toBytes("f"), Bytes.toBytes("vehicle_speed"), CompareOp.EQUAL, Bytes.toBytes("77"));
filterList.addFilter(scvf);
scan.setFilter(filterList);
ResultScanner scanner = table.getScanner(scan);

Filter是可以加多个的,HBase提供十多种Filter类型。filterList.addFilter(scvf) 就是可以添加多个查询条件,然后调用setFilter函数给Scanner。

这里再简单介绍一下分页的方式:

  • client分页,scan查到N*M条,过滤掉N*M-M条,返回M条。对于M,N较小时比较适合。
  • 自定义Filter,该filter可以传递offset(server端需要过滤的记录条数),在server端分页,注意,跨不同的region时需要重新计算该offset
  • 缓存上次分页查询的最后一条,下次分页查询从这条(不包含)开始查。
  • 查询条件固定的话,定时任务汇总表
  • PageFilter

使用RowKey + Filter的方式只能满足一些查询(数据量少,或者RowKey是必须的参数),包括其分页的实现并不是最优,但这是使用原生的HBase的方法,比较简单。下面介绍的方法更好,但是依赖于其他的组件。

 

Coprocessor

利用Coprocessor协处理器,用户可以编写运行在 HBase Server 端的代码。HBase的Coprocessor分为两类,Observer和EndPoint。

HBase 支持两种类型的协处理器,Endpoint 和 Observer。Endpoint 协处理器类似传统数据库中的存储过程,客户端可以调用这些 Endpoint 协处理器执行一段Server 端代码,并将 Server 端代码的结果返回给客户端进一步处理。

另外一种协处理器叫做Observer Coprocessor,这种协处理器类似于传统数据库中的触发器,当发生某些事件的时候这类协处理器会被 Server 端调用。Observer Coprocessor 就是一些散布在 HBase Server 端代码中的 hook 钩子,在固定的事件发生时被调用。比如:put 操作之前有钩子函数 prePut,该函数在 put 操作执行前会被 Region Server 调用;在 put 操作之后则有 postPut 钩子函数。

使用Coprocessor来实现简单的HBase二级索引也是比较常见的方案。但是如果要使用Coprocessor进行二级索引的话,还是推荐下面成熟的方案,它其中也使用到了协处理器。

 

Phoenix

最早由Salesforce.com开源的Apache Phoenix 是一个Java中间层,可以让开发者在Apache HBase上执行SQL查询,目前的版本基本支持常用的操作(分页,排序,Group By,Having,函数,序列等等)。目前的Phoenix是非常成熟的解决方案,阿里、Salesforce、eBay等互联网都在广泛使用。

Phoenix完全使用Java编写,代码位于GitHub上,并且提供了一个客户端可嵌入的JDBC驱动。它查询的实时性非常高,一般查询都在秒级返回,可以应用OLTP的系统中。在用户必须通过Phoenix来建HBase的表,它会映射到HBase的表上。Phoenix可以创建索引来提升提升多条件查询HBase的效率。比如,在查询订单的时候,可以通过订单号、时间、状态等不同的维度来查询,要想把这么多角度的数据都放到RowKey中几乎不可能。而在Phoenix中,你可以针对这几个字段建立索引。在写SQL语句的时候,如果Where语句中使用到了这些条件,Phoenix就会自动判断是否走索引。

Phoenix的索引本质上也是一张HBase的表,它维护了索引和RowKey的关系。在查询的时候,它会从索引表中先找到RowKey,然后再根据RowKey再去HBase原始数据表中获取数据。关于Phoenix的二级索引在后续的文章中专门介绍。

 

Impala

Impala是Cloudera在受到Google的Dremel启发下开发的实时交互SQL大数据查询工具,Impala没有再使用缓慢的Hive+MapReduce批处理,而是通过使用与商用并行关系数据库中类似的分布式查询引擎(由Query Planner、Query Coordinator和Query Exec Engine三部分组成),可以直接从HDFS或HBase中用SELECT、JOIN和统计函数查询数据,从而大大降低了延迟。

Impala目前是Apache的孵化项目。Impala并非是一个OLTP系统,而更像是一个OLAP系统,更加类似于Hive。Impala不能运用在实时系统中,但是如果是针对HBase的统计或者异步查询的话不妨一试。

 

ElasticSearch/Solr + HBase

针对HBase使用RowKey访问超高的效率,我们可以把索引数据放在类似于ElasticSearch或者Solr这样的搜索引擎里面。用搜索引擎做二级索引。查询数据的时候先从搜索引擎中查询出RowKey,然后再用RowKey去获取数据。流行的搜索引擎基本可以满足查询的所有需求。

举个例子:订单数据项有10个,但是用于查询的有5个。当数据插入HBase的同时,也把这5个数据项加上预先生成的RowKey插入搜索引擎,也就是说部分数据存储两份。一份用于搜索,一份用于查询。大致的架构也许会是这样:

image

程序A和B分开主要是为了解耦和避免互相影响,当然也可以合并在一个程序里面。程序A和B也可以是类似于flume或者logstash这样的组件。

 

一些建议

在作者的实际经验中方案的选择还是要根据数据量和性能要求来选择。当数据量较小几十万,上百万的话可以使用RowKey+Filter的方式实现。如果数据量到了千万,甚至亿级别,可以尝试Phoenix。如果数据量到了10亿或者更多则需要选择搜索引擎。同时方案的系统维护难度和对技术的要求也是逐级递增的。

posted on 2017-05-09 22:04  nick hao  阅读(17746)  评论(0编辑  收藏  举报