Hive支持使用HDFS之外的存储系统作为底层存储系统,其中官方明确支持HBase,Kudu,Druid和JDBC(MySQL等)。
Hive提供了相应的接口 StorageHandlers,用以实现和其他存储系统的整合。本人整理的已有相应实现的其他存储系统有:Phoenix HBase,MongoDB,ElasticSearch等。
本文主要讨论Hive整合HBase在使用上的一些关键内容,更详细的内容请参考 HBaseIntegration 。
Hive整合HBase之后,可以通过Hive QL语法读写HBase表,并且可以和Hive的本地表进行join和union。
不过需要注意的是,虽然可以通过Hive QL读写HBase表,但是Hive QL操作是下推到HBase操作,所以在编写Hive QL时,需要依照HBase的数据操作思维,否则性能表现可能会比较差。
经过本人的简单测试,当通过Hive QL进行过滤操作时,使用HBase rowkey,性能表现很好,但是如果使用其他的列,性能表现非常糟糕。
StorageHandlers 是Hive提供的整合其他存储系统的接口,最初是 HBaseIntegration 的一部分,后面抽象出来,成为一个通用接口。
该接口主要规范了以下几个部分:
input formats
output formats
serialization/deserialization libraries
之后也可以实现 metadata hook 接口,允许Hive DDL管理目标存储系统。
这里有几个术语需要了解一下。
Hive有 managed
vs external
两种表,这里不做介绍。
StorageHandlers有两个新增的概念:native
vs non-native
。
一个 native
表是没有 storage handler 的表。
一个 non-native
表是带有 storage handler 的表。
当创建一个 non-native
表时,使用 STORED BY
子句指定。
需要注意,官网提到:
When STORED BY is specified, then row_format (DELIMITED or SERDE) and STORED AS cannot be specified.
我的理解是,STORED BY
与 ROW FORMAT and STORED AS
是二选一的,不能同时使用。
这个理解可能不对,因为在下文的HBaseIntegration基本使用中,出现了ROW FORMAT
和 STORED BY
同时存在的情况。
HBaseIntegration的实现是在Hive工程的一个子工程(hbase-handler)下,如果要使用这个特性,需要编译这个子工程,编译结果如 hive-hbase-handler-x.y.z.jar
,这个jar需要添加到Hive client auxpath中,并进行相应的配置,具体操作请看 Usage。
在CDH中,这个特性是直接可用的,不需要进行编译和配置。
创建一个被Hive管理的HBase表:
CREATE TABLE hbase_table_1(key int, value string)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,cf1:val")
TBLPROPERTIES ("hbase.table.name" = "xyz", "hbase.mapred.output.outputtable" = "xyz");
使用Hive访问已存在的HBase表:
CREATE EXTERNAL TABLE hbase_table_2(key int, value string)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" = "cf1:val")
TBLPROPERTIES("hbase.table.name" = "some_existing_table", "hbase.mapred.output.outputtable" = "some_existing_table");
hbase.columns.mapping
属性是必要的,用来映射HBase表列到Hive表列,下面重点说明。
hbase.table.name
属性是可选的,用来指定HBase表名,如果不指定,则Hive和HBase的表名将是一致的。
hbase.mapred.output.outputtable
属性是可选的,如果你打算插入数据到表,则使用这个属性指定HBase表名。
有两个 SERDEPROPERTIES
用来控制HBase列到Hive的映射:
hbase.columns.mapping
:指定HBase列,用于和Hive表列进行映射。
hbase.table.default.storage.type
:指定默认存储类型。可指定类型有两种,string
或 binary
,其中默认类型是string
。
当前对于列映射的支持有一些约束,在使用上需要注意:
hbase.columns.mapping
指定的列是一个以英文逗号分隔的字符串,空格会被认为是列名的一部分。
Hive表的列必须和 hbase.columns.mapping
指定的列一一对应。
hbase.columns.mapping
指定的每个映射列必须是 :key
,:timestamp
或者是 column-family-name:[column-name][#(binary|string)]格式
的一种。其中 :key
对应 rowkey
,:timestamp
对应 timestamp
,column-family-name:[column-name][#(binary|string)]格式
用来指定具体的列。#
用来指定列的类型,如果不指定列类型,则默认使用 hbase.table.default.storage.type
值,任何有效值的前缀是有效的,例如可以用 #b
代替 #binary
。
:key
必须是准确的,可能是一个字符串或者一个结构体。:key
可以不指定,这时Hive表第一列对应rowkey
,但是建议显式指定 :key
,因为在未来,将不再支持隐式映射rowkey
。
如果不指定列名,那么Hive表的相应列将映射HBase表整个列簇,Hive表的相应列必须是MAP类型,MAP的key的类型必须是string
。
从HBase 1.1开始,支持了使用 :timestamp
访问HBase timestamp,但是相应的Hive表列的类型需要是 bigint
或者 timestamp
。
在一个Hive表中,不必映射每个HBase列簇,但是未映射的列簇无法通过Hive表进行访问。可以将同一个HBase表映射成多个Hive表,这种做法在实际应用中或许会更方便。
在实际使用中,Hive本地表和Hive映射的HBase表可以进行一定程度的互操作。
例如从Hive中移动数据到HBase中:
INSERT OVERWRITE TABLE hbase_table_1 SELECT * FROM pokes WHERE foo=98;
对于Hive-HBase映射表,使用Hive或HBase都可以操作数据。
因为HBase的WAL,插入大量数据的时候,可能会比较慢,可以通过关闭WAL来提高写入速度,但是可能导致数据丢失。
关闭WAL的指令如下:
set hive.hbase.wal.enabled=false;
另外,HBase rowkey是唯一的,Hive映射表的操作也会遵循这个特性。
Hive有三列,HBase有两个列簇:
CREATE TABLE hbase_table_1(key int, value1 string, value2 int, value3 int)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES (
"hbase.columns.mapping" = ":key,a:b,a:c,d:e"
);
使用Hive MAP类型映射HBase列簇:
CREATE TABLE hbase_table_1(value map<string,int>, row_key int)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES (
"hbase.columns.mapping" = "cf:,:key"
);
value列映射了HBase cf列簇,每行可能有不同的列集。
需要注意,map
中,key的类型必须是string
,value的类型需要保持一致。
使用通配符可以匹配HBase列,实现在Hive中映射指定前缀的列集:
CREATE TABLE hbase_table_1(value map<string,int>, row_key int)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES (
"hbase.columns.mapping" = "cf:col_prefix.*,:key"
);
需要注意,map
中,key的类型必须是string
,value的类型需要保持一致。
有一个 hbase.columns.mapping.prefix.hide
属性,默认为false
,如果设置为 true
,则可以在查询结果中隐藏列前缀,示例如下:
CREATE TABLE hbase_table_1(tags map<string,int>, row_key string)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES (
"hbase.columns.mapping" = "cf:tag_.*,:key",
"hbase.columns.mapping.prefix.hide" = "true"
);
此时查询tags列时,结果如下:
"x": 1
如果未启用隐藏列前缀属性,查询结果应该如下:
"tag_x": 1
使用hbase.table.default.storage.type
默认值:
CREATE TABLE hbase_table_1 (key int, value string, foobar double)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES (
"hbase.columns.mapping" = ":key#b,cf:val,cf:foo#b"
);
指定hbase.table.default.storage.type
值:
CREATE TABLE hbase_table_1 (key int, value string, foobar double)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES (
"hbase.columns.mapping" = ":key,cf:val#s,cf:foo",
"hbase.table.default.storage.type" = "binary"
);
还可以使用Hive结构体映射rowkey,假设rowkey是由两个字符串拼接来的,连接符是~
,可以通过ROW FORMAT DELIMITED...COLLECTION ITEMS TERMINATED BY
拆分rowkey,示例如下:
CREATE EXTERNAL TABLE delimited_example(key struct<f1:string, f2:string>, value string)
ROW FORMAT DELIMITED
COLLECTION ITEMS TERMINATED BY '~'
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES (
'hbase.columns.mapping'=':key,f:c1');
还支持更多的使用方法,比如复杂复合Row Keys,HBase列使用Avro序列化类型等。
更多内容请参考 HBaseIntegration 。