hive 全排序優化

全排序

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;

這一查詢可能得到非期望的排序。指定的2reducer分發到的數據可能是(各自排序):

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 keyhive會生成隨機數作爲reduce key。這樣的話輸入記錄也隨機地被分發到不同reducer機器上去了。爲了保證reducer之間沒有重複的sale_id記錄,可以使用DISTRIBUTE BY關鍵字指定分發keysale_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,分成100reducer),可以將上述查詢改寫爲

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目錄下)可以讓TotalOrderPartitionersale_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


發佈了72 篇原創文章 · 獲贊 9 · 訪問量 72萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章