一、 常用參數優化
參數名 | 參數釋義和用法 |
---|---|
列裁剪和分區裁剪 | 列裁剪就是在查詢時只讀取需要的列,分區裁剪就是隻讀取需要的分區。解析階段對應的則是ColumnPruner邏輯優化器 |
hive.optimize.cp | True(默認) |
hive.optimize.pruner | True(默認) |
謂詞下推 | 在關係型數據庫如MySQL中,也有謂詞下推(Predicate Pushdown,PPD)的概念。它就是將SQL語句中的where謂詞邏輯都儘可能提前執行,減少下游處理的數據量。對應邏輯優化器是PredicatePushDown |
hive.optimize.ppd | true |
group by配置調整 | |
hive.map.aggr(map端聚合) | 默認 true,並不是所有的聚合操作都需要在reduce部分進行,很多聚合操作都可以先在Map端進行部分聚合,然後reduce端得出最終結果。 對應的優化器爲GroupByOptimizer |
hive.groupby.mapaggr.checkinterval | 設置map端預聚合的行數閾值,超過該值就會分拆job,默認值100000 |
hive.groupby.skewindata | false, 解釋:在group by時啓動兩個MR job。第一個job會將map端數據隨機輸入reducer,每個reducer做部分聚合,相同的key就會分佈在不同的reducer中。第二個job再將前面預處理過的數據按key聚合並輸出結果,這樣就起到了均衡的效果。 |
啓用壓縮 | 壓縮job的中間結果數據和輸出數據,可以用少量CPU時間節省很多空間。壓縮方式一般選擇Snappy,效率最高。 |
hive.exec.compress.intermediate | default is false |
hive.intermediate.compression.codec | org.apache.hadoop.io.compress.SnappyCodec |
hive.intermediate.compression.type | 可以選擇對塊(BLOCK)還是記錄(RECORD)壓縮,BLOCK的壓縮率比較高。 |
hive.exec.compress.output=true | 輸出壓縮配置項 |
Join基礎優化 | |
build table(小表)前置 | 小表叫build table,大表叫probe table Hive在解析帶join的SQL語句時,會默認將最後一個表作爲probe table,將前面的表作爲build table並試圖將它們讀進內存。如果表順序寫反,probe table在前面,引發OOM的風險就高了 |
多表join時key相同 | 這種情況會將多個join合併爲一個MR job來處理,負責這個的是相關性優化器CorrelationOptimizer,參考wiki |
利用map join特性 | map join特別適合大小表join的情況。Hive會將build table和probe table在map端直接完成join過程,消滅了reduce,效率很高。 |
hive.auto.convert.join | 默認值true,對應邏輯優化器是MapJoinProcessor。 |
hive.mapjoin.smalltable.filesize | 默認值25000000(25MB) |
hive.mapjoin.cache.numrows | 表示緩存build table的多少行數據到內存,默認值25000。 |
傾斜均衡配置項 | |
hive.optimize.skewjoin | False |
hive.skewjoin.key | 如果開啓了,在join過程中Hive會將計數超過閾值hive.skewjoin.key (默認100000)的傾斜key對應的行臨時寫進文件中,然後再啓動另一個job做map join生成結果。通過hive.skewjoin.mapjoin.map.tasks 參數還可以控制第二個job的mapper數量,默認10000。 |
使用向量化查詢 | 向量化查詢執行通過一次性批量執行1024行而不是每次單行執行,從而提供掃描、聚合、篩選器和連接等操作的性能。在Hive 0.13中引入,此功能顯着提高了查詢執行時間,並可通過兩個參數設置輕鬆啓用: |
hive.vectorized.execution.enabled | true |
hive.vectorizedexecution.reduce.enabled | true |
CBO(cost based query optimization) | |
自動優化HQL中多個JOIN的順序,並選擇合適的JOIN算法 | hive.cbo.enable = true; hive.compute.query.using.stats = true; hive.stats.fetch.column.stats = true; hive.stats.fetch.partition.stats = true; |
推測執行 | |
Hadoop推測執行可以觸發執行一些重複的任務,儘管因對重複的數據進行計算而導致消耗更多的計算資源,不過這個功能的目標是通過加快獲取單個task的結果以偵測執行慢的TaskTracker加入到沒名單的方式來提高整體的任務執行效率。Hadoop的推測執行功能由2個配置控制着,通過mapred-site.xml中配置 | mapred.map.tasks.speculative.execution=true mapred.reduce.tasks.speculative.execution=true |
二、SQL語法相關的優化
-
sort by代替order by
HiveSQL中的order by與其他SQL方言中的功能一樣,就是將結果按某字段全局排序,這會導致所有map端數據都進入一個reducer中,在數據量大時可能會長時間計算不完。如果使用sort by,那麼還是會視情況啓動多個reducer進行排序,並且保證每個reducer內局部有序。爲了控制map端數據分配到reducer的key,往往還要配合distribute by一同使用。如果不加distribute by的話,map端數據就會隨機分配到reducer。
舉個例子,假如要以UID爲key,以上傳時間倒序、記錄類型倒序輸出記錄數據。select uid,upload_time,event_type,record_data from calendar_record_log where pt_date >= 20190201 and pt_date <= 20190224 distribute by uid sort by upload_time desc,event_type desc;
-
group by代替distinct
當要統計某一列的去重數時,如果數據量很大,count(distinct)就會非常慢,原因與order by類似,count(distinct)邏輯只會有很少的reducer來處理。這時可以用group by來改寫:
select count(1) from ( select uid from calendar_record_log where pt_date >= 20190101 group by uid ) t;
這樣寫會啓動兩個MR job(單純distinct只會啓動一個),所以要確保數據量大到啓動job的overhead遠小於計算耗時,才考慮這種方法。
-
優化SQL處理join數據傾斜
空值或無意義值
這種情況很常見,比如當事實表是日誌類數據時,往往會有一些項沒有記錄到,我們視情況會將它置爲null,或者空字符串、-1等。如果缺失的項很多,在做join時這些空值就會非常集中,拖累進度。
因此,若不需要空值數據,就提前寫where語句過濾掉。需要保留的話,將空值key用隨機方式打散,例如將用戶ID爲null的記錄隨機改爲負值:
select a.uid,a.event_type,b.nickname,b.age from ( select (case when uid is null then cast(rand()*-10240 as int) else uid end) as uid, event_type from calendar_record_log where pt_date >= 20190201 ) a left outer join ( select uid,nickname,age from user_info where status = 4 ) b on a.uid = b.uid;
單獨處理傾斜key
這其實是上面處理空值方法的拓展,不過傾斜的key變成了有意義的。一般來講傾斜的key都很少,我們可以將它們抽樣出來,對應的行單獨存入臨時表中,然後打上一個較小的隨機數前綴(比如0~9),最後再進行聚合。
不同數據類型
這種情況不太常見,主要出現在相同業務含義的列發生過邏輯上的變化時。 舉個例子,假如我們有一舊一新兩張日曆記錄表,舊錶的記錄類型字段是(event_type int),新表的是(event_type string)。爲了兼容舊版記錄,新表的event_type也會以字符串形式存儲舊版的值,比如’17’。當這兩張表join時,經常要耗費很長時間。其原因就是如果不轉換類型,計算key的hash值時默認是以int型做的,這就導致所有“真正的”string型key都分配到一個reducer上。所以要注意類型轉換:
select a.uid,a.event_type,b.record_data from calendar_record_log a left outer join ( select uid,event_type from calendar_record_log_2 where pt_date = 20190228 ) b on a.uid = b.uid and b.event_type = cast(a.event_type as string) where a.pt_date = 20190228;
build table過大
有時,build table會大到無法直接使用map join的地步,比如全量用戶維度表,而使用普通join又有數據分佈不均的問題。這時就要充分利用probe table的限制條件,削減build table的數據量,再使用map join解決。代價就是需要進行兩次join。舉個例子:
select /*+mapjoin(b)*/ a.uid,a.event_type,b.status,b.extra_info from calendar_record_log a left outer join ( select /*+mapjoin(s)*/ t.uid,t.status,t.extra_info from (select distinct uid from calendar_record_log where pt_date = 20190228) s inner join user_info t on s.uid = t.uid ) b on a.uid = b.uid where a.pt_date = 20190228;
-
MapReduce優化
調整mapper數
mapper數量與輸入文件的split數息息相關,在Hadoop源碼
org.apache.hadoop.mapreduce.lib.input.FileInputFormat
類中可以看到split劃分的具體邏輯。這裏不貼代碼,直接敘述mapper數是如何確定的。- 可以直接通過參數
mapred.map.tasks
(默認值2)來設定mapper數的期望值,但它不一定會生效,下面會提到。 - 設輸入文件的總大小爲
total_input_size
。HDFS中,一個塊的大小由參數dfs.block.size
指定,默認值64MB或128MB。在默認情況下,mapper數就是:default_mapper_num = total_input_size / dfs.block.size
。 - 參數
mapred.min.split.size
(默認值1B)和mapred.max.split.size
(默認值64MB)分別用來指定split的最小和最大大小。split大小和split數計算規則是:split_size = MAX(mapred.min.split.size, MIN(mapred.max.split.size, dfs.block.size))
;split_num = total_input_size / split_size
。 - 得出mapper數:
mapper_num = MIN(split_num, MAX(default_num, mapred.map.tasks))
。
可見,如果想減少mapper數,就適當調高
mapred.min.split.size
,split數就減少了。如果想增大mapper數,除了降低mapred.min.split.size
之外,也可以調高mapred.map.tasks
。一般來講,如果輸入文件是少量大文件,就減少mapper數;如果輸入文件是大量非小文件,就增大mapper數;至於大量小文件的情況,則需要合併小文件後再處理。
調整reducer數
reducer數量的確定方法比mapper簡單得多。使用參數
mapred.reduce.tasks
可以直接設定reducer數量,不像mapper一樣是期望值。但如果不設這個參數的話,Hive就會自行推測,邏輯如下:- 參數
hive.exec.reducers.bytes.per.reducer
用來設定每個reducer能夠處理的最大數據量,默認值1G(1.2版本之前)或256M(1.2版本之後)。 - 參數
hive.exec.reducers.max
用來設定每個job的最大reducer數量,默認值999(1.2版本之前)或1009(1.2版本之後)。 - 得出reducer數:
reducer_num = MIN(total_input_size / reducers.bytes.per.reducer, reducers.max)
。
reducer數量與輸出文件的數量相關。如果reducer數太多,會產生大量小文件,對HDFS造成壓力。如果reducer數太少,每個reducer要處理很多數據,容易拖慢運行時間或者造成OOM。
合併小文件
- 輸入階段合併 需要更改Hive的輸入文件格式,即參數
hive.input.format
,默認值是org.apache.hadoop.hive.ql.io.HiveInputFormat
,我們改成org.apache.hadoop.hive.ql.io.CombineHiveInputFormat
。 這樣比起上面調整mapper數時,又會多出兩個參數,分別是mapred.min.split.size.per.node
和mapred.min.split.size.per.rack
,含義是單節點和單機架上的最小split大小。如果發現有split大小小於這兩個值(默認都是100MB),則會進行合併。具體邏輯可以參看Hive源碼中的對應類。 - 輸出階段合併 直接將
hive.merge.mapfiles
和hive.merge.mapredfiles
都設爲true即可,前者表示將map-only任務的輸出合併,後者表示將map-reduce任務的輸出合併。 另外,hive.merge.size.per.task
可以指定每個task輸出後合併文件大小的期望值,hive.merge.size.smallfiles.avgsize
可以指定所有輸出文件大小的均值閾值,默認值都是1GB。如果平均大小不足的話,就會另外啓動一個任務來進行合併。
啓用壓縮
壓縮job的中間結果數據和輸出數據,可以用少量CPU時間節省很多空間。壓縮方式一般選擇Snappy,效率最高。
Job輸出文件按照block以Gzip的方式進行壓縮:
set mapreduce.output.fileoutputformat.compress=true -- 默認值是 false set mapreduce.output.fileoutputformat.compress.type=BLOCK -- 默認值是 Record --可以選擇對塊(BLOCK)還是記錄(RECORD)壓縮,BLOCK的壓縮率比較高 set mapreduce.output.fileoutputformat.compress.codec =org.apache.hadoop.io.compress.GzipCodec --默認值是 org.apache.hadoop.io.compress.DefaultCodec
Map輸出結果也以Gzip進行壓縮:
set mapred.map.output.compress=true set mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.GzipCodec -- 默認值是 org.apache.hadoop.io.compress.DefaultCodec
對Hive輸出結果和中間都進行壓縮:
set hive.exec.compress.output=true --默認值是 false,不壓縮 set hive.exec.compress.intermediate=true --默認值是 false,爲 true 時 MR 設置的壓縮才啓用
JVM重用
在MR job中,默認是每執行一個task就啓動一個JVM。如果task非常小而碎,那麼JVM啓動和關閉的耗時就會很長。可以通過調節參數
mapred.job.reuse.jvm.num.tasks
來重用。例如將這個參數設成5,那麼就代表同一個MR job中順序執行的5個task可以重複使用一個JVM,減少啓動和關閉的開銷。但它對不同MR job中的task無效。並行執行與本地模式
- 並行執行 Hive中互相沒有依賴關係的job間是可以並行執行的,最典型的就是多個子查詢union all。在集羣資源相對充足的情況下,可以開啓並行執行,即將參數
hive.exec.parallel
設爲true。另外hive.exec.parallel.thread.number
可以設定並行執行的線程數,默認爲8,一般都夠用。 - 本地模式 Hive也可以不將任務提交到集羣進行運算,而是直接在一臺節點上處理。因爲消除了提交到集羣的overhead,所以比較適合數據量很小,且邏輯不復雜的任務。 設置
hive.exec.mode.local.auto
爲true可以開啓本地模式。但任務的輸入數據總量必須小於hive.exec.mode.local.auto.inputbytes.max
(默認值128MB),且mapper數必須小於hive.exec.mode.local.auto.tasks.max
(默認值4),reducer數必須爲0或1,纔會真正用本地模式執行。
嚴格模式
所謂嚴格模式,就是強制不允許用戶執行3種有風險的HiveSQL語句,一旦執行會直接失敗。這3種語句是:
- 查詢分區表時不限定分區列的語句;
- 兩表join產生了笛卡爾積的語句;
- 用order by來排序但沒有指定limit的語句。
要開啓嚴格模式,需要將參數==
hive.mapred.mode
==設爲strict。採用合適的存儲格式
在HiveSQL的create table語句中,可以使用
stored as ...
指定表的存儲格式。Hive表支持的存儲格式有TextFile、SequenceFile、RCFile、Avro、ORC、Parquet等。 存儲格式一般需要根據業務進行選擇,在我們的實操中,絕大多數表都採用TextFile與Parquet兩種存儲格式之一。 TextFile是最簡單的存儲格式,它是純文本記錄,也是Hive的默認格式。雖然它的磁盤開銷比較大,查詢效率也低,但它更多地是作爲跳板來使用。RCFile、ORC、Parquet等格式的表都不能由文件直接導入數據,必須由TextFile來做中轉。 Parquet和ORC都是Apache旗下的開源列式存儲格式。列式存儲比起傳統的行式存儲更適合批量OLAP查詢,並且也支持更好的壓縮和編碼。我們選擇Parquet的原因主要是它支持Impala查詢引擎,並且我們對update、delete和事務性操作需求很低。 這裏就不展開講它們的細節,可以參考各自的官網: https://parquet.apache.org/ https://orc.apache.org/ - 可以直接通過參數
-
單個大文件處理辦法
當input的文件都很大,任務邏輯複雜,map執行非常慢的時候,可以考慮增加Map數,
來使得每個map處理的數據量減少,從而提高任務的執行效率。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 mapred.reduce.tasks=10; create table a_1 strored as orc as select * from a distribute by rand(123);
distribute by :用來控制map輸出結果的分發,即map端如何拆分數據給reduce端。 會根據distribute by 後邊定義的列,根據reduce的個數進行數據分發,默認是採用hash算法。當 distribute by 後邊跟的列是:rand()函數時,即表示保證每個分區的數據量基本一致。
- cluster by: 對同一字段分桶並排序,不能和sort by連用;
- distribute by + sort by: 分桶,保證同一字段值只存在一個結果文件當中,結合sort by 保證每個reduceTask結果有序;
- sort by: 單機排序,單個reduce結果有序
- order by:全局排序,缺陷是隻能使用一個reduce
擴展參考:https://blog.csdn.net/qq_40795214/article/details/82190827
-
優化in/exists語句
雖然經過測驗,hive1.2.1也支持in/exists操作,但還是推薦使用hive的一個高效替代方案:left semi join
比如說:
select a.id, a.name from a where a.id in (select b.id from b); select a.id, a.name from a where exists (select id from b where a.id = b.id);
應該轉換成:
select a.id, a.name from a left semi join b on a.id = b.id;
-
補充
三、原理釋義
-
Map join 工作原理釋義
上圖是Hive MapJoin的原理圖,從圖中可以看出MapJoin分爲兩個階段:
(1)通過MapReduce Local Task,將小表讀入內存,生成內存HashTableFiles上傳至Distributed Cache中,這裏會對HashTableFiles進行壓縮。
(2)MapReduce Job在Map階段,每個Mapper從Distributed Cache讀取HashTableFiles到內存中,順序掃描大表,在Map階段直接進行Join,將數據傳遞給下一個MapReduce任務。也就是在map端進行join避免了shuffle。
Join操作在Map階段完成,不再需要Reduce,有多少個Map Task,就有多少個結果文件。 -
Hive SQL Join的實現原理
select u.name, o.orderid from order o join user u on o.uid = u.uid;
在map的輸出value中爲不同表的數據打上tag標記,在reduce階段根據tag判斷數據來源。
-
Hive Group by 實現原理
select rank, isonline, count(*) from city group by rank, isonline;
將GroupBy的字段組合爲map的輸出key值,利用MapReduce的排序,在reduce階段保存LastKey區分不同的key。MapReduce的過程如下(當然這裏只是說明Reduce端的非Hash聚合過程)
-
Distinct的實現原理
select dealid, count(distinct uid) num from order group by dealid;
-
當只有一個distinct字段時,如果不考慮Map階段的Hash GroupBy,只需要將GroupBy字段和Distinct字段組合爲map輸出key,利用mapreduce的排序,同時將GroupBy字段作爲reduce的key,在reduce階段保存LastKey即可完成去重.
-
如果有多個distinct字段呢,如下面的SQL
select dealid, count(distinct uid), count(distinct date) from order group by dealid;
實現方式有兩種:
(1)如果仍然按照上面一個distinct字段的方法,即下圖這種實現方式,無法跟據uid和date分別排序,也就無法通過LastKey去重,仍然需要在reduce階段在內存中通過Hash去重。(2)第二種實現方式,可以對所有的distinct字段編號,每行數據生成n行數據,那麼相同字段就會分別排序,這時只需要在reduce階段記錄LastKey即可去重。
這種實現方式很好的利用了MapReduce的排序,節省了reduce階段去重的內存消耗,但是缺點是增加了shuffle的數據量。需要注意的是,在生成reduce value時,除第一個distinct字段所在行需要保留value值,其餘distinct數據行value字段均可爲空。
簡單解釋下,如上圖,對多字段的去重是從打編號開始的,如 uid=0,date=1,然後每一行按照groupby 字段拆成兩行如下:
<1001,0,1> <1001,1,1101> <1001,0,2> <1001,1,1101> <1001,0,2> <1001,1,1102>
-
-
Hive SQL轉化爲MapReduce的過程
整個編譯過程分爲六個階段:
- Antlr定義SQL的語法規則,完成SQL詞法,語法解析,將SQL轉化爲抽象語法樹AST Tree
- 遍歷AST Tree,抽象出查詢的基本組成單元QueryBlock
- 遍歷QueryBlock,翻譯爲執行操作樹OperatorTree
- 邏輯層優化器進行OperatorTree變換,合併不必要的ReduceSinkOperator,減少shuffle數據量
- 遍歷OperatorTree,翻譯爲MapReduce任務
- 物理層優化器進行MapReduce任務的變換,生成最終的執行計劃
參考:https://www.cnblogs.com/felixzh/p/8604188.html
爲這個圖的作者點個贊,製作精良,通俗易懂。簡述如下,詳情參考鏈接。
【map階段】
1、讀取數據HDFS [map數:輸入文件數目,輸入文件的大小,配置參數]
2、處理數據 (分區-> 環形緩衝區)
分區:默認是通過計算key的hash值後對Reduce task的數量取模獲得
環形緩衝區:Map的輸出結果是由collector處理的,每個Map任務不斷地將鍵值對輸出到在內存中構造的一個環形數據結構中。使用環形數據結構是爲了更有效地使用內存空間,在內存中放置儘可能多的數據
3、寫數據 (Combiner -> spill -> 壓縮 )
【reduce階段】
1、拷貝 (Reduce任務通過HTTP向各個Map任務下載它所需要的數據(網絡傳輸)
2、合併
1)內存到內存(memToMemMerger)
2)內存中Merge(inMemoryMerger)
3)磁盤上的Merge(onDiskMerger)具體包括兩個:(一)Copy過程中磁盤合併(二)磁盤到磁盤
參考:
https://cloud.tencent.com/developer/article/1453464
https://tech.meituan.com/2014/02/12/hive-sql-to-mapreduce.html
https://www.cnblogs.com/swordfall/p/11037539.html