Fetch抓取(Hive可以避免進行MapReduce)
Hive中對某些情況的查詢可以不必使用MapReduce計算。例如在查詢表所有數據上,Hive可以簡單地讀取表對應的存儲目錄下的文件,然後輸出查詢結果到控制檯。
在hive-default.xml.template文件中hive.fetch.task.conversion默認是more,老版本hive默認是minimal,該屬性修改爲more以後,在全局查找、字段查找、limit查找等都不走mapreduce。
1)把hive.fetch.task.conversion設置成none,然後執行查詢語句,都會執行mapreduce程序。
hive (default)> set hive.fetch.task.conversion=none;
hive (default)> select * from score;
hive (default)> select s_score from score;
hive (default)> select s_score from score limit 3;
2)把hive.fetch.task.conversion設置成more,然後執行查詢語句,如下查詢方式都不會執行mapreduce程序。
hive (default)> set hive.fetch.task.conversion=more;
hive (default)> select * from score;
hive (default)> select s_score from score;
hive (default)> select s_score from score limit 3;
本地模式:
對於小數據集,使用本地模式執行時間可以明顯被縮短。
查詢觸發執行任務時消耗可能會比實際job的執行時間要多的多。對於大多數這種情況,Hive可以通過本地模式在單臺機器上處理所有的任務。
設置hive.exec.mode.local.auto的值爲true,來讓Hive在適當的時候自動啓動這個優化。
set hive.exec.mode.local.auto=true; //開啓本地mr //設置local mr的最大輸入數據量,當輸入數據量小於這個值時採用local mr的方式,默認爲134217728,即128M set hive.exec.mode.local.auto.inputbytes.max=51234560; //設置local mr的最大輸入文件個數,當輸入文件個數小於這個值時採用local mr的方式,默認爲4 set hive.exec.mode.local.auto.input.files.max=10; |
案例實操:
1)開啓本地模式,並執行查詢語句
hive (default)> set hive.exec.mode.local.auto=true;
hive (default)> select * from score cluster by s_id;
查詢時間顯示:18 rows selected (1.568 seconds)
2)關閉本地模式,並執行查詢語句
hive (default)> set hive.exec.mode.local.auto=false;
hive (default)> select * from score cluster by s_id;
查詢時間顯示: 18 rows selected (11.865 seconds)
Group By:
默認情況下,Map階段同一Key數據分發給一個reduce,當一個key數據過大時就傾斜了。
並不是所有的聚合操作都需要在Reduce端完成,很多聚合操作都可以先在Map端進行部分聚合,最後在Reduce端得出最終結果。
1)開啓Map端聚合參數設置
(1)是否在Map端進行聚合,默認爲True
set hive.map.aggr = true;
(2)在Map端進行聚合操作的條目數目
set hive.groupby.mapaggr.checkinterval = 100000;
(3)有數據傾斜的時候進行負載均衡(默認是false)
set hive.groupby.skewindata = true;
當選項設定爲 true,生成的查詢計劃會有兩個MR Job。
第一個MR Job中,Map的輸出結果會隨機分佈到Reduce中,每個Reduce做部分聚合操作,並輸出結果,這樣處理的結果是相同的Group By Key有可能被分發到不同的Reduce中,從而達到負載均衡的目的;第二個MR Job再根據預處理的數據結果按照Group By Key分佈到Reduce中(這個過程可以保證相同的Group By Key被分佈到同一個Reduce中),最後完成最終的聚合操作。
Count(distinct)
數據量小的時候無所謂,數據量大的情況下,由於COUNT DISTINCT操作需要用一個Reduce Task來完成,這一個Reduce需要處理的數據量太大,就會導致整個Job很難完成,一般COUNT DISTINCT使用先GROUP BY再COUNT的方式替換:
環境準備:
create table bigtable(id bigint, time bigint, uid string, keyword string, url_rank int, click_num int, click_url string) row format delimited fields terminated by '\t';
load data local inpath '/home/admin/softwares/data/100萬條大表數據(id除以10取整)/bigtable' into table bigtable; |
set hive.exec.reducers.bytes.per.reducer=32123456; SELECT count(DISTINCT id) FROM bigtable; 結果: c0 10000 Time taken: 35.49 seconds, Fetched: 1 row(s) |
可以轉換成:
set hive.exec.reducers.bytes.per.reducer=32123456; SELECT count(id) FROM (SELECT id FROM bigtable GROUP BY id) a; 結果: Stage-Stage-1: Map: 1 Reduce: 4 Cumulative CPU: 13.07 sec HDFS Read: 120749896 HDFS Write: 464 SUCCESS Stage-Stage-2: Map: 3 Reduce: 1 Cumulative CPU: 5.14 sec HDFS Read: 8987 HDFS Write: 7 SUCCESS _c0 10000 Time taken: 51.202 seconds, Fetched: 1 row(s) |
雖然會多用一個Job來完成,但在數據量大的情況下,這個絕對是值得的。
笛卡爾積
儘量避免笛卡爾積,即避免join的時候不加on條件,或者無效的on條件,Hive只能使用1個reducer來完成笛卡爾積。
使用分區剪裁、列剪裁
在SELECT中,只拿需要的列,如果有,儘量使用分區過濾,少用SELECT *。
在分區剪裁中,當使用外關聯時,如果將副表的過濾條件寫在Where後面,那麼就會先全表關聯,之後再過濾
分桶
將數據按照指定的字段進行分成多個桶中去,說白了就是將數據按照字段進行劃分,可以將數據按照字段劃分到多個文件當中去
數據傾斜:
Map數:
1)通常情況下,作業會通過input的目錄產生一個或者多個map任務。
主要的決定因素有:input的文件總個數,input的文件大小,集羣設置的文件塊大小(目前爲128M,可在hive中通過set dfs.block.size;命令查看到,該參數不能自定義修改);
2)舉例:
a)一個大文件: 假設input目錄下有1個文件a,大小爲780M,那麼hadoop會將該文件a分隔成7個塊(6個128m的塊和1個12m的塊),從而產生7個map數。
b) 多個小文件:假設input目錄下有3個文件a,b,c大小分別爲10m,20m,150m,那麼hadoop會分隔成4個塊(10m,20m,128m,22m),從而產生4個map數。即,如果文件大於塊大小(128m),那麼會拆分,如果小於塊大小,則把該文件當成一個塊。
3)是不是map數越多越好?
答案是否定的。如果一個任務有很多小文件(遠遠小於塊大小128m),則每個小文件也會被當做一個塊,用一個map任務來完成,而一個map任務啓動和初始化的時間遠遠大於邏輯處理的時間,就會造成很大的資源浪費。而且,同時可執行的map數是受限的。
4)是不是保證每個map處理接近128m的文件塊,就高枕無憂了?
答案也是不一定。比如有一個127m的文件,正常會用一個map去完成,但這個文件只有一個或者兩個字段,卻有幾千萬的記錄,如果map處理的邏輯比較複雜,用一個map任務去做,肯定也比較耗時。
針對上面的問題3和4,我們需要採取兩種方式來解決:即減少map數和增加map數;
如何適當的增加map數
當input的文件都很大,任務邏輯複雜,map執行非常慢的時候,可以考慮增加Map數,來使得每個map處理的數據量減少,從而提高任務的執行效率。
針對上面的第4條
假設有這樣一個任務:
Select data_desc, count(1), count(distinct id), sum(case when …), sum(case when …), sum(…) from a group by data_desc |
如果表a只有一個文件,大小爲120M,但包含幾千萬的記錄,如果用1個map去完成這個任務,肯定是比較耗時的,這種情況下,我們要考慮將這一個文件合理的拆分成多個,這樣就可以用多個map任務去完成。
set mapreduce.job.reduces =10; create table a_1 as select * from a distribute by rand(); |
這樣會將a表的記錄,隨機的分散到包含10個文件的a_1表中,再用a_1代替上面sql中的a表,則會用10個map任務去完成。
每個map任務處理大於12M(幾百萬記錄)的數據,效率肯定會好很多。
看上去,貌似這兩種有些矛盾,一個是要合併小文件,一個是要把大文件拆成小文件,這點正是重點需要關注的地方,根據實際情況,控制map數量需要遵循兩個原則:使大數據量利用合適的map數;使單個map任務處理合適的數據量;
reduce數
1)調整reduce個數方法一
(1)每個Reduce處理的數據量默認是256MB
hive.exec.reducers.bytes.per.reducer=256123456
(2)每個任務最大的reduce數,默認爲1009
hive.exec.reducers.max=1009
(3)計算reducer數的公式
N=min(參數2,總輸入數據量/參數1)
2)調整reduce個數方法二
在hadoop的mapred-default.xml文件中修改
設置每個job的Reduce個數
set mapreduce.job.reduces = 15;
3)reduce個數並不是越多越好
1)過多的啓動和初始化reduce也會消耗時間和資源;
2)另外,有多少個reduce,就會有多少個輸出文件,如果生成了很多個小文件,那麼如果這些小文件作爲下一個任務的輸入,則也會出現小文件過多的問題;
在設置reduce個數的時候也需要考慮這兩個原則:處理大數據量利用合適的reduce數;使單個reduce任務處理數據量大小要合適;
並行執行
Hive會將一個查詢轉化成一個或者多個階段,某個特定的job可能包含衆多的階段,而這些階段可能並非完全互相依賴的,這些階段是可以並行執行,這樣可能使得整個job的執行時間縮短。不過,如果有更多的階段可以並行執行,那麼job可能就越快完成。
通過設置參數hive.exec.parallel值爲true,就可以開啓併發執行。不過,在共享集羣中,需要注意下,如果job中並行階段增多,那麼集羣利用率就會增加。
set hive.exec.parallel=true; //打開任務並行執行 set hive.exec.parallel.thread.number=16; //同一個sql允許最大並行度,默認爲8。 |
當然,得是在系統資源比較空閒的時候纔有優勢,否則,沒資源,並行也起不來。
嚴格模式
Hive提供了一個嚴格模式,可以防止用戶執行“高危”的查詢。
通過設置屬性hive.mapred.mode值爲默認是非嚴格模式nonstrict 。開啓嚴格模式需要修改hive.mapred.mode值爲strict,開啓嚴格模式可以禁止3種類型的查詢。
<property> <name>hive.mapred.mode</name> <value>strict</value> <description> The mode in which the Hive operations are being performed. In strict mode, some risky queries are not allowed to run. They include: Cartesian Product. No partition being picked up for a query. Comparing bigints and strings. Comparing bigints and doubles. Orderby without limit. </description> </property> |
- 對於分區表,用戶不允許掃描所有分區,除非where語句中含有分區字段過濾條件來限制範圍,否則不允許執行。進行這個限制的原因是,通常分區表都擁有非常大的數據集,而且數據增加迅速。沒有進行分區限制的查詢可能會消耗令人不可接受的巨大資源來處理這個表。
- 對於使用了order by語句的查詢,要求必須使用limit語句。因爲order by爲了執行排序過程會將所有的結果數據分發到同一個Reducer中進行處理,強制要求用戶增加這個LIMIT語句可以防止Reducer額外執行很長一段時間。
- 限制笛卡爾積的查詢。對關係型數據庫非常瞭解的用戶可能期望在執行JOIN查詢的時候不使用ON語句而是使用where語句,這樣關係數據庫的執行優化器就可以高效地將WHERE語句轉化成那個ON語句。不幸的是,Hive並不會執行這種優化,因此,如果表足夠大,那麼這個查詢就會出現不可控的情況。
JVM重用
JVM重用是Hadoop調優參數的內容,其對Hive的性能具有非常大的影響,特別是對於很難避免小文件的場景或task特別多的場景,這類場景大多數執行時間都很短。
Hadoop的默認配置通常是使用派生JVM來執行map和Reduce任務的。這時JVM的啓動過程可能會造成相當大的開銷,尤其是執行的job包含有成百上千task任務的情況。JVM重用可以使得JVM實例在同一個job中重新使用N次。N的值可以在Hadoop的mapred-site.xml文件中進行配置。通常在10-20之間,具體多少需要根據具體業務場景測試得出。
<property> <name>mapreduce.job.jvm.numtasks</name> <value>10</value> <description>How many tasks to run per jvm. If set to -1, there is no limit. </description> </property> |
我們也可以在hive當中通過
set mapred.job.reuse.jvm.num.tasks=10;
這個設置來設置我們的jvm重用
這個功能的缺點是,開啓JVM重用將一直佔用使用到的task插槽,以便進行重用,直到任務完成後才能釋放。如果某個“不平衡的”job中有某幾個reduce task執行的時間要比其他Reduce task消耗的時間多的多的話,那麼保留的插槽就會一直空閒着卻無法被其他的job使用,直到所有的task都結束了纔會釋放。
推測執行
在分佈式集羣環境下,因爲程序Bug(包括Hadoop本身的bug),負載不均衡或者資源分佈不均等原因,會造成同一個作業的多個任務之間運行速度不一致,有些任務的運行速度可能明顯慢於其他任務(比如一個作業的某個任務進度只有50%,而其他所有任務已經運行完畢),則這些任務會拖慢作業的整體執行進度。爲了避免這種情況發生,Hadoop採用了推測執行(Speculative Execution)機制,它根據一定的法則推測出“拖後腿”的任務,併爲這樣的任務啓動一個備份任務,讓該任務與原始任務同時處理同一份數據,並最終選用最先成功運行完成任務的計算結果作爲最終結果。
Hive 同樣可以開啓推測執行
設置開啓推測執行參數:Hadoop的mapred-site.xml文件中進行配置
<property> <name>mapreduce.map.speculative</name> <value>true</value> <description>If true, then multiple instances of some map tasks may be executed in parallel.</description> </property>
<property> <name>mapreduce.reduce.speculative</name> <value>true</value> <description>If true, then multiple instances of some reduce tasks may be executed in parallel.</description> </property> |
不過hive本身也提供了配置項來控制reduce-side的推測執行:
<property> <name>hive.mapred.reduce.tasks.speculative.execution</name> <value>true</value> <description>Whether speculative execution for reducers should be turned on. </description> </property> |
關於調優這些推測執行變量,還很難給一個具體的建議。如果用戶對於運行時的偏差非常敏感的話,那麼可以將這些功能關閉掉。如果用戶因爲輸入數據量很大而需要執行長時間的map或者Reduce task的話,那麼啓動推測執行造成的浪費是非常巨大大。
表的優化
Join
Join原則:
1)小表Join大表,
將key相對分散,並且數據量小的表放在join的左邊,這樣可以有效減少內存溢出錯誤發生的機率;再進一步,可以使用Group讓小的維度表(1000條以下的記錄條數)先進內存。在map端完成reduce。
select count(distinct s_id) from score;
select count(s_id) from score group by s_id; 在map端進行聚合,效率更高
2)多個表關聯時,最好分拆成小段,避免大sql(無法控制中間Job)
3)大表Join大表
(1)空KEY過濾
有時join超時是因爲某些key對應的數據太多,而相同key對應的數據都會發送到相同的reducer上,從而導致內存不夠。此時我們應該仔細分析這些異常的key,很多情況下,這些key對應的數據是異常數據,我們需要在SQL語句中進行過濾。例如key對應的字段爲空,操作如下:
環境準備:
create table ori(id bigint, time bigint, uid string, keyword string, url_rank int, click_num int, click_url string) row format delimited fields terminated by '\t';
create table nullidtable(id bigint, time bigint, uid string, keyword string, url_rank int, click_num int, click_url string) row format delimited fields terminated by '\t';
create table jointable(id bigint, time bigint, uid string, keyword string, url_rank int, click_num int, click_url string) row format delimited fields terminated by '\t';
load data local inpath '/export/servers/hivedatas/hive_big_table/*' into table ori; load data local inpath '/export/servers/hivedatas/hive_have_null_id/*' into table nullidtable; |
不過濾:
INSERT OVERWRITE TABLE jointable SELECT a.* FROM nullidtable a JOIN ori b ON a.id = b.id; 結果: No rows affected (152.135 seconds) |
過濾:
INSERT OVERWRITE TABLE jointable SELECT a.* FROM (SELECT * FROM nullidtable WHERE id IS NOT NULL ) a JOIN ori b ON a.id = b.id; 結果: No rows affected (141.585 seconds) |
(2)空key轉換
有時雖然某個key爲空對應的數據很多,但是相應的數據不是異常數據,必須要包含在join的結果中,此時我們可以表a中key爲空的字段賦一個隨機的值,使得數據隨機均勻地分不到不同的reducer上。例如:
不隨機分佈:
set hive.exec.reducers.bytes.per.reducer=32123456; set mapreduce.job.reduces=7; INSERT OVERWRITE TABLE jointable SELECT a.* FROM nullidtable a LEFT JOIN ori b ON CASE WHEN a.id IS NULL THEN 'hive' ELSE a.id END = b.id; No rows affected (41.668 seconds) 52.477 |
結果:這樣的後果就是所有爲null值的id全部都變成了相同的字符串“hive”,及其容易造成數據的傾斜(所有的key相同,相同key的數據會到同一個reduce當中去)
爲了解決這種情況,我們可以通過hive的rand函數,隨記的給每一個爲空的id賦上一個隨機值,這樣就不會造成數據傾斜
隨機分佈:
set hive.exec.reducers.bytes.per.reducer=32123456; set mapreduce.job.reduces=7; INSERT OVERWRITE TABLE jointable SELECT a.* FROM nullidtable a LEFT JOIN ori b ON CASE WHEN id IS NULL THEN concat('hive', rand()) ELSE a.id END = b.id; No rows affected (42.594 seconds) |
4)案例實操
(0)需求:測試大表JOIN小表和小表JOIN大表的效率 (新的版本當中已經沒有區別了,舊的版本當中需要使用小表)
(1)建大表、小表和JOIN後表的語句
create table bigtable(id bigint, time bigint, uid string, keyword string, url_rank int, click_num int, click_url string) row format delimited fields terminated by '\t';
create table smalltable(id bigint, time bigint, uid string, keyword string, url_rank int, click_num int, click_url string) row format delimited fields terminated by '\t';
create table jointable2(id bigint, time bigint, uid string, keyword string, url_rank int, click_num int, click_url string) row format delimited fields terminated by '\t'; |
(2)分別向大表和小表中導入數據
hive (default)> load data local inpath '/export/servers/hivedatas/big_data' into table bigtable;
hive (default)>load data local inpath '/export/servers/hivedatas/small_data' into table smalltable;
(3)關閉mapjoin功能(默認是打開的)
set hive.auto.convert.join = false;
(4)執行小表JOIN大表語句
INSERT OVERWRITE TABLE jointable2 SELECT b.id, b.time, b.uid, b.keyword, b.url_rank, b.click_num, b.click_url FROM smalltable s left JOIN bigtable b ON b.id = s.id; |
Time taken: 67.411 seconds
(5)執行大表JOIN小表語句
INSERT OVERWRITE TABLE jointable2 SELECT b.id, b.time, b.uid, b.keyword, b.url_rank, b.click_num, b.click_url FROM bigtable b left JOIN smalltable s ON s.id = b.id; |
Time taken: 69.376seconds
可以看出大表join小表或者小表join大表,就算是關閉map端join的情況下,在新的版本當中基本上沒有區別了(hive爲了解決數據傾斜的問題,會自動進行過濾)
MapJoin
如果不指定MapJoin或者不符合MapJoin的條件,那麼Hive解析器會將Join操作轉換成Common Join(在Reduce階段完成join)。容易發生數據傾斜。可以用MapJoin把小表全部加載到內存在map端進行join,避免reducer處理。
1)開啓MapJoin參數設置:
(1)設置自動選擇Mapjoin
set hive.auto.convert.join = true; 默認爲true
(2)大表小表的閾值設置(默認25M以下認爲是小表):
set hive.mapjoin.smalltable.filesize=25123456;
2)MapJoin工作機制
首先是Task A,它是一個Local Task(在客戶端本地執行的Task),負責掃描小表b的數據,將其轉換成一個HashTable的數據結構,並寫入本地的文件中,之後將該文件加載到DistributeCache中。
接下來是Task B,該任務是一個沒有Reduce的MR,啓動MapTasks掃描大表a,在Map階段,根據a的每一條記錄去和DistributeCache中b表對應的HashTable關聯,並直接輸出結果。
由於MapJoin沒有Reduce,所以由Map直接輸出結果文件,有多少個Map Task,就有多少個結果文件。
案例實操:
(1)開啓Mapjoin功能
set hive.auto.convert.join = true; 默認爲true
(2)執行小表JOIN大表語句
INSERT OVERWRITE TABLE jointable2 SELECT b.id, b.time, b.uid, b.keyword, b.url_rank, b.click_num, b.click_url FROM smalltable s JOIN bigtable b ON s.id = b.id; |
Time taken: 31.814 seconds
(3)執行大表JOIN小表語句
INSERT OVERWRITE TABLE jointable2 SELECT b.id, b.time, b.uid, b.keyword, b.url_rank, b.click_num, b.click_url FROM bigtable b JOIN smalltable s ON s.id = b.id; |
Time taken: 28.46 seconds