默认情况下,impalatable使用存储在HDFS中的数据文件,这种存储方式适用于批量数据加载和查询(bulk loads and query)。相反,HBase可以对用于OLTP风格的负载的数据进行高效率查询,比如查找单个row或者一个range的values。
对于impala用户来说,HBase是key-value存储形式的数据库,value包含多个fields。Key在impala table中被map到某一个column,而value的各个fields被map到impala table的其他columns。
使用impala withHBase时:
l 使用Hive shell创建table
A、可以使用STORED BY
‘org.apache.hadoop.hive.hbase.HBaseStorageHandler ‘语句
B、直接把impala table map到一个已有的HBase table中
l Map到HBase row key的impala column必须是string类型的
l 由于impala和hive共享metastore,一旦在hive中创建table后,在impalashell中使用INVALIDATE METADATA语句刷新即可看到新的table
l 在impala中查询HBase数据时,尽量使用WHERE从句定位单个key或者一个key range,这样可以提高查询效率,全表scan对于HBase效率很低
确保impala user具有对HBasetable的read/write权限。
HBase在Impala Box之外工作,没有强制需要的配置。
为了避免在HBase不可用的情况下,impala启动或者更新元数据的延迟,Cloudera建议设置timeoutvalue在/etc/impala/conf/hbase-site.xml中(在非Cloudera Manager的环境下才需要):
<property> <name>hbase.client.retries.number</name> <value>3</value> </property> <property> <name>hbase.rpc.timeout</name> <value>3000</value> </property>
目前,ClouderaManager并不提供仅针对impala的HBase特有配置文件,所以你在Cloudera Manager中进行的任何的HBase配置更改都会在所有的HBase applications中生效。因此,这个timeout配置不建议在cloudera manager中设置。
为了弄清Impalacolumn数据如何映射到HBase中的字段(field),你应该有一些关于HBase的背景知识。在Hive shell中利用CREATE TABLE语句设置映射关系。见theHive wiki作为起点,Examples of Querying HBase Tables from Impala作为例子。
HBase作为一种“bitbucket”进行工作,它并不强制要求对key或者value字段输入值,所有的强制输入都是在Impala这边进行的。
为了在impala查询HBase时获得最好的性能,大多数查询会在WHERE中的column对应的row key进行比较操作。当在Hive shell中创建table时,把映射为HBase row key列的那一列设置为STRING类型。Impala可以把针对某个column的条件测试(例如操作符=, <, BETWEEN, 和IN)翻译成HBase中的快速查找,但是这个优化(predicate push down即谓词下推)仅当列为STRING类型时才有效。
从Impala 1.1开始,Impala也支持读写在HiveCREATE TABLE语句中定义的二进制类型的列,即在Hive table中使用#binary定义(通常简写为#b)。定义数值column为二进制类型通常可以降低其在HBase table中的空间占用。
切记Row key列定义成string类型的,这样可以进行快速查找。其他列可以为binary类型的,这样可以节省存储空间。
为了理解HBase上进行SQL查询的性能特点,你应该有一些相关的背景知识。可以以 the Hive wiki为起点,因为Impala与Hive共享同一个metastore,所以Hive table到HBase table的列映射信息也适用于Impala。
Impala使用HBaseclient API通过JNI来查询HBase的数据。查询不直接读HFiles。额外的通信开销使得选择将数据存储在HBase还是HDFS变得很重要,同时构造高效的查询可以高效地获取HBase数据也变得很重要。
l 使用HBase table用来进行singlerow或者一个range of rows的查询,而不是scan entire table的query(如果query中不包含WHERE从句,说明很可能它对于HBase table是低效率的)
l 如果你做一个join查询,在一个大的facttable上做汇总操作,然后将结果与一个小维度的table进行join操作,考虑使用Impala存储fact table,并且HBase存储这个小维度table(因为Impala在这种情况下会对HBase table进行全表scan,而不是做single-row的HBase查找,基于这个join column,只有在HBase table足够小的情况下,全表扫描才不至于时间过长,这样才不会出现查询性能瓶颈)
Query predicates(谓词、判断)用来表示row key的start和stop key,从而限制了lookup操作的scope。如果row key不对应string类型的列,那么通常是无法正确排序的,因为comparison操作无法正常进行。
Non-key列的谓词判断被发送到HBase作为SingleColumnValueFilters进行scan,提供一些性能提升。这种情况下,HBase比在impala中使用相同的谓词返回更少的行(?这句没看懂)。尽管non-key列谓词的使用会有一些性能提升,但是这种提升与使用row-key谓词的情况相比还是微不足道的。因为这种情况下,HBase要扫描的总行数依然是没有限制的。只要有row key的predicate,那么HBase就能快速定位并返回那一行,相反的是,如果只有non-key的predicate,那么即使查询结果只有一行,HBase也要进行全表scan。
例如,这有一些针对Impalatable(已经映射到HBase table)的查询。例子中展示了除了EXPLAIN语句的输出,还可以看到根据哪些信息可以预知该查询针对HBase table是否是高效的查询。
第一列(cust_id)在CREATEEXTERNAL TABLE语句中被指定为key列,将该列声明为STRING类型对于性能来说是很重要的;其他列例如BIRTH_YEAR, NEVER_LOGGED_ON也声明为STRING,而不是它们本来的INT和BOOLEAN类型,因为Impala可以在HBasetable中更高效地优化这些类型。为了比较,我们将YEAR_REGISTERED这列声明为INT类型,来展示针对这一列的filtering是低效的。
describe hbase_table; Query: describe hbase_table +-----------------------+--------+---------+ | name | type | comment | +-----------------------+--------+---------+ | cust_id | string | | | birth_year | string | | | never_logged_on | string | | | private_email_address | string | | | year_registered | int | | +-----------------------+--------+---------+
关于使用row key列等值比较条件进行单行查询是性能最好的例子:
explain select count(*) from hbase_table where cust_id = '[email protected]'; +------------------------------------------------------------------------------------+ | Explain String | +------------------------------------------------------------------------------------+ | Estimated Per-Host Requirements: Memory=1.01GB VCores=1 | | WARNING: The following tables are missing relevant table and/or column statistics. | | hbase.hbase_table | | | | 03:AGGREGATE [MERGE FINALIZE] | | | output: sum(count(*)) | | | | | 02:EXCHANGE [PARTITION=UNPARTITIONED] | | | | | 01:AGGREGATE | | | output: count(*) | | | | | 00:SCAN HBASE [hbase.hbase_table] | | start key: [email protected] | | stop key: [email protected]\0 | +------------------------------------------------------------------------------------+
另外一类高效查询是针对rowkey列的一个range查找,使用SQL操作符例如>, <, =, BETWEEN。下面例子也包好一个non-key列的等值test,因为这一列也是STRING类型。Impala可以HBase执行这个test,体现在hbase filter中(见下面的output),在HBase中进行filtering比将数据全部传给impala再在impala这边进行filtering更高效。
explain select count(*) from hbase_table where cust_id between 'a' and 'b'
and never_logged_on = 'true';
+------------------------------------------------------------------------------------+
| Explain String |
+------------------------------------------------------------------------------------+
...
| 01:AGGREGATE |
| | output: count(*) |
| | |
| 00:SCAN HBASE [hbase.hbase_table] |
| start key: a |
| stop key: b\0 |
| hbase filters: cols:never_logged_on EQUAL 'true' |
+------------------------------------------------------------------------------------+
这样的查询是低效的:如果Impala必须评估一些predicates,因为Impala必须scan整个HBase table。Impala只能把关于STRING类型column的predicate下推给HBase处理,而下例中是INT类型,故output中最下面的predicate:这一行表示这个等值test会在数据都传输给impala之后才能进行:(即explain输出的predicate:语句不会在HBase中执行,这一点与hbase filters、start key、stop key不同)
explain select count(*) from hbase_table where year_registered = 2010;
+------------------------------------------------------------------------------------+
| Explain String |
+------------------------------------------------------------------------------------+
...
| 01:AGGREGATE |
| | output: count(*) |
| | |
| 00:SCAN HBASE [hbase.hbase_table] |
| predicates: year_registered = 2010
这样的查询也是低效的:如果key列与任何非常量值进行比较。这里,即使key column是STRING类型的,并且使用=操作符,Impala也必须scan整个HBase table,因为key column是与另外一列的value进行比较,而不是一个常量:
explain select count(*) from hbase_table where cust_id = private_email_address;
+------------------------------------------------------------------------------------+
| Explain String |
+------------------------------------------------------------------------------------+
...
| 01:AGGREGATE |
| | output: count(*) |
| | |
| 00:SCAN HBASE [hbase.hbase_table] |
| predicates: cust_id = private_email_address |
+------------------------------------------------------------------------------------+
当前,针对row key的OR,IN语句test没有优化成直接的查找,这个限制未来可能会被解决。所以请每次check EXPLAIN的output来观察你的query是否是一个对于HBase table来说高效的查询。
explain select count(*) from hbase_table where cust_id = '[email protected]' or cust_id = '[email protected]'; +----------------------------------------------------------------------------------------+ | Explain String | +----------------------------------------------------------------------------------------+ ... | 01:AGGREGATE | | | output: count(*) | | | | | 00:SCAN HBASE [hbase.hbase_table] | | predicates: cust_id = '[email protected]' OR cust_id = '[email protected]' | +----------------------------------------------------------------------------------------+ explain select count(*) from hbase_table where cust_id in ('[email protected]', '[email protected]'); +------------------------------------------------------------------------------------+ | Explain String | +------------------------------------------------------------------------------------+ ... | 01:AGGREGATE | | | output: count(*) | | | | | 00:SCAN HBASE [hbase.hbase_table] | | predicates: cust_id IN ('[email protected]', '[email protected]') | +------------------------------------------------------------------------------------+
拆分成单个针对单行的查询,然后在application中合并结果,或者combine单行查询使用UNION ALL关键词:
select count(*) from hbase_table where cust_id = '[email protected]'; select count(*) from hbase_table where cust_id = '[email protected]'; explain select count(*) from hbase_table where cust_id = '[email protected]' union all select count(*) from hbase_table where cust_id = '[email protected]'; +------------------------------------------------------------------------------------+ | Explain String | +------------------------------------------------------------------------------------+ ... | | 04:AGGREGATE | | | | output: count(*) | | | | | | | 03:SCAN HBASE [hbase.hbase_table] | | | start key: [email protected] | | | stop key: [email protected]\0 | | | | | 10:MERGE | ... | 02:AGGREGATE | | | output: count(*) | | | | | 01:SCAN HBASE [hbase.hbase_table] | | start key: [email protected] | | stop key: [email protected]\0 | +-------------------------------------------
总结:
即尽量使用string类型的列,尽量使用WHERE限制key的范围,这样避免将所有数据传输到impala中进行查找。
Impala只能把针对STRING类型列的predicates下推到HBase中去,但是对于其他类型比如INT型的column的predicate,只能是在impala这边做过滤,所以必须对HBase进行全表scan。
如果key column不是和常量值进行比较,那么也是低效的,也要进行全表扫描,比如select count(*) from hbase_table where cust_id =private_email_address。
如果你有一个HBaseJava application调用了 org.apache.hadoop.hbase.client.Scan的setCacheBlocks或者setCaching方法,你也可以使用Impala查询参数来设置这些值来控制HBaseregion server的内存压力。例如,当在HBase中进行查询并导致全表scan是,你可以通过关闭HBASE_CACHE_BLOCKS设置并指定一个很大的值给HBASE_CACHING来降低内存占用并且加速查询。
设置这些参数,在impala-shell中执行如下命令:
-- Same as calling setCacheBlocks(true) or setCacheBlocks(false). set hbase_cache_blocks=true; set hbase_cache_blocks=false; -- Same as calling setCaching(rows). set hbase_caching=1000;
或者更新impalad的默认文件/etc/default/impala,并且设置HBASE_CACHE_BLOCKSand/or HBASE_CACHING in the -default_query_options setting forIMPALA_SERVER_ARGS。细节请参考Modifying Impala Startup Options。
Note:在Impala 2.0或者更新版本中,这些选项是可设置的,通过JDBC或者ODBC接口,使用SET语句。
Impala查询HBase table的通常情景:
l 在impala中保存大的facttables,在HBase中保存smaller dimension tables。Fact tables使用Parquet或者其他类型的二进制格式(针对scan操作进行优化的)文件存储。Join操作scan这个大的impala fact table,并且使用高效的single-row lookup来交叉引用HBase中的table。即大表和小表join时,用impala扫描大表,结果作为join过滤条件传给HBase,以快速定位对应的row。
l 使用HBase存储快速增长的counter数据,比如一个webpage已经被访问了多少次,一个user已经发起了多少次连接等。HBase对于捕捉这种变化无常的data是非常有效的,因为它的append-only存储机制对于把每个change写入disk非常高效,并且一个query总是返回最新的value。
l 在HBase中存储非常wide的table。Wide table可能有几千个columns,通常记录着某个项目的很多属性。这些table通常是sparse的,大部分列的值是NULL或者0、false、空字符串等。例如某个网站服务的用户作为一个row,他可能仅仅用过其中的几个服务,通常的query是:查找一个single row,提取出所有列的信息,而不是做sum、averge等impala中常见的操作。
比如impala table中存储着某个网站的流量信息(一张大表),impala选出了浏览该网站的50个user,现在如果要看这些user的属性的话,我们只需与HBase中存储的user table进行join,这样只需scan这50个rows,而不必扫描全表。
Impala中可以对HBase表insert单行,因为插入单行操作对于HBase本来就是有效的,但是对于存储在HDFS中的其他impala table,insert单行是不行的,因为这样会产生很多小文件,必须批量插入。
Impala中没有update语句,但是使用相同的rowkey进行insert时可以起到update的效果,因为相同的row key插入时会覆盖原来的value。
l Impala中的DROP TABLE语句执行后,HBase中的table没有remove,只是impala中的remove了
l Hive中支持INSERT OVERWRITE语句,可以清空整个table,然后插入新数据,但是impala里不支持对于HBase table的这类语句,你只能插入新行或者使用相同的row key更新原有行
l Impala中对HBase table执行CREATE TABLE LIKE语句时,在HBase中发生的是产生一个对旧表的别名,并没有完全复制出一个新表,所以应该避免使用此语句
l 在impala中使用INSERT…SELECT语句向HBasetable中插入数据时,首先插入的行数可能比SELECT出来的少,因为各个行可能有key列值相同的,那样就只会产生一列结果,其次,无法保证多个相同key的rows插入时,由于后来的row会替换已有row的值,这样就没法保证最终插入的row的值是最新的了,谨慎使用。
1、 首先在HBase中创建一个表,HBase中创建的table是“enabled”状态,在hbaseshell中dropping:他们之前必须执行disable ‘table_name’语句;
$ hbase shell ... create 'hbasealltypessmall', 'bools', 'ints', 'floats', 'strings' quit
2、在hive中创建外部表指向HBasetable,注意用来做key的列最好使用string类型,其他类型也可以,但是lookup的速度要慢很多,string最快;
下例中创建了一个外部表映射到hbase table中。由于是一个外部表,所以在impala或者Hive中drop之后,原始的hbase table并没有删除。STORED BY语句目前在Impala中还不支持,所以需要在Hive shell中使用CREATE TABLE语句执行。WITH SERDEPROPERTIED语句声明了第一列(id)代表row key列,并且映射其余列到HBase列簇中。
$ hive ... hive> CREATE EXTERNAL TABLE hbasestringids ( id string, bool_col boolean, tinyint_col tinyint, smallint_col smallint, int_col int, bigint_col bigint, float_col float, double_col double, date_string_col string, string_col string, timestamp_col timestamp) STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler' WITH SERDEPROPERTIES ( "hbase.columns.mapping" = ":key,bools:bool_col,ints:tinyint_col,ints:smallint_col,ints:int_col,ints:\ bigint_col,floats:float_col,floats:double_col,strings:date_string_col,\ strings:string_col,strings:timestamp_col" ) TBLPROPERTIES("hbase.table.name" = "hbasealltypessmall");
Note:在Hive中创建table之后,下次connectto impala时要执行INVALIDATE METADATA table_name语句,以便让impala知道这个新的table。
本例中定义lookupkey column为INT类型,而不是STRING类型。
Note:尽管这样定义可以,但是Cloudera强烈建议使用STRING类型作为key列,因为这样lookup操作更快。
再次,执行CREATETABLE语句在Hive中,然后切换到Impala和impala-shell中执行查询:
$ hive ... CREATE EXTERNAL TABLE hbasealltypessmall ( id int, bool_col boolean, tinyint_col tinyint, smallint_col smallint, int_col int, bigint_col bigint, float_col float, double_col double, date_string_col string, string_col string, timestamp_col timestamp) STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler' WITH SERDEPROPERTIES ( "hbase.columns.mapping" = ":key,bools:bool_col,ints:tinyint_col,ints:smallint_col,ints:int_col,ints:bigint_col,floats\ :float_col,floats:double_col,strings:date_string_col,strings:string_col,strings:timestamp_col" ) TBLPROPERTIES("hbase.table.name" = "hbasealltypessmall");
一旦建立了与HBasetable的映射关系,你就可以执行查询了。例如:
# if the row key is mapped as a string col, range predicates are applied to the scan select * from hbasestringids where id = '5'; # predicate on row key doesn't get transformed into scan parameter, because # it's mapped as an int (but stored in ASCII and ordered lexicographically) select * from hbasealltypessmall where id < 5;