天天看點

分區、桶、Sort Merge Bucket Join

Hive 已是目前業界最為通用、廉價的建構大資料時代資料倉庫的解決方案了,雖然也有 Impala 等後起之秀,但目前從功能、穩定性等方面來說,Hive 的地位尚不可撼動。

其實這篇博文主要是想聊聊 SMB join 的,Join 是整個 MR/Hive 最為核心的部分之一,是每個 Hadoop/Hive/DW RD 必須掌握的部分,之前也有幾篇文章聊到過 MR/Hive 中的 join,其實底層都是相同的,隻是上層做了些封裝而已,如果你還不了解究竟 Join 有哪些方式,以及底層怎麼實作的,請參考如下連結:

http://my.oschina.net/leejun2005/blog/95186 MapReduce 中的兩表 join 幾種方案簡介

http://my.oschina.net/leejun2005/blog/111963 Hadoop 多表 join:map side join 範例

http://my.oschina.net/leejun2005/blog/158491 Hive & Performance 學習筆記

在最後一篇連結中,有這麼兩副圖:

分區、桶、Sort Merge Bucket Join
分區、桶、Sort Merge Bucket Join

前面兩個很好了解,基本上每個人都會接觸到,但最後一種,可能有同學還是比較陌生,SMB 存在的目的主要是為了解決大表與大表間的 Join 問題,分桶其實就是把大表化成了“小表”,然後 Map-Side Join 解決之,這是典型的分而治之的思想。在聊 SMB Join 之前,我們還是先複習下相關的基礎概念。

1、Hive 分區表

在Hive Select查詢中一般會掃描整個表内容,會消耗很多時間做沒必要的工作。有時候隻需要掃描表中關心的一部分資料,是以建表時引入了partition概念。分區表指的是在建立表時指定的partition的分區空間。 Hive可以對資料按照某列或者某些列進行分區管理,所謂分區我們可以拿下面的例子進行解釋。 目前網際網路應用每天都要存儲大量的日志檔案,幾G、幾十G甚至更大都是有可能。存儲日志,其中必然有個屬性是日志産生的日期。在産生分區時,就可以按照日志産生的日期列進行劃分。把每一天的日志當作一個分區。 将資料組織成分區,主要可以提高資料的查詢速度。至于使用者存儲的每一條記錄到底放到哪個分區,由使用者決定。即使用者在加載資料的時候必須顯示的指定該部分資料放到哪個分區。

1.1 實作細節

1、一個表可以擁有一個或者多個分區,每個分區以檔案夾的形式單獨存在表檔案夾的目錄下。 2、表和列名不區分大小寫。 3、分區是以字段的形式在表結構中存在,通過describe table指令可以檢視到字段存在, 但是該字段不存放實際的資料内容,僅僅是分區的表示(僞列) 。

1.2 文法

1. 建立一個分區表,以 ds 為分區列: create table invites (id int, name string) partitioned by (ds string) row format delimited fields terminated by 't' stored as textfile; 2. 将資料添加到時間為 2013-08-16 這個分區中: load data local inpath '/home/hadoop/Desktop/data.txt' overwrite into table invites partition (ds='2013-08-16'); 3. 将資料添加到時間為 2013-08-20 這個分區中: load data local inpath '/home/hadoop/Desktop/data.txt' overwrite into table invites partition (ds='2013-08-20'); 4. 從一個分區中查詢資料: select * from invites where ds ='2013-08-12'; 5.  往一個分區表的某一個分區中添加資料: insert overwrite table invites partition (ds='2013-08-12') select id,max(name) from test group by id; 可以檢視分區的具體情況,使用指令: hadoop fs -ls /home/hadoop.hive/warehouse/invites 或者: show partitions tablename;

2、Hive 桶

對于每一個表(table)或者分區, Hive可以進一步組織成桶,也就是說桶是更為細粒度的資料範圍劃分。Hive也是 針對某一列進行桶的組織。Hive采用對列值哈希,然後除以桶的個數求餘的方式決定該條記錄存放在哪個桶當中。

把表(或者分區)組織成桶(Bucket)有兩個理由:

