Hive 已是目前業(yè)界最為通用滑蚯、廉價(jià)的構(gòu)建大數(shù)據(jù)時(shí)代數(shù)據(jù)倉(cāng)庫(kù)的解決方案了浪蹂,雖然也有 Impala 等后起之秀,但目前從功能告材、穩(wěn)定性等方面來(lái)說(shuō)坤次,Hive 的地位尚不可撼動(dòng)。
其實(shí)這篇博文主要是想聊聊 SMB join 的斥赋,Join 是整個(gè) MR/Hive 最為核心的部分之一浙踢,是每個(gè)Hadoop/Hive/DW RD 必須掌握的部分,之前也有幾篇文章聊到過(guò) MR/Hive 中的 join灿渴,其實(shí)底層都是相同的洛波,只是上層做了些封裝而已胰舆,如果你還不了解究竟 Join 有哪些方式,以及底層怎么實(shí)現(xiàn)的蹬挤,請(qǐng)參考如下鏈接:
http://my.oschina.net/leejun2005/blog/95186 MapReduce 中的兩表 join 幾種方案簡(jiǎn)介
http://my.oschina.net/leejun2005/blog/111963 Hadoop 多表 join:map side join 范例
http://my.oschina.net/leejun2005/blog/158491 Hive & Performance 學(xué)習(xí)筆記
在最后一篇鏈接中缚窿,有這么兩副圖:
前面兩個(gè)很好理解,基本上每個(gè)人都會(huì)接觸到焰扳,但最后一種倦零,可能有同學(xué)還是比較陌生,SMB 存在的目的主要是為了解決大表與大表間的 Join 問(wèn)題吨悍,分桶其實(shí)就是把大表化成了“小表”扫茅,然后 Map-Side Join 解決之,這是典型的分而治之的思想育瓜。在聊 SMB Join 之前葫隙,我們還是先復(fù)習(xí)下相關(guān)的基礎(chǔ)概念。
1躏仇、Hive 分區(qū)表
在Hive Select查詢(xún)中一般會(huì)掃描整個(gè)表內(nèi)容恋脚,會(huì)消耗很多時(shí)間做沒(méi)必要的工作。有時(shí)候只需要掃描表中關(guān)心的一部分?jǐn)?shù)據(jù)焰手,因此建表時(shí)引入了partition概念糟描。分區(qū)表指的是在創(chuàng)建表時(shí)指定的partition的分區(qū)空間。
Hive可以對(duì)數(shù)據(jù)按照某列或者某些列進(jìn)行分區(qū)管理书妻,所謂分區(qū)我們可以拿下面的例子進(jìn)行解釋船响。
當(dāng)前互聯(lián)網(wǎng)應(yīng)用每天都要存儲(chǔ)大量的日志文件,幾G躲履、幾十G甚至更大都是有可能灿意。存儲(chǔ)日志,其中必然有個(gè)屬性是日志產(chǎn)生的日期崇呵。在產(chǎn)生分區(qū)時(shí),就可以按照日志產(chǎn)生的日期列進(jìn)行劃分馅袁。把每一天的日志當(dāng)作一個(gè)分區(qū)域慷。
將數(shù)據(jù)組織成分區(qū),主要可以提高數(shù)據(jù)的查詢(xún)速度汗销。至于用戶存儲(chǔ)的每一條記錄到底放到哪個(gè)分區(qū)犹褒,由用戶決定。即用戶在加載數(shù)據(jù)的時(shí)候必須顯示的指定該部分?jǐn)?shù)據(jù)放到哪個(gè)分區(qū)弛针。
1.1 實(shí)現(xiàn)細(xì)節(jié)
1叠骑、一個(gè)表可以擁有一個(gè)或者多個(gè)分區(qū),每個(gè)分區(qū)以文件夾的形式單獨(dú)存在表文件夾的目錄下削茁。
2宙枷、表和列名不區(qū)分大小寫(xiě)掉房。
3、分區(qū)是以字段的形式在表結(jié)構(gòu)中存在慰丛,通過(guò)describe table命令可以查看到字段存在卓囚, 但是該字段不存放實(shí)際的數(shù)據(jù)內(nèi)容,僅僅是分區(qū)的表示(偽列) 诅病。
1.2 語(yǔ)法
1. 創(chuàng)建一個(gè)分區(qū)表哪亿,以 ds 為分區(qū)列:
create table invites (id int, name string) partitioned by (ds string) row format delimited fields terminated by 't' stored as textfile;
2. 將數(shù)據(jù)添加到時(shí)間為 2013-08-16 這個(gè)分區(qū)中:
load data local inpath '/home/hadoop/Desktop/data.txt' overwrite into table invites partition (ds='2013-08-16');
3. 將數(shù)據(jù)添加到時(shí)間為 2013-08-20 這個(gè)分區(qū)中:
load data local inpath '/home/hadoop/Desktop/data.txt' overwrite into table invites partition (ds='2013-08-20');
4. 從一個(gè)分區(qū)中查詢(xún)數(shù)據(jù):
select * from invites where ds ='2013-08-12';
5. 往一個(gè)分區(qū)表的某一個(gè)分區(qū)中添加數(shù)據(jù):
insert overwrite table invites partition (ds='2013-08-12') select id,max(name) from test group by id;
可以查看分區(qū)的具體情況,使用命令:
hadoop fs -ls /home/hadoop.hive/warehouse/invites
或者:
show partitions tablename;
2贤笆、Hive 桶
對(duì)于每一個(gè)表(table)或者分區(qū)蝇棉, Hive可以進(jìn)一步組織成桶,也就是說(shuō)桶是更為細(xì)粒度的數(shù)據(jù)范圍劃分芥永。Hive也是 針對(duì)某一列進(jìn)行桶的組織篡殷。Hive采用對(duì)列值哈希,然后除以桶的個(gè)數(shù)求余的方式?jīng)Q定該條記錄存放在哪個(gè)桶當(dāng)中恤左。
把表(或者分區(qū))組織成桶(Bucket)有兩個(gè)理由:
(1)獲得更高的查詢(xún)處理效率贴唇。桶為表加上了額外的結(jié)構(gòu),Hive 在處理有些查詢(xún)時(shí)能利用這個(gè)結(jié)構(gòu)飞袋。具體而言戳气,連接兩個(gè)在(包含連接列的)相同列上劃分了桶的表,可以使用 Map 端連接 (Map-side join)高效的實(shí)現(xiàn)巧鸭。比如JOIN操作瓶您。對(duì)于JOIN操作兩個(gè)表有一個(gè)相同的列,如果對(duì)這兩個(gè)表都進(jìn)行了桶操作纲仍。那么將保存相同列值的桶進(jìn)行JOIN操作就可以呀袱,可以大大較少JOIN的數(shù)據(jù)量。
(2)使取樣(sampling)更高效郑叠。在處理大規(guī)模數(shù)據(jù)集時(shí)夜赵,在開(kāi)發(fā)和修改查詢(xún)的階段,如果能在數(shù)據(jù)集的一小部分?jǐn)?shù)據(jù)上試運(yùn)行查詢(xún)乡革,會(huì)帶來(lái)很多方便寇僧。
1. 創(chuàng)建帶桶的 table :
create table bucketed_user(id int,name string) clustered by (id) sorted by(name) into 4 buckets row format delimited fields terminated by '\t' stored as textfile;
首先,我們來(lái)看如何告訴Hive—個(gè)表應(yīng)該被劃分成桶沸版。我們使用CLUSTERED BY 子句來(lái)指定劃分桶所用的列和要?jiǎng)澐值耐暗膫€(gè)數(shù):
CREATE TABLE bucketed_user (id INT) name STRING)
CLUSTERED BY (id) INTO 4 BUCKETS;
在這里嘁傀,我們使用用戶ID來(lái)確定如何劃分桶(Hive使用對(duì)值進(jìn)行哈希并將結(jié)果除 以桶的個(gè)數(shù)取余數(shù)。這樣视粮,任何一桶里都會(huì)有一個(gè)隨機(jī)的用戶集合(PS:其實(shí)也能說(shuō)是隨機(jī)细办,不是嗎?)蕾殴。
對(duì)于map端連接的情況笑撞,兩個(gè)表以相同方式劃分桶岛啸。處理左邊表內(nèi)某個(gè)桶的 mapper知道右邊表內(nèi)相匹配的行在對(duì)應(yīng)的桶內(nèi)。因此娃殖,mapper只需要獲取那個(gè)桶 (這只是右邊表內(nèi)存儲(chǔ)數(shù)據(jù)的一小部分)即可進(jìn)行連接琉苇。這一優(yōu)化方法并不一定要求 兩個(gè)表必須桶的個(gè)數(shù)相同已烤,兩個(gè)表的桶個(gè)數(shù)是倍數(shù)關(guān)系也可以。用HiveQL對(duì)兩個(gè)劃分了桶的表進(jìn)行連接,可參見(jiàn)“map連接”部分(P400)诱渤。
桶中的數(shù)據(jù)可以根據(jù)一個(gè)或多個(gè)列另外進(jìn)行排序仍秤。由于這樣對(duì)每個(gè)桶的連接變成了高效的歸并排序(merge-sort), 因此可以進(jìn)一步提升map端連接的效率畴博。以下語(yǔ)法聲明一個(gè)表使其使用排序桶:
CREATE TABLE bucketed_users (id INT, name STRING)
CLUSTERED BY (id) SORTED BY (id ASC) INTO 4 BUCKETS;
我們?nèi)绾伪WC表中的數(shù)據(jù)都劃分成桶了呢吮成?把在Hive外生成的數(shù)據(jù)加載到劃分成 桶的表中,當(dāng)然是可以的郁稍。其實(shí)讓Hive來(lái)劃分桶更容易赦政。這一操作通常針對(duì)已有的表。
Hive并不檢查數(shù)據(jù)文件中的桶是否和表定義中的桶一致(無(wú)論是對(duì)于桶 的數(shù)量或用于劃分桶的列)耀怜。如果兩者不匹配恢着,在査詢(xún)時(shí)可能會(huì)碰到錯(cuò) 誤或未定義的結(jié)果。因此财破,建議讓Hive來(lái)進(jìn)行劃分桶的操作掰派。
有一個(gè)沒(méi)有劃分桶的用戶表:
hive> SELECT * FROM users;
0 Nat
2 Doe
B Kay
4 Ann
2. 強(qiáng)制多個(gè) reduce 進(jìn)行輸出:
要向分桶表中填充成員,需要將 hive.enforce.bucketing 屬性設(shè)置為 true左痢。①這 樣靡羡,Hive 就知道用表定義中聲明的數(shù)量來(lái)創(chuàng)建桶。然后使用 INSERT 命令即可俊性。需要注意的是: clustered by和sorted by不會(huì)影響數(shù)據(jù)的導(dǎo)入略步,這意味著,用戶必須自己負(fù)責(zé)數(shù)據(jù)如何如何導(dǎo)入定页,包括數(shù)據(jù)的分桶和排序趟薄。
'set hive.enforce.bucketing = true' 可以自動(dòng)控制上一輪reduce的數(shù)量從而適配bucket的個(gè)數(shù),當(dāng)然典徊,用戶也可以自主設(shè)置mapred.reduce.tasks去適配bucket個(gè)數(shù)杭煎,推薦使用'set hive.enforce.bucketing = true'
3. 往表中插入數(shù)據(jù):
INSERT OVERWRITE TABLE bucketed_users SELECT * FROM users;
物理上,每個(gè)桶就是表(或分區(qū))目錄里的一個(gè)文件宫峦。它的文件名并不重要,但是桶 n 是按照字典序排列的第 n 個(gè)文件玫鸟。事實(shí)上导绷,桶對(duì)應(yīng)于 MapReduce 的輸出文件分區(qū):一個(gè)作業(yè)產(chǎn)生的桶(輸出文件)和reduce任務(wù)個(gè)數(shù)相同。我們可以通過(guò)查看剛才 創(chuàng)建的bucketd_users表的布局來(lái)了解這一情況屎飘。運(yùn)行如下命令:
4. 查看表的結(jié)構(gòu):
hive> dfs -ls /user/hive/warehouse/bucketed_users;
將顯示有4個(gè)新建的文件妥曲。文件名如下(文件名包含時(shí)間戳,由Hive產(chǎn)生褂萧,因此 每次運(yùn)行都會(huì)改變):
attempt_201005221636_0016_r_000000_0
attempt_201005221636_0016_r-000001_0
attempt_201005221636_0016_r_000002_0
attempt_201005221636_0016_r_000003_0
第一個(gè)桶里包括用戶IDO和4导犹,因?yàn)橐粋€(gè)INT的哈希值就是這個(gè)整數(shù)本身谎痢,在這里 除以桶數(shù)(4)以后的余數(shù):②
5. 讀取數(shù)據(jù)节猿,看每一個(gè)文件的數(shù)據(jù):
hive> dfs -cat /user/hive/warehouse/bucketed_users/*0_0;
0 Nat
4 Ann
用TABLESAMPLE子句對(duì)表進(jìn)行取樣滨嘱,我們可以獲得相同的結(jié)果太雨。這個(gè)子句會(huì)將 查詢(xún)限定在表的一部分桶內(nèi),而不是使用整個(gè)表:
6. 對(duì)桶中的數(shù)據(jù)進(jìn)行采樣:
hive> SELECT * FROM bucketed_users
TABLESAMPLE(BUCKET 1 OUT OF 4 ON id);
0 Nat
4 Ann
桶的個(gè)數(shù)從1開(kāi)始計(jì)數(shù)宪拥。因此铣减,前面的查詢(xún)從4個(gè)桶的第一個(gè)中獲取所有的用戶。 對(duì)于一個(gè)大規(guī)模的缔刹、均勻分布的數(shù)據(jù)集校镐,這會(huì)返回表中約四分之一的數(shù)據(jù)行捺典。我們 也可以用其他比例對(duì)若干個(gè)桶進(jìn)行取樣(因?yàn)槿硬⒉皇且粋€(gè)精確的操作,因此這個(gè) 比例不一定要是桶數(shù)的整數(shù)倍)牍陌。例如毒涧,下面的查詢(xún)返回一半的桶:
7. 查詢(xún)一半返回的桶數(shù):
hive> SELECT * FROM bucketed_users
TABLESAMPLE(BUCKET 1 OUT OF 2 ON id)契讲;
0 Nat
4 Ann
2 Joe
因?yàn)椴樵?xún)只需要讀取和TABLESAMPLE子句匹配的桶,所以取樣分桶表是非常高效 的操作霹琼。如果使用rand()函數(shù)對(duì)沒(méi)有劃分成桶的表進(jìn)行取樣凉当,即使只需要讀取很 小一部分樣本看杭,也要掃描整個(gè)輸入數(shù)據(jù)集:
hive〉 SELECT * FROM users
TABLESAMPLE(BUCKET 1 OUT OF 4 ON rand());
2 Doe
①?gòu)腍ive 0.6.0開(kāi)始楼雹,對(duì)以前的版本贮缅,必須把mapred.reduce .tasks設(shè)為表中要填 充的桶的個(gè)數(shù)谴供。如果桶是排序的,還需要把hive.enforce.sorting設(shè)為true桂肌。
②顯式原始文件時(shí)数焊,因?yàn)榉指糇址且粋€(gè)不能打印的控制字符崎场,因此字段都擠在一起佩耳。
3谭跨、舉個(gè)完整的小例子:
(1)建student & student1 表:
| 1
| create
table
student(id ``INT``, age ``INT``, ``name
STRING)
|
| 2
| partitioned ``by``(stat_date STRING)
|
| 3
| clustered ``by``(id) sorted ``by``(age) ``into
2 buckets
|
| 4
| row format delimited fields terminated ``by
','``;
|
| 5
| |
| 6
| create
table
student1(id ``INT``, age ``INT``, ``name
STRING)
|
| 7
| partitioned ``by``(stat_date STRING)
|
| 8
| clustered ``by``(id) sorted ``by``(age) ``into
2 buckets
|
| 9
| row format delimited fields terminated ``by
','``;
|
(2)設(shè)置環(huán)境變量:
set hive.enforce.bucketing = true;
(3)插入數(shù)據(jù):
| 01
| cat bucket.txt
|
| 02
| |
| 03
| 1,20,zxm
|
| 04
| 2,21,ljz
|
| 05
| 3,19,cds
|
| 06
| 4,18,mac
|
| 07
| 5,22,android
|
| 08
| 6,23,symbian
|
| 09
| 7,25,wp
|
| 10
| |
| 11
| LOAD
DATA ``local
INPATH ``'/home/lijun/bucket.txt'
OVERWRITE ``INTO
TABLE
student partition(stat_date=``"20120802"``);
|
| 12
| |
| 13
| from
student
|
| 14
| insert
overwrite ``table
student1 partition(stat_date=``"20120802"``)
|
| 15
| select
id,age,``name
where
stat_date=``"20120802"
sort ``by
age;
|
(4)查看文件目錄:
hadoop fs -ls /hive/warehouse/test.db/student1/stat_date=20120802
Found 2 items
-rw-r--r-- 2 lijun supergroup 31 2013-11-24 19:16 /hive/warehouse/test.db/student1/stat_date=20120802/000000_0
-rw-r--r-- 2 lijun supergroup 39 2013-11-24 19:16 /hive/warehouse/test.db/student1/stat_date=20120802/000001_0
(5)查看sampling數(shù)據(jù):
hive> select * from student1 tablesample(bucket 1 out of 2 on id);
Total MapReduce jobs = 1
Launching Job 1 out of 1
.......
OK
4 18 mac 20120802
2 21 ljz 20120802
6 23 symbian 20120802
Time taken: 20.608 seconds
注:tablesample是抽樣語(yǔ)句干厚,語(yǔ)法:TABLESAMPLE(BUCKET x OUT OF y)
y必須是table總bucket數(shù)的倍數(shù)或者因子。hive根據(jù)y的大小污呼,決定抽樣的比例。例如,table總共分了64份苗缩,當(dāng)y=32時(shí),抽取(64/32=)2個(gè)bucket的數(shù)據(jù),當(dāng)y=128時(shí)泻肯,抽取(64/128=)1/2個(gè)bucket的數(shù)據(jù)渊迁。x表示從哪個(gè)bucket開(kāi)始抽取。例如灶挟,table總bucket數(shù)為32琉朽,tablesample(bucket 3 out of 16),表示總共抽戎上场(32/16=)2個(gè)bucket的數(shù)據(jù)箱叁,分別為第3個(gè)bucket和第(3+16=)19個(gè)bucket的數(shù)據(jù)。
4惕医、Refer:
http://rdc.taobao.org/?p=1457 從MR到Hive – 一次遷移的過(guò)程
http://blog.573114.com/Blog/Html/A031/516857.html Hadoop權(quán)威指南 第12章 Hive簡(jiǎn)介 P384
http://superlxw1234.iteye.com/blog/1545150 hive--Sort Merge Bucket Map Join