Spark SQL在HBase的查询性能优化

云湖湖导读:

Spark与HBase是当今非常火的两个大数据开源项目,一个负责数据的分析处理,一个负责数据的存储。

近年来,Spark on HBase尤其是Spark SQL on HBase成为许多企业云上大数据与AI解决方案的首选。两者的结合,不仅兼顾了计算与存储,还兼顾了易用与性能。本文将会通过以下几点来分享:

1、什么是HBase

2、华为云DLI在Spark SQL on HBase的项目实践

3、查询性能优化思路

4、深度优化:Rowkey的区间范围分析

更多优质内容请关注微信公众号“智能数据湖”

1、什么是HBase

Apache HBase是一个开源的,分布式的,版本化的非关系数据库,其目标是在大型商业硬件集群上能托管非常大的表与数十亿行*百万列的数据。

HBase支持海量列式稀疏数据存储、极易扩展、高并发等特性,HBase有着弹性、灵活、毫秒级点查等优势,它的出现打破了传统数据库存储的界限,使得大数据技术得到了更进一步的发展。

接下来介绍几个会在实践中应用到的基本概念,来重点了解HBase。

1.1Rowkey

Rowkey是HBase中的一级索引,概念和MySQL中的主键是完全一样的,HBase使用Rowkey来唯一区分某一行的数据。

Rowkey是二进制存放数据的,数据根据字节递增排序,且不重复。比如,将数据(”1″, “2”, “11”, “a”, “A”, “b”, “abc”)作为Rowkey存放在HBase中的一张表中,Rowkey是这样排序的:

《Spark SQL在HBase的查询性能优化》

由于每个字节的范围是0x00~0xFF,0xFF相当于数字-1,因此,在字节的世界里面,0是最小的,-1是最大的。

1.2Region

Region的概念和关系型数据库的分区或者分片差不多。HBase会将一个大表的数据基于Rowkey的不同范围分配到不同的Region中,每个Region负责一定范围的数据访问和存储。这样即使是一张巨大的表,由于被切割到不同的Region,访问起来的时延也很低。

1.3HBase Table

HBase Table会根据数据大小拆分成多个Region存储在不同的Region server中,每个Region在Region server中又根据不同的列簇拆分成不同的HFile文件存放到HDFS中。

并发读取HBase数据,核心点就是Region,它是根据Rowkey进行拆分的,每个Region中的Rowkey都是连续的。在RDD中的getPartition接口里面,最简单的方式就是根据Region的个数来设置并发。

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》 图 Spark并发访问HBase Table

在HBase中Region的描述类TableRegionModel中,有getStartKey与getEndKey两个方法,而在HBase Client中的Scan里面也有setStartRow与setStopRow两个方法。所以,我们可以先获取目的表的Region信息,拿到每一个Region的StartKey与EndKey,并且set到Scan里面来指定HBase表的查找范围。

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》 图 Spark Datasource查询HBase时序图

2、华为云DLI在Spark SQL on HBase的项目实践

2.1场景描述

这是一个车联网的场景,用户可通过输入车辆的id和查询的时间范围来查找规定时间内的车辆信息(比如运行轨迹、车况等等)。由于车辆信息指标项庞大并且各种不同型号的车辆指标项存在差异,因此需要使用适合大宽表与稀疏列存储的HBase来作为该场景解决方案。

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》 图 车联网场景Spark on HBase解决方案的流程图

灰色部分是数据的采集和存储,使用了实时大数据服务。黄色部分是对存储在HBase中的车辆信息历史数据进行查询与计算。

注:在代码解读中,提及的HBase对应华为云上CloudTable,Spark对应华为云上DLI。

2.2项目实践 on DLI

在华为云的解决方案中,黄色部分的Spark SQL模块对应的是华为云数据湖探索(DLI)产品,该企业通过DLI访问HBase数据库来查询相关车辆信息。看了上面场景的流程图之后,很多人会有疑问为什么接收指令后不能直接访问HBase,而需要通过Spark SQL进一步处理?这就来看看DLI于数据查询的优势。