(1)獲得更高的查詢處理效率。桶為表加上了額外的結構,Hive 在處理有些查詢時能利用這個結構。具體而言,連接配接兩個在(包含連接配接列的)相同列上劃分了桶的表,可以使用 Map 端連接配接 (Map-side join)高效的實作。比如JOIN操作。對于JOIN操作兩個表有一個相同的列,如果對這兩個表都進行了桶操作。那麼将儲存相同列值的桶進行JOIN操作就可以,可以大大較少JOIN的資料量。

(2)使取樣(sampling)更高效。在處理大規模資料集時,在開發和修改查詢的階段,如果能在資料集的一小部分資料上試運作查詢,會帶來很多友善。

1. 建立帶桶的 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; 首先,我們來看如何告訴Hive—個表應該被劃分成桶。我們使用CLUSTERED BY 子句來指定劃分桶所用的列和要劃分的桶的個數: CREATE TABLE bucketed_user (id INT) name STRING) CLUSTERED BY (id) INTO 4 BUCKETS; 在這裡,我們使用使用者ID來确定如何劃分桶(Hive使用對值進行哈希并将結果除 以桶的個數取餘數。這樣,任何一桶裡都會有一個随機的使用者集合(PS:其實也能說是随機,不是嗎?)。 對于map端連接配接的情況,兩個表以相同方式劃分桶。處理左邊表内某個桶的 mapper知道右邊表内相比對的行在對應的桶内。是以,mapper隻需要擷取那個桶 (這隻是右邊表記憶體儲資料的一小部分)即可進行連接配接。這一優化方法并不一定要求 兩個表必須桶的個數相同,兩個表的桶個數是倍數關系也可以。用HiveQL對兩個劃分了桶的表進行連接配接,可參見“map連接配接”部分(P400)。 桶中的資料可以根據一個或多個列另外進行排序。由于這樣對每個桶的連接配接變成了高效的歸并排序(merge-sort), 是以可以進一步提升map端連接配接的效率。以下文法聲明一個表使其使用排序桶: CREATE TABLE bucketed_users (id INT, name STRING) CLUSTERED BY (id) SORTED BY (id ASC) INTO 4 BUCKETS; 我們如何保證表中的資料都劃分成桶了呢?把在Hive外生成的資料加載到劃分成 桶的表中,當然是可以的。其實讓Hive來劃分桶更容易。這一操作通常針對已有的表。 Hive并不檢查資料檔案中的桶是否和表定義中的桶一緻(無論是對于桶 的數量或用于劃分桶的列)。如果兩者不比對,在査詢時可能會碰到錯 誤或未定義的結果。是以,建議讓Hive來進行劃分桶的操作。 有一個沒有劃分桶的使用者表: hive> SELECT * FROM users; 0    Nat 2    Doe B    Kay 4    Ann

2. 強制多個 reduce 進行輸出:

要向分桶表中填充成員,需要将 hive.enforce.bucketing 屬性設定為 true。①這 樣,Hive 就知道用表定義中聲明的數量來建立桶。然後使用 INSERT 指令即可。需要注意的是: clustered by和sorted by不會影響資料的導入,這意味着,使用者必須自己負責資料如何如何導入,包括資料的分桶和排序。 'set hive.enforce.bucketing = true' 可以自動控制上一輪reduce的數量進而适配bucket的個數,當然,使用者也可以自主設定mapred.reduce.tasks去适配bucket個數,推薦使用'set hive.enforce.bucketing = true' 

3. 往表中插入資料:

INSERT OVERWRITE TABLE bucketed_users SELECT * FROM users; 實體上,每個桶就是表(或分區)目錄裡的一個檔案。它的檔案名并不重要,但是桶 n 是按照字典序排列的第 n 個檔案。事實上,桶對應于 MapReduce 的輸出檔案分區:一個作業産生的桶(輸出檔案)和reduce任務個數相同。我們可以通過檢視剛才 建立的bucketd_users表的布局來了解這一情況。運作如下指令: 

4. 檢視表的結構:

hive> dfs -ls /user/hive/warehouse/bucketed_users; 将顯示有4個建立的檔案。檔案名如下(檔案名包含時間戳,由Hive産生,是以 每次運作都會改變): attempt_201005221636_0016_r_000000_0 attempt_201005221636_0016_r-000001_0 attempt_201005221636_0016_r_000002_0 attempt_201005221636_0016_r_000003_0 第一個桶裡包括使用者IDO和4,因為一個INT的哈希值就是這個整數本身,在這裡 除以桶數(4)以後的餘數:②

5. 讀取資料,看每一個檔案的資料:

hive> dfs -cat /user/hive/warehouse/bucketed_users/*0_0; 0 Nat 4 Ann 用TABLESAMPLE子句對表進行取樣,我們可以獲得相同的結果。這個子句會将 查詢限定在表的一部分桶内,而不是使用整個表:

6. 對桶中的資料進行采樣:

hive> SELECT * FROM bucketed_users >    TABLESAMPLE(BUCKET 1 OUT OF 4 ON id); 0 Nat 4 Ann 桶的個數從1開始計數。是以,前面的查詢從4個桶的第一個中擷取所有的使用者。 對于一個大規模的、均勻分布的資料集,這會傳回表中約四分之一的資料行。我們 也可以用其他比例對若幹個桶進行取樣(因為取樣并不是一個精确的操作,是以這個 比例不一定要是桶數的整數倍)。例如,下面的查詢傳回一半的桶:

7. 查詢一半傳回的桶數:

hive> SELECT * FROM bucketed_users >    TABLESAMPLE(BUCKET 1 OUT OF 2 ON id); 0 Nat 4 Ann 2 Joe 因為查詢隻需要讀取和TABLESAMPLE子句比對的桶,是以取樣分桶表是非常高效 的操作。如果使用rand()函數對沒有劃分成桶的表進行取樣,即使隻需要讀取很 小一部分樣本,也要掃描整個輸入資料集: hive〉 SELECT * FROM users > TABLESAMPLE(BUCKET 1 OUT OF 4 ON rand()); 2 Doe ①從Hive 0.6.0開始,對以前的版本,必須把mapred.reduce .tasks設為表中要填 充的桶的個數。如果桶是排序的,還需要把hive.enforce.sorting設為true。 ②顯式原始檔案時,因為分隔字元是一個不能列印的控制字元,是以字段都擠在一起。

3、舉個完整的小栗子:

(1)建student & student1 表:

create table student(id INT, age INT, name STRING)
partitioned by(stat_date STRING) 
clustered by(id) sorted by(age) into 2 buckets
row format delimited fields terminated by ',';

create table student1(id INT, age INT, name STRING)
partitioned by(stat_date STRING) 
clustered by(id) sorted by(age) into 2 buckets
row format delimited fields terminated by ',';
           

(2)設定環境變量:

set hive.enforce.bucketing = true; 

(3)插入資料:

cat bucket.txt

1,20,zxm
2,21,ljz
3,19,cds
4,18,mac
5,22,android
6,23,symbian
7,25,wp

LOAD DATA local INPATH '/home/lijun/bucket.txt' OVERWRITE INTO TABLE student partition(stat_date="20120802");

from student 
insert overwrite table student1 partition(stat_date="20120802") 
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資料:

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是抽樣語句,文法:TABLESAMPLE(BUCKET x OUT OF y) y必須是table總bucket數的倍數或者因子。hive根據y的大小,決定抽樣的比例。例如,table總共分了64份,當y=32時,抽取(64/32=)2個bucket的資料,當y=128時,抽取(64/128=)1/2個bucket的資料。x表示從哪個bucket開始抽取。例如,table總bucket數為32,tablesample(bucket 3 out of 16),表示總共抽取(32/16=)2個bucket的資料,分别為第3個bucket和第(3+16=)19個bucket的資料。

4、Refer:

http://rdc.taobao.org/?p=1457  從MR到Hive – 一次遷移的過程

http://blog.573114.com/Blog/Html/A031/516857.html  Hadoop權威指南 第12章 Hive簡介 P384

http://superlxw1234.iteye.com/blog/1545150  hive--Sort Merge Bucket Map Join

http://blog.csdn.net/yfkiss/article/details/7816916

本文參與騰訊雲自媒體分享計劃,歡迎正在閱讀的你也加入,一起分享。

繼續閱讀