全排序
Hive的排序關鍵字是SORT BY,它有意區別於傳統數據庫的ORDER BY也是爲了強調兩者的區別–SORT BY只能在單機範圍內排序。考慮以下表定義:
CREATE TABLE if not exists t_order( id int, -- 訂單編號 sale_id int, -- 銷售ID customer_id int, -- 客戶ID product _id int, -- 產品ID amount int -- 數量 ) PARTITIONED BY (ds STRING);
在表中查詢所有銷售記錄,並按照銷售ID和數量排序:
set mapred.reduce.tasks=2; Select sale_id, amount from t_order Sort by sale_id, amount;
這一查詢可能得到非期望的排序。指定的2個reducer分發到的數據可能是(各自排序):
Reducer1:
Sale_id | amount 0 | 100 1 | 30 1 | 50 2 | 20
Reducer2:
Sale_id | amount 0 | 110 0 | 120 3 | 50 4 | 20
因爲上述查詢沒有reduce key,hive會生成隨機數作爲reduce key。這樣的話輸入記錄也隨機地被分發到不同reducer機器上去了。爲了保證reducer之間沒有重複的sale_id記錄,可以使用DISTRIBUTE BY關鍵字指定分發key爲sale_id。改造後的HQL如下:
set mapred.reduce.tasks=2; Select sale_id, amount from t_order Distribute by sale_id Sort by sale_id, amount;
這樣能夠保證查詢的銷售記錄集合中,銷售ID對應的數量是正確排序的,但是銷售ID不能正確排序,原因是hive使用hadoop默認的HashPartitioner分發數據。
這就涉及到一個全排序的問題。解決的辦法無外乎兩種:
1.) 不分發數據,使用單個reducer:
set mapred.reduce.tasks=1;
這一方法的缺陷在於reduce端成爲了性能瓶頸,而且在數據量大的情況下一般都無法得到結果。但是實踐中這仍然是最常用的方法,原因是通常排序的查詢是爲了得到排名靠前的若干結果,因此可以用limit子句大大減少數據量。使用limit n後,傳輸到reduce端(單機)的數據記錄數就減少到n* (map個數)。
2.) 修改Partitioner,這種方法可以做到全排序。這裏可以使用Hadoop自帶的TotalOrderPartitioner(來自於Yahoo!的TeraSort項目),這是一個爲了支持跨reducer分發有序數據開發的Partitioner,它需要一個SequenceFile格式的文件指定分發的數據區間。如果我們已經生成了這一文件(存儲在/tmp/range_key_list,分成100個reducer),可以將上述查詢改寫爲
set mapred.reduce.tasks=100; set hive.mapred.partitioner=org.apache.hadoop.mapred.lib.TotalOrderPartitioner; set total.order.partitioner.path=/tmp/ range_key_list; Select sale_id, amount from t_order Cluster by sale_id Sort by amount;
有很多種方法生成這一區間文件(例如hadoop自帶的o.a.h.mapreduce.lib.partition.InputSampler工具)。這裏介紹用Hive生成的方法,例如有一個按id有序的t_sale表:
CREATE TABLE if not exists t_sale ( id int, name string, loc string );
則生成按sale_id分發的區間文件的方法是:
create external table range_keys(sale_id int) row format serde 'org.apache.hadoop.hive.serde2.binarysortable.BinarySortableSerDe' stored as inputformat 'org.apache.hadoop.mapred.TextInputFormat' outputformat 'org.apache.hadoop.hive.ql.io.HiveNullValueSequenceFileOutputFormat' location '/tmp/range_key_list'; insert overwrite table range_keys select distinct sale_id from source t_sale sampletable(BUCKET 100 OUT OF 100 ON rand()) s sort by sale_id;
生成的文件(/tmp/range_key_list目錄下)可以讓TotalOrderPartitioner按sale_id有序地分發reduce處理的數據。區間文件需要考慮的主要問題是數據分發的均衡性,這有賴於對數據深入的理解。
測試案例:
數據 140g, 按照字段time 降序排列 選出最大的前50個。
使用 一般方法 select * from table order by time desc limit 50. 執行了1小時6分鐘完全算出。
任務數1個 map數 1783 reduce 1
而 select * from (select * from table distribute by time sort by time desc limit 50 ) t order by time desc limit 50;
需要5分鐘算出。結果一致。
任務數2個 分別是:
map 1783 reduce 245
map 245 reduce 1