简介
这篇文章最初是基于介绍HIVE-705。这个功能允许Hive QL命令访问HBase表,进行读(select)、写(insert)操作。它甚至可以基于join、union操作对hbase表和hive原生的表进行混合访问。
这个功能还在不断的完善中,欢迎提出建议。
在开始介绍之前,首先请阅读StorageHandlers,对存储处理程序的框架有个初步的认识,可以帮助读者理解HBase集成。
这个存储处理程序被编译成一个独立的模块, hive-hbase-handler-x.y.z.jar,必须与guava、zookeeper的jar包被hive的客户端程序识别到(通过--auxpath指定)。也需要正确的配置hbase master的ip地址,保证能够与hbase连接。启动HBase集群的方法,请见the HBase documentation。
这里有个例子是使用源代码编译环境的命令行,连接到一个单结点的HBase server。(注意,jar包的位置在hive 0.9.0版本以后发生了变化,对于早期的版本,要注意这些变化)
$HIVE_SRC/build/dist/bin/hive --auxpath $HIVE_SRC/build/dist/lib/hive-hbase-handler-0.9.0.jar,$HIVE_SRC/build/dist/lib/hbase-0.92.0.jar,$HIVE_SRC/build/dist/lib/zookeeper-3.3.4.jar,$HIVE_SRC/build/dist/lib/guava-r09.jar -hiveconf hbase.master=hbase.yoyodyne.com:60000
以下则是一个连接到一个分布式的HBase集群的例子,通过一个3结点的zookeeper集群来确定HBase的master结点:
$HIVE_SRC/build/dist/bin/hive --auxpath $HIVE_SRC/build/dist/lib/hive-hbase-handler-0.9.0.jar,$HIVE_SRC/build/dist/lib/hbase-0.92.0.jar,$HIVE_SRC/build/dist/lib/zookeeper-3.3.4.jar,$HIVE_SRC/build/dist/lib/guava-r09.jar -hiveconf hbase.zookeeper.quorum=zk1.yoyodyne.com,zk2.yoyodyne.com,zk3.yoyodyne.com
这个处理程序需要Hadoop 0.20版本以上,当前只测试过基于hadoop-0.20.x、hbase-0.92.0和zookeeper-3.3.4的情况。如果你不是使用hbase-0.92.0,你将需要重新编译与hbase的jar包相对应的handler处理程序,并且修改上文中提到的相应的--auxpath命令行参数。使用不对应的hbase jar包编译,将会导致连接错误异常,比如 MasterNotRunningException,这是因为hbase的RPC协议经常随版本更新发生变化。
为了创建一个新的可供hive管理的HBase表,需要使用“ STORED BY
clause on CREATE TABLE
”命令:
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.columns.mapping属性是必需的,将会被下一段的所解释。hbase.table.name属性是可选的,它控制了HBase中创建的表的名字,并且允许其与hive表使用不同的名字。在这个例子中欧,在hive中的表名是hbase_table_1,在HBase中,则被命名为xyz。如果没有特别标名,hive与hbase表将会使用相同的名字。
在以上命令执行之后,我们就能通过HBase的shell看到一个新的空表:
$ hbase shell
HBase Shell; enter 'help' for list of supported commands.
Version: 0.20.3, r902334, Mon Jan 25 13:13:08 PST 2010
hbase(main):001:0> list
xyz
1 row(s) in 0.0530 seconds
hbase(main):002:0> describe "xyz"
DESCRIPTION ENABLED
{NAME => 'xyz', FAMILIES => [{NAME => 'cf1', COMPRESSION => 'NONE', VE true
RSIONS => '3', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY =>
'false', BLOCKCACHE => 'true'}]}
1 row(s) in 0.0220 seconds
hbase(main):003:0> scan "xyz"
ROW COLUMN+CELL
0 row(s) in 0.0060 seconds
注意,即使在映射中配置了“val”这个列名,但是在hbase shell输出的描述中,只能看到列族(column family)的名字“cf1”。这是因为在HBase中,只有列族(而不是列)的名字会被保存在表级的元数据中;列族中的列名只能保存在底层的列数据中。
这里将介绍如何将数据从Hive中转移到HBase的表中(如果创建一个示例表pokes,可以参考GettingStarted):
INSERT OVERWRITE TABLE hbase_table_1 SELECT * FROM pokes WHERE foo=98;
使用HBase shell来验证一下数据是否被加载:
hbase(main):009:0> scan "xyz"
ROW COLUMN+CELL
98 column=cf1:val, timestamp=1267737987733, value=val_98
1 row(s) in 0.0110 seconds
通过hive进行检索的结果是:
hive> select * from hbase_table_1;
Total MapReduce jobs = 1
Launching Job 1 out of 1
...
OK
98 val_98
Time taken: 4.582 seconds
Inserting large amounts of data may be slow due to WAL overhead; if you would like to disable this, make sure you have HIVE-1383 (as of Hive 0.6), and then issue this command before the INSERT:
插入大量的数据将会因为WAL机制导致速度缓慢;如果想禁用该功能,要确保已经打过HIVE-1383(针对hive 0.6),然后在插入数据之前,执行以下命令:
set hive.hbase.wal.enabled=false;
警告: 禁用WAL功能将会导致HBase在异常出错时丢失数据,所以只能在还有其他恢复手段的时候使用这个功能。
如果你想让hive访问HBase上已经存在的表,可以使用创建外表功能“CREATE EXTERNAL TABLE”:
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.columns.mapping仍然是必需的,而且要保证它与HBase表上现有的列族相对应。hbase.table.name是可选项。
这里有两种SERDEPROPERTIES,将会对HBase列映射到hive产生影响:
当前可用的列映射配置方法多少有些繁琐:
列族名:[列名][#(二进制|字符串)
(通过#的方式指定该映射项在HBase中以二进制数的形式保存,在hive 0.9.0版本之前,没有此功能)#b
instead of #binary
)下面几段将会通过一个具体的例子来介绍当前可以使用的几种映射方式:
这里有个例子是三个hive列映射到两个HBase的列族,两个Hive列(value1、value2)对应一个列族(a,其中两个HBase列名为b、c),另一个Hive列则对应一个列族(d)中的单独的列(e)
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"
);
INSERT OVERWRITE TABLE hbase_table_1 SELECT foo, bar, foo+1, foo+2
FROM pokes WHERE foo=98 OR foo=100;
在hive中查看表结构,结果如下所示:
hbase(main):014:0> describe "hbase_table_1"
DESCRIPTION ENABLED
{NAME => 'hbase_table_1', FAMILIES => [{NAME => 'a', COMPRESSION => 'N true
ONE', VERSIONS => '3', TTL => '2147483647', BLOCKSIZE => '65536', IN_M
EMORY => 'false', BLOCKCACHE => 'true'}, {NAME => 'd', COMPRESSION =>
'NONE', VERSIONS => '3', TTL => '2147483647', BLOCKSIZE => '65536', IN
_MEMORY => 'false', BLOCKCACHE => 'true'}]}
1 row(s) in 0.0170 seconds
hbase(main):015:0> scan "hbase_table_1"
ROW COLUMN+CELL
100 column=a:b, timestamp=1267740457648, value=val_100
100 column=a:c, timestamp=1267740457648, value=101
100 column=d:e, timestamp=1267740457648, value=102
98 column=a:b, timestamp=1267740457648, value=val_98
98 column=a:c, timestamp=1267740457648, value=99
98 column=d:e, timestamp=1267740457648, value=100
2 row(s) in 0.0240 seconds
hive> select * from hbase_table_1;
Total MapReduce jobs = 1
Launching Job 1 out of 1
...
OK
100 val_100 101 102
98 val_98 99 100
Time taken: 4.054 seconds
这里,举一个Hive使用MAP类型来读取这个column family的例子。每行包含不同的列,列的命名根据map中的key,列的值为map中的value:
CREATE TABLE hbase_table_1(value map, row_key int)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES (
"hbase.columns.mapping" = "cf:,:key"
);
INSERT OVERWRITE TABLE hbase_table_1 SELECT map(bar, foo), foo FROM pokes
WHERE foo=98 OR foo=100;
这个例子同时也验证了hive使用的第一个列不一定是rowkey所在的列
这个表在HBase中看起来是这样的:
hbase(main):012:0> scan "hbase_table_1"
ROW COLUMN+CELL
100 column=cf:val_100, timestamp=1267739509194, value=100
98 column=cf:val_98, timestamp=1267739509194, value=98
2 row(s) in 0.0080 seconds
hive> select * from hbase_table_1;
Total MapReduce jobs = 1
Launching Job 1 out of 1
...
OK
{"val_100":100} 100
{"val_98":98} 98
Time taken: 3.808 seconds
注意,MAP类型中key必须是字符串,因为它将用来为HBase列命名,所以如下的表定义方式将会出错:
CREATE TABLE hbase_table_1(key int, value map)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES (
"hbase.columns.mapping" = ":key,cf:"
);
FAILED: Error in metadata: java.lang.RuntimeException: MetaException(message:org.apache.hadoop.hive.serde2.SerDeException org.apache.hadoop.hive.hbase.HBaseSerDe: hbase column family 'cf:' should be mapped to map but is mapped to map)
CREATE TABLE hbase_table_1(key int, value string)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES (
"hbase.columns.mapping" = ":key,cf:"
);
FAILED: Error in metadata: java.lang.RuntimeException: MetaException(message:org.apache.hadoop.hive.serde2.SerDeException org.apache.hadoop.hive.hbase.HBaseSerDe: hbase column family 'cf:' should be mapped to map but is mapped to string)
依赖默认的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"
);