DLI的优势

易用性

DLI封装了Spark SQL的API,并且进行了并发控制与计算下压。对于企业用户来讲,只需要会写SQL就能够做到访问HBase了,不用再过多的去考虑如何访问HBase,如何提高性能。

通用性

DLI的通用性源于云服务背景,除了要支持特定场景的需求,还必须能应用到其他企业项目场景,与不同系统兼容。

在Apache Phoenix中,组合Rowkey是使用一套自己的编解码的,但这里就会有一层限制,就是数据的写入和读取都必须使用Apache Phoenix,否则的话开发一套适配Phoenix编解码的模块代价是非常大的。

场景分析

从实践场景中的流程图中可以看到,数据的写入和读取是两套不同的系统;同时,业务沟通中了解到所需的Rowkey字段都是长度固定的String类型。

综合以上2点考虑,我们确定了两种使用RowKey的方式。

第一种是不限制类型与数据长度的单Rowkey方式:

CREATE TABLE [IF NOT EXISTS] TABLE_NAME (

ATTR1 TYPE,

ATTR2 TYPE,

ATTR3 TYPE)

USING CLOUDTABLE OPTIONS (

‘ClusterId’=’xx’,

‘ZKHost’=’xx’,

‘TableName’=’TABLE_IN_CLOUDTABLE’,

‘RowKey’=’ATTR1’,

‘Cols’=’ATTR2:CF1.C1, ATTR3:CF1.C2’);

第二种是支持String类型定长的组合RowKey:

CREATE TABLE [IF NOT EXISTS] TABLE_NAME (

ATTR1 String,

ATTR2 String,

ATTR3 TYPE)

USING CLOUDTABLE OPTIONS (

‘ClusterId’=’xx’,

‘ZKHost’=’xx’,

‘TableName’=’TABLE_IN_CLOUDTABLE’,

‘RowKey’=’ATTR1:2, ATTR2:10’,

‘Cols’=’ATTR2:CF1.C1, ATTR3:CF1.C2’);

PS:斜体的字段名字冒号后面跟着的数字是字符长度。在实际应用上,结合Spark SQL中的函数,就能够满足绝大部分的场景。

2.3性能优化问题

在一般的SQL on HBase项目中,对查询HBase的性能会做两点通用优化:根据HBase表的Region个数设置并发和过滤条件下压(SingleColumnValueFilter和RowFilter),但是这对查询性能并没有多大帮助。

HBase Table的Region个数设置并发

虽然根据Region个数起并发,但是HBase对于表的Region拆分拥有很高的门槛,一方面是默认情况下往往需要表的大小达到十几到几十G左右才进行自动拆分,每个Region实际上的数据量还是很大;另一方面是由于实际数据的影响,对Region的自动拆分经常会出现数据倾斜,部分Region拥有上百万条数据,而部分Region只有一两条数据。并且让用户手动拆分Region也是一个比较复杂的操作,因为很难评估拆分的点在哪里。这就导致了很多时候,数据量没有达到的情况下,只有一个并发task在执行读取任务。而在并发读取的时候,又因为数据倾斜,导致有些task很快就执行完成了,有些task又执行的特别慢。

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》 图 HBase自动拆分Region易导致数据分布不均

过滤条件下压

1、SingleColumnValueFilter是有一些作用的,但是由于HBase是根据RowKey进行索引的,如果没有确定RowKey,对列的过滤效果还是不够明显(二级索引除外)。

2、RowFilter也是没有明显的效果,因为在HBase中,即使带上了RowFilter,还是会进行全表扫描然后再执行过滤。唯一的作用是,相比较于将所有的数据读取到Spark中做过滤,RowFilter能使HBase中返回的数据量减小,这样减少了Spark与HBase两个组件之间的网络通信压力。

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》 图 不进行过滤,将全表数据拉回Spark会增加网络压力
《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》 图 RowFilter下压后,返回过滤后的数据能够减小网络压力

