此篇文章基於hive官方英文文檔翻譯,有些不好理解的地方加入了我個人的理解,官方的英文地址爲:
1.https://cwiki.apache.org/confluence/display/Hive/StorageHandlers
2.https://cwiki.apache.org/confluence/display/Hive/HBaseIntegration
一 存儲處理器介紹
通過HIVE存儲處理器,不但可以讓hive基於hbase實現,還可以支持cassandra JDBC MongoDB 以及 Google Spreadsheets
HIVE存儲器的實現原理基於HIVE以及Hadoop的可擴展性實現:
輸入格式化(input formats)
輸出格式化(output formats)
序列化/反序列化包(serialization/deserialization librarises)
除了依據以上可擴展性,存儲處理器還需要實現新的元數據鉤子接口,這個接口允許使用HIVE的DDL語句來定義和管理hive自己的元數據以及其它系統的目錄(此目錄個人理解爲其它系統的元數據目錄)
一些術語:
HIVE本身有的概念:
被管理的表即內部表(managed):元數據由hive管理,並且數據也存儲在hive的體系裏面
外部表(external table):表的定義被外部的元數據目錄所管理,數據也存儲在外部系統中
hive存儲處理器的概念:
本地(native)表:hive不需要藉助存儲處理器就可以直接管理和訪問的表
非本地(non-native)表:需要通過存儲處理器才能管理和訪問的表
內部表 外部表 和 本地表 非本地表 形成交叉,就有了下面四種形式的概念定義:
被管理的本地表(managed native):通過CREATE TABLE創建的表
外部本地表(external native):通過CREATE EXTERNAL TABLE創建,但是沒有帶STORED BY子句
被管理的非本地表()managed non-native):通過CREATE TABLE 創建,同時有STORED BY子句,hive在元數據中存儲表定義,但是不創建任何文件(我的理解是存放數據的目錄文件,hive在定義好表結構後會創建對應的目錄來存儲對應的數據),hive存儲處理器向存儲數據的系統發出一個請求來創建一個一致的對象結構;
外部非本地(external non-native):通過CREATE EXTERNAL TABLE創建,並且帶有STORED BY子句;hive在自己的元數據中註冊表的定義信息,並且通過調用存儲處理器來檢查這些註冊在hive中的信息是否與其它系統中原來定義的信息一致
通過hive創建表結構的DDL語句:
CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name
[(col_name data_type [COMMENT col_comment], ...)]
[COMMENT table_comment]
[PARTITIONED BY (col_name data_type [col_comment], col_name data_type [COMMENT col_comment], ...)]
[CLUSTERED BY (col_name, col_name, ...) [SORTED BY (col_name, ...)] INTO num_buckets BUCKETS]
[
[ROW FORMAT row_format] [STORED AS file_format]
| STORED BY 'storage.handler.class.name' [WITH SERDEPROPERTIES (...)]
]
[LOCATION hdfs_path]
[AS select_statement]
具體的使用樣例,會在下面詳細說明
二 Hive與Hbase的整合
hive和Hbase的整合通過Hbase handler jar包實現,它的形式爲hive-hbase-x.y.z.jar ,這個處理器需要依賴hadoop0.20以上版本,並且只在hadoop-0.20.x hbase-0.92.0 和zookeeper-3.3.4 上進行過測試。如果hbase版本不是0.92則需要基於你使用的版本重新編譯對應的hive存儲處理器。
爲了創建一張可以被hive管理的hbase的表,需要在hive的ddl語句CREATE TABLE 後面加入語句STORED BY
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.table.name屬性是可選的,用它來指定此表在hbase中的名字,這就是說,允許同一個表在hive和hbase中有不同的名字。上述語句中,在hive中的表名叫hbase_talbe_1,在hbase中,此表名叫xyz。如果不指定,兩個名字則會是相同的。
當執行完命令後,就可以在HBase的shell中看到一張新的空表,如下:
$ hbase shell
HBase Shell; enter 'help<RETURN>' 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中只有列族的名字纔會被定義在表級的元數據中,列族中的列只定義在行級別的元數據中。
下面的語句是定義怎麼把數據從hive中加載進入HBase的表,表pokes是hive中已經存在的一個表,並且表中有數據。
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:
set hive.hbase.wal.enabled=false;
Warning: disabling WAL may lead to data loss if an HBase failure occurs, so only use this if you have some other recovery strategy available.
如果想基於已經存在的hbase表創建hive可訪問的表,則需要用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表的列族以及列的定義信息。
hive與hbase的列匹配
有兩種SERDEPROPERTIES配置信息來控制HBase列到HIve的映射
1.hbase.columns,mapping
2.hbase.table.default.storage.type
這個屬性可以有String和二進制兩種類型的值,默認是String;這個選項只有在hive0.9這個版本可用, 並且在早期的版本中,只有String類型可用
當前列匹配使用起來顯得有點笨重和憋手:
1.每個hive的列,都需要在參數hbase.columns.mapping中指定一個對應的條目(比如:a:b或者:key就叫一個條目),多個列之間的條目通過逗號分隔;也就是說,如果某個表有n個列,則參數hbase.columns.mapping的值中就有n個以逗號分隔的條目。比如:
"hbase.columns.mapping" = ":key,a:b,a:c,d:e" 代表有兩個列族,一個是a一個是d,a列族中有兩列,分別爲b和c
注意,hbase.columns.mapping的值中是不允許出現空格的
2.每個匹配條目的形式必須是:key或者列族名:[列名][#(binary|string)](前面帶有#標識的是在hive0.9才添加的,早期的版本把所有都當作String類型)
1)如果沒有給列或者列族指定類型,hbase.table.default.storage.type的值會被當作這些列或者列族的類 型
2)表示類型的值(binary|string)的任何前綴都可以被用來表示這個類型,比如#b就代表了#binary
3)如果指定列的類型爲二進制(binary)字節,則在HBase的單元格的存儲類型也必須爲二進制字節
3.必須竄至一個:key的形式匹配條目(不支持複合key的形式)
4.在hive0.6版本以前,是通過第一個條目來作爲關鍵字(key)字段,從0.6以後,都需要直接通過:key的方式來指定
5.當前沒有辦法可以訪問到HBase的時間撮屬性,並且查詢總是訪問到最新的數據(這條主要是因爲hbase單元格數據存儲是有版本的,根據時間撮)
6. 因爲HBase的列定義沒有包含有數據類型信息,所以在存儲的時候,會把所有的其它類型都轉換爲string 代表;所以,列數據類型不支持自定義
7. 沒有必要對HBase的所有列族都進行映射,但是沒有被映射的列族不能通過訪問Hive表讀取到數據;可以把多個Hive表映射到同一個HBase的表
下面的章節提供更加詳細的例子,來說明當前各種不同的列映射
多個列和列族
下面的例子包含三個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"
);
INSERT OVERWRITE TABLE hbase_table_1 SELECT foo, bar, foo+1, foo+2
FROM pokes WHERE foo=98 OR foo=100;
上面的例子中,hive擁有除去可以外的列爲三個,value1 value2 value3,對應的HBase表爲兩個列族,分別爲a和d,其中a包括兩列(b和c);值的對應關係從左到右一一對應,a列族中的b列對應value1,c列對應value2,d列族的e列對應value3
下面是從HBase中查看的結果:
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中的查詢結果是:
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(集合)與Hbase 列族的映射
下面是Hive的MAP數據類型與Hbase列族的映射例子。每行都可以包含不同的列組合,列名與map的可以對應,values與列值對應。
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"
);
INSERT OVERWRITE TABLE hbase_table_1 SELECT map(bar, foo), foo FROM pokes
WHERE foo=98 OR foo=100;
其中cf表示列族,冒號後面爲空,它與Hive表的列value對應,即value的key爲cf列族的列,可以通過下面HBase的查詢來理解key爲列名,value爲值的用法。
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
cf爲列族名,val_100爲hive表中MAP(集合)的key,100爲MAP(集合)中的val_100的值
下面是對應的hive的查詢顯示結果:
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必須是string類型,否則會失敗,因爲key是HBase列的名字;如下的定義將會失敗
CREATE TABLE hbase_table_1(key int, value map<int,int>)
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<string,?> but is mapped to map<int,int>)
注意:當hbase.columns.mapping中有“:key,cf:”這樣的值,即列族冒號後面爲空時,表示Hive中對應的類型爲集合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<string,?> but is mapped to string)
hbase.columns.mapping中值類型用法列舉
如果沒有指定值的類型,比如cf:val,則採用配置hbase.table.default.storage.type的值爲數據類型
1.當不配置hbase.table.default.storage.type時,它默認是string,如果有二進制的數據類型,則如下定義:
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"
);
2.如果顯示的指定hbase.table.default.storage.type爲binary時,如果類型有string類型,則需要指定,如:
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"
);
cf:val#s 中的#s就表示類型爲string,cf:foo沒有配置類型,則採用hbase.table.default.storage.type的配置,爲binary
添加時間戳
當用hive給HBase的表添加記錄時,時間戳默認爲當前時間,如果想改變這個值,可以通過設置SERDEPROPERIES屬性的可選配置項hbase.put.timestamp,當這個配置項爲-1的時候,就是默認策略,即添加記錄爲當前時間戳
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"
"hbase.put.timestamp" = "2013-03-17 09:04:30"
)
主鍵唯一性
HBase與Hive的表有一個細微的差別,就是HBase的表有一個主鍵,並且需要唯一,但是Hive沒有;如果不能保證這個主鍵的唯一,則HBase存儲的時候,只能存儲其中一個,這會導致查詢的時候,hive總能找到正確的值,而HBase出來的結果就不確定
如下,在hive中查詢:
CREATE TABLE pokes2(foo INT, bar STRING);
INSERT OVERWRITE TABLE pokes2 SELECT * FROM pokes;
-- this will return 3
SELECT COUNT(1) FROM POKES WHERE foo=498;
-- this will also return 3
SELECT COUNT(1) FROM pokes2 WHERE foo=498;
同一張表在HBase中查詢,就會出來不確定的結果:
CREATE TABLE pokes3(foo INT, bar STRING)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES (
"hbase.columns.mapping" = ":key,cf:bar"
);
INSERT OVERWRITE TABLE pokes3 SELECT * FROM pokes;
-- this will return 1 instead of 3
SELECT COUNT(1) FROM pokes3 WHERE foo=498;
覆蓋(Overwrite)
當執行OVERWRITE時,HBase中已經存在的記錄是不會被刪除的;但是,如果存在的記錄與新紀錄的主鍵(key)是一樣的,則老數據會被覆蓋