3、优化思路

3.1使用Get,对HBase读取优化

确定RowKey的时候使用Get进行点查。

用一个例子来进行说明:

CREATE TABLE test1 (ID INT, NAME STRING)

USING HBASE OPTIONS (

‘ZKHost’=’xx’,

‘TableName’=’TABLE_IN_CLOUDTABLE’,

‘RowKey’=’ID’,

‘Cols’=’NAME:CF1.C1);

上面SQL建了一张HBase关联表,有ID和NAME两个字段,其中ID是Rowkey。如果查询语句是“select * from test1 where id=1;”,那么在读取的时候,就只需要Get Rowkey等于1的那一行数据即可,而不用使用Scan + RowFilter了。

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》 图 Spark使用Get获取HBase数据示例

再进一步,如果查询语句是“select * from test1 where id=1 or id=2;”或者“select * from test1 where id in (1, 2);”,那么就只要起2个并发task,分别Get Rowkey等于1和Rowkey等于2的数据。

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》 图 Spark并发使用Get获取HBase数据示例

缩小RowKey的查询范围

我们来看一个组合RowKey的例子:

CREATE TABLE test2 (ID STRING, NAME STRING, AGE STRING, VALUE DOUBLE)

USING HBASE OPTIONS (

‘ZKHost’=’xx’,

‘TableName’=’TABLE_IN_CLOUDTABLE’,

‘RowKey’=’ID:2, NAME:4, AGE:2’,

‘Cols’=’VALUE:CF1.C1);

上面这张表,Rowkey由2个字节的ID,4个字节的NAME,2个字节的AGE拼接组成,一共有8个字节。如果需要查询“select * from test2 where id=’01’; ”,又要怎么做呢?

之前我们分析过RowKey是按照字节排序的,0x00是最小的,0xFF是最大的。那么,我们的查询范围其实已经缩小到了从

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》

这里面,01后面分别跟上了6个字节的0x00和0xFF代表了4个字节的NAME和2个字节的AGE。所以,在Scan的时候,我们只要设置startKey由‘01’加上6个字节的0x00,endKey由‘01’加上6个字节的0xFF就好,RowFilter都不需要了。

再举个例子,如果需要查询“select * from test2 where id=’01’ or id=’03’; ”或者”select * from test2 where id in (‘01’, ‘03’); ”的时候,就需要起两个并发,分别对startKey设置‘01’加上6个字节的0x00和‘03’加上6个字节的0x00,对endKey设置‘01’加上6个字节的0xFF和‘03’加上6个字节的0xFF就好。

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》 图 缩小Rowkey查询范围后Spark使用Scan获取HBase数据示例

另外,会有很多的特殊情况是需要考虑的,比如”select * from test2 where id=’01’ or name=’abcd’; ”,注意这里是用‘or’过滤条件来连接的。尽管,大多数情况下,这种属于写错的概率很高,但是对计算引擎的开发来讲,还是需要认真对待的。当然,这种情况下,只能进行全表扫描了,因为01abcdxx满足条件,01aaaaxx、02abcdxx、03abcdxx…都满足条件。如果满足id=’01’或者name=’abcd’的数据加在一起还是比较少的话,可以使用上RowFilter。在单RowKey的时候,也会有类似的情况。

最后再来看一个例子,“select * from test2 where id=’01’ and name=’abcd’ and age=’20’; ”。这种情况下,实际只是需要Get Rowkey的值为‘01abcd20’的数据。

于是,有很多适用的场景我们都能迎刃而解了:

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》

值得注意的是,Get的效率远远高于Scan,所以建议能用Get的时候就不要使用Scan了。

基于上面的单Rowkey与组合Rowkey的例子,我们能得到一个一致的结论:对where条件进行分析,判断出Rowkey的查询范围,尽量缩小它,并尽量使用Get。那么,如何对where条件进行分析呢?下面会给出一个方法。

4、深度优化:Rowkey的区间范围分析

4.1String类型与单RowKey的说明

对于String类型来讲,在SparkSQL中是允许做“大于”、“小于”的比较的,它所比较的是字典序,比如’1’ < ‘2’、‘11’ < ‘2’、‘a’ < ‘b’等等。

再来看一个单Rowkey的例子:

CREATE TABLE test1 (ID STRING, NAME STRING)

USING HBASE OPTIONS (

‘ZKHost’=’xx’,

‘TableName’=’TABLE_IN_CLOUDTABLE’,

‘RowKey’=’ID’,

‘Cols’=’NAME:CF1.C1);

这里的ID类型是String。如果遇到查询“select * from test1 where id > ‘01’; ”的时候,是否可以缩小Rowkey的查询范围呢?答案是肯定的。首先我们可以设置startKey为‘01’,虽然id=’01’的结果也会被获取到,但是无论是下压RowFilter还是在Spark中做过滤,都会将id=’01’的结果过滤掉,在大数据的场景下,多处理一条记录对性能的影响是微乎其微的。

再来谈一谈endKey。因为在Scan HBase表的时候可以不用设置Rowkey的startKey和endKey,如果startKey默认为空的话,Scan会从头开始扫描,endKey默认为空的话,Scan会一直扫描到结束,因此上面的例子其实完全可以不用设置endKey。

最后,再讨论一下关于ID是数值型的话,要如何处理。回到2.1中的例子,表test1中的字段ID类型为INT。那对于“id > 1”的条件判断,是需要多加注意的。因为在字节的世界里面,数字的大小是这样的(以单个字节为例):

0 < 1 < 2 < … < 127 < -128 < -127 < … < -2 < -1

所以,“id > 1”的区间范围应该是(注意是Int,4字节):

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》

既“1 < id < Int.MinValue(2147483648)”。

4.2过滤条件列表

在这里,我们来归纳一下能够进行分析Rowkey区间范围的条件。

单RowKey

以3.1中的表test1为例,ID是String类型。

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》 表 String类型的单Rowkey部分过滤条件对应的查询范围

组合RowKey

以3.2的表test2为例,’RowKey’=’ID:2, NAME:4, AGE:2’,ID、NAME、AGE 3个字段都是String类型。

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》 表 String类型组合Rowkey部分过滤条件对应的查询范围

其实除了以上列举的几个基本过滤条件以外,还有一些条件也是可以做区间范围分析的,比如“Not”条件,就是取相反的区间,而‘like’条件语句和前缀匹配(prefix)也是可以做分析的。本文就暂时介绍一些基本的过滤条件,对于其他的过滤条件,大家其实可以举一反三,在这里就不详细说明了。

4.3Spark中的Filter

在Spark SQL中有一个Filter类,是Spark中所有过滤条件的基类,它可以转换成各种不同的条件实例:

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》

基本的实现思路在上面代码的注释中已经说明了,遇到‘or’或者‘and’的表达式,就递归处理,遇到基本条件的时候就按照3.2中的列表设置startKey和endKey。

有了思路之后,具体的实现其实大家都已经了然了,各自都有各自的方式,条条大路通罗马,这里就不再做太详细的描述。

总结

在所有关于RowKey查询优化的场景中,中心思想就是缩小RowKey的查询范围。除此之外还有一些其他的手段,比如Region的数据合理分配、提高并发等等。本文中的优化手段虽然很大一部分是围绕着本文的车联网案例来的,但是优化思路还是可以应用到其他不同的场景中的。除了String加定长的这种数据格式,分隔符或者其他的数据格式都可以考虑这种优化手段。

《Spark SQL在HBase的查询性能优化》
《Spark SQL在HBase的查询性能优化》

    原文作者:云湖湖
    原文地址: https://zhuanlan.zhihu.com/p/59226170
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