HIVE 优化浅谈
HIVE 優化淺談
hive不怕數據量大,導致運行慢的主要原因是數據傾斜。hive的運行機制這里就不再贅述,咱們直入正題,聊一下hive的優化方法。
優化點一:業務邏輯優化
1.去除冗余邏輯
??對于復雜業務邏輯來說,在非數據傾斜的情況下,最有效的優化方式就是對業務邏輯的優化,去掉冗余的邏輯過程或無用的中間過程,能一步完成的不要分兩步。尤其對于舊邏輯優化及數據遷移工作中較為常見。
2.重復邏輯落臨時表
復雜的業務場景很可能會有復用的邏輯,把重復的邏輯落入臨時表中不僅能減少資源消耗,還能有利于后期的代碼維護。
優化點二:配置合理的參數
1.在hive-site.xml里面有個配置參數叫:
hive.fetch.task.conversion
配置成more,簡單查詢就不走map/reduce了,設置為minimal,就任何簡單select都會走map/reduce;
2.其他見優化點九、十、十四
優化點三:設置合理的map reduce的task數量
1. map task數量設置
mapred.min.split.size: 指的是數據的最小分割單元大小;min的默認值是1B mapred.max.split.size: 指的是數據的最大分割單元大小;max的默認值是128MB 通過調整max可以起到調整map數的作用:減小max可以增加map數,增大max可以減少map數。 *注意*:直接調整mapred.map.tasks這個參數是沒有效果的。??因為map任務啟動和初始化的時間遠遠大于邏輯處理的時間,如果map數過多,就會造成很大的資源浪費,同時可執行的map數是受限的,這樣就會造成運行過慢。如果map數過少,一個map要處理上千萬行的數據也肯定沒有多個map并行處理的快,這種情況也需要優化。所以我們需要合理地調整map數。
1.1 減少map數
??如果數據中有大量文件,每個文件都遠小于128M,為避免每個小文件開啟一個map程序,我們可以通過如下配置合并小文件,從而減少map數:
set mapred.max.split.size=100000000;(100M) set mapred.min.split.size.per.node=100000000;(100M) set mapred.min.split.size.per.rack=100000000;(100M) set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;其中:set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;表示執行前進行小文件合并,
前面三個參數確定合并文件塊的大小,大于文件block大小128m的,按照128m來分隔;小于128m,大于100m的,按照100m來分隔;把那些小于100m的(包括小文件和分隔大文件剩下的)進行合并。
1.2 增加map數
??當input的文件都很大,任務邏輯復雜或者字段少造成行數過多,map執行非常慢的情況下,可以考慮增加Map數,來減少每個map的任務量,從而提高執行效率。
2. reduce task數量設置
??Reduce的個數對整個作業的運行性能有很大影響。如果Reduce數量設置的過多,那么將會產生很多小文件,對NameNode會產生一定的影響,而且整個作業的運行時間未必會減少;如果Reduce設置的過小,那么單個Reduce處理的數據將會加大,很可能會引起OOM異常。
如果設置了mapred.reduce.tasks/mapreduce.job.reduces參數,那么Hive會直接使用它的值作為Reduce的個數;如果mapred.reduce.tasks/mapreduce.job.reduces的值沒有設置(也就是-1),那么Hive會根據輸入文件的大小估算出Reduce的個數。根據輸入文件估算Reduce的個數可能未必很準確,因為Reduce的輸入是Map的輸出,而Map的輸出可能會比輸入要小,所以最準確的數根據Map的輸出估算Reduce的個數。
??不指定reduce個數的情況下,Hive會基于以下兩個設定reduce個數:
計算reducer數的公式很簡單N=min(參數2,總輸入數據量/參數1)
即,如果reduce的輸入(map的輸出)總大小不超過1G,那么只會有一個reduce任務;
2.1 注意 1:reduce個數并不是越多越好;
??同map一樣,啟動和初始化reduce也會消耗時間和資源;
另外,有多少個reduce,就會有個多少個輸出文件,如果生成了很多個小文件,那么如果這些小文件作為下一個任務的輸入,則也會出現小文件過多的問題;
2.2 注意2 :有的邏輯只會產生一個reduce
只有一個reduce的情況也比較多,一種情況是數據量比較少,這種當然無需優化。但有的時候我們會發現不管數據量多大,無論怎么調整reduce個數的參數,任務中一直都只有一個reduce任務,例如:
3.小文件合并
??小文件數目過多,容易在文件存儲端造成瓶頸,給HDFS帶來壓力,影響處理效率。對此,可以通過合并Map和Reduce的結果文件來消除這樣的影響。
用于設置合并的參數有:
小文件是如何產生的:
1.動態分區插入數據,產生大量的小文件,從而導致map數量劇增;
2.reduce數量越多,小文件也越多(reduce的個數和輸出文件是對應的);
3.數據源本身就包含大量的小文件。
小文件問題的影響:
1.從Hive的角度看,小文件會開很多map,一個map開一個JVM去執行,所以這些任務的初始化,啟動,執行會浪費大量的資源,嚴重影響性能。
2.在HDFS中,每個小文件對象約占150byte,如果小文件過多會占用大量內存。這樣NameNode內存容量嚴重制約了集群的擴展。
小文件問題的解決方案:
從小文件產生的途徑就可以從源頭上控制小文件數量,方法如下:
1.使用Sequencefile作為表存儲格式,不要用textfile,在一定程度上可以減少小文件;
2.減少reduce的數量(可以使用參數進行控制);
3.少用動態分區,用時記得按distribute by分區,distribute by 可以控制分發到reduce的方式,避免數據傾斜。
對于已有的小文件,我們可以通過以下幾種方案解決:
1.使用hadoop archive命令把小文件進行歸檔;
2.重建表,建表時減少reduce數量;
3.通過參數進行調節,設置map/reduce端的相關參數,如下:
優化點四:sql優化
1.減少讀取數據量,節約讀取開銷
1.1 列裁剪
??數據量較大的情況下,使用具體的所需字段代替select * 操作。
??比如黑科技清單表中有a,b,c,d…z 26列數據,我們需要使用其中的a,b,c三列,使用 "select a,b,c from 黑科技清單"的性能要比"select * from 黑科技清單"好。實際上生產環境一般也會禁止使用select * 操作的。
1.2 分區裁剪
對于數據量比較大的表,我們一般會進行分區。業務查詢過程中通過選擇所需分區的方式過濾數據量,也會對查詢性能有明顯的提升。
1.3 過濾操作
如果表數據量較大,需要把對業務沒有價值的數據過濾掉再進行關聯等其他查詢操作。
2. 用group by 語句替代COUNT(DISTINCT)
1.數據量小的時候COUNT(DISTINCT)性能可能比group by 好,因為group by語句需要子查詢,里外層兩個select 會產生兩個job。
2.數據量大的情況下,由于COUNT DISTINCT操作需要只用一個Reduce Task來完成,這一個Reduce需要處理的數據量太大,就會導致整個Job很難完成,一般這種情況下,COUNT DISTINCT需要采用先GROUP BY再COUNT的方式替換。
3.優化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;4. Group by操作
??默認情況下,Map階段同一Key數據分發給一個reduce,當一個key數據過大時就傾斜了。進行GROUP BY操作時需要注意以下幾點:
Map端部分聚合
事實上并不是所有的聚合操作都需要在reduce部分進行,很多聚合操作都可以先在Map端進行部分聚合,然后reduce端得出最終結果。
有數據傾斜時進行負載均衡
此處需要設定hive.groupby.skewindata,當選項設定為true時,生成的查詢計劃有兩個MapReduce任務。在第一個MapReduce中,map的輸出結果集合會隨機分布到reduce中,每個reduce做部分聚合操作,并輸出結果。這樣處理的結果是,相同的Group By Key有可能分發到不同的reduce中,從而達到負載均衡的目的;第二個MapReduce任務再根據預處理的數據結果按照Group By Key分布到reduce中(這個過程可以保證相同的Group By Key分布到同一個reduce中),最后完成最終的聚合操作。
5. 利用Hive對UNION ALL優化的特性
多表union all會優化成一個job。
??問題:比如推廣效果表要和商品表關聯,效果表中的auction_id列既有32位字符串商品id,也有數字id,和商品表關聯得到商品的信息。
解決方法:Hive SQL性能會比較好
比分別過濾數字id,字符串id然后分別和商品表關聯性能要好。商品表只讀一次,推廣效果表只讀取一次。
6.解決Hive對UNION ALL優化的短板
Hive對union all的優化的特性:對union all優化只局限于非嵌套查詢。
6.1 消滅子查詢內的group by
SELECT * FROM(SELECT * FROM t1 GROUP BY c1,c2,c3UNION ALLSELECT * FROM t2 GROUP BY c1,c2,c3 ) t3 GROUP BY c1,c2,c3??從業務邏輯上說,子查詢內的GROUP BY怎么看都是多余(功能上的多余,除非有COUNT(DISTINCT)),如果不是因為Hive Bug或者性能上的考量(曾經出現如果不執行子查詢GROUP BY,數據得不到正確的結果的Hive Bug)。所以這個Hive按經驗轉換成如下所示:
SELECT * FROM (SELECT * FROM t1 UNION ALL SELECT * FROM t2) t3 GROUP BY c1,c2,c3??經過測試,并未出現union all的Hive Bug,數據是一致的。MapReduce的作業數由3減少到1。
t1相當于一個目錄,t2相當于一個目錄,對Map/Reduce程序來說,t1、t2可以作為Map/Reduce作業的mutli inputs。這可以通過一個Map/Reduce來解決這個問題。Hadoop的計算框架,不怕數據多,就怕作業數多。
但如果換成是其他計算平臺如Oracle,那就不一定了,因為把大輸入拆成兩個輸入,分別排序匯總成merge(假如兩個子排序是并行的話),是有可能性能更優的(比如希爾排序比冒泡排序的性能更優)。
6.2 消滅子查詢內的COUNT(DISTINCT),MAX,MIN
6.3 消滅子查詢內的JOIN
SELECT * FROM(SELECT * FROM t1UNION ALLSELECT * FROM t4UNION ALLSELECT * FROM t2 JOIN t3ON t2.id=t3.id ) x GROUP BY c1,c2; -- 上面代碼運行會有5個jobs。加入先JOIN生存臨時表的話t5,然后UNION ALL,會變成2個jobs。 INSERT OVERWRITE TABLE t5 SELECT * FROM t2 JOIN t3 ON t2.id=t3.id; SELECT * FROM (t1 UNION ALL t4 UNION ALL t5); -- 調優結果顯示:針對千萬級別的廣告位表,由原先5個Job共15分鐘,分解為2個job,一個8-10分鐘,一個3分鐘。7. JOIN操作
7.1 小表、大表JOIN(新版本已經優化,紀念一下青春吧)
??在使用寫有Join操作的查詢語句時有一條原則:應該將條目少的表/子查詢放在Join操作符的左邊。原因是在Join操作的Reduce階段,位于Join操作符左邊的表的內容會被加載進內存,將條目少的表放在左邊,可以有效減少發生OOM錯誤的幾率;再進一步,可以使用Group讓小的維度表(1000條以下的記錄條數)先進內存。在map端完成reduce。
實際測試發現:新版的hive已經對小表JOIN大表和大表JOIN小表進行了優化。小表放在左邊和右邊已經沒有明顯區別。
優化點五:使用向量化查詢
??向量化查詢執行通過一次性批量執行1024行而不是每次單行執行,從而提供掃描、聚合、篩選器和連接等操作的性能。在Hive 0.13中引入,此功能顯著提高了查詢執行時間,并可通過兩個參數設置輕松啟用:
設置hive.vectorized.execution.enabled = true;設置hive.vectorized.execution.reduce.enabled = true;向量化查詢只支持orc等列存儲格式。
優化點六:選擇引擎
??Hive可以使用Apache Tez執行引擎而不是古老的Map-Reduce引擎。在環境中沒有默認打開,在Hive查詢開頭將以下內容設置為‘true’來使用Tez:
使用Tez 引擎: set hive.execution.engine = tez; 使用spark 引擎: set hive.execution.engine = spark;優化點七:存儲格式
| TextFile | 行存儲 | 存儲空間消耗比較大,并且壓縮的text 無法分割和合并 查詢的效率最低,可以直接存儲,加載數據的速度最高 |
| SequenceFile | 行存儲 | 存儲空間消耗最大,壓縮的文件可以分割和合并 查詢效率高,需要通過text文件轉化來加載 |
| RCFile | 數據按行分塊 每塊按列存儲 | 存儲空間最小, 查詢的效率最高 , 需要通過text文件轉化來加載, 加載的速度最低。 壓縮快 快速列存取。 讀記錄盡量涉及到的block最少 讀取需要的列只需要讀取每個row group 的頭部定義。 讀取全量數據的操作 性能可能比sequencefile沒有明顯的優勢 |
| ORCFile | 數據按行分塊 每塊按列存儲 | 壓縮快,快速列存取 ,效率比rcfile高,是rcfile的改良版本 |
| Parquet | 列存儲 | 相對于PRC,Parquet壓縮比較低,查詢效率較低,不支持update、insert和ACID.但是Parquet支持Impala查詢引擎 |
| 推薦使用orcFile; |
優化點八:壓縮格式
??大數據場景下存儲格式壓縮格式尤為關鍵,可以提升計算速度,減少存儲空間,降低網絡io,磁盤io,所以要選擇合適的壓縮格式和存儲格式。
??壓縮比率,壓縮解壓縮速度,是否支持Split,這三點是選擇壓縮格式要考慮的要素。
優化點九:運行模式選擇
1.本地模式
??對于大多數情況,Hive可以通過本地模式在單臺機器上處理所有任務。對于小數據,執行時間可以明顯被縮短。開啟本地模式,簡單查詢將不會提交到yarn。
set hive.exec.mode.local.auto=true; -- 開啟本地mr -- 設置local mr的最大輸入數據量,當輸入數據量小于這個值時采用local mr的方式,默認為134217728,即128M set hive.exec.mode.local.auto.inputbytes.max=50000000; -- 設置local mr的最大輸入文件個數,當輸入文件個數小于這個值時采用local mr的方式,默認為4 set hive.exec.mode.local.auto.input.files.max=10;2.并行模式
??Hive會將一個查詢轉化成一個或多個階段。這樣的階段可以是MapReduce階段、抽樣階段、合并階段、limit階段。默認情況下,Hive一次只會執行一個階段,由于job包含多個階段,而這些階段并非完全相互依賴,即:這些階段可以并行執行,可以縮短整個job的執行時間。設置參數,set hive.exec.parallel=true,或者通過配置文件來完成:
3.嚴格模式
Hive提供一個嚴格模式,可以防止用戶執行那些可能產生意想不到的影響查詢,通過設置Hive.mapred.modestrict來完成。
set hive.mapred.mode=strict;
3.1 設置為嚴格模式后,可以禁止3種類型的查詢:
a.不帶分區或過濾的分區表查詢
??如果在一個分區表執行查詢,除非where語句中包含分區字段過濾條件來顯示數據范圍,否則不允許執行。換句話說就是在嚴格模式下不允許用戶掃描所有的分區。
b. 帶有order by的查詢
對于使用了orderby的查詢,要求必須有limit語句。因為orderby為了執行排序過程會將所有的結果分發到同一個reducer中
進行處理,強烈要求用戶增加這個limit語句可以防止reducer額外執行很長一段時間。
c. 限制笛卡爾積的查詢
嚴格模式下,進行笛卡爾積的查詢會報錯,必須要有on條件。
優化點十:JVM重用
??JVM重用是Hadoop調優參數的內容,其對Hive的性能具有非常大的影響,特別是對于很難避免小文件的場景或task特別多的場景,這類場景大多數task執行時間都很短。
?? Hadoop的默認配置通常是使用派生JVM來執行map和Reduce任務的。這時JVM的啟動過程可能會造成相當大的開銷,尤其是執行的job包含有成百上千task任務的情況。JVM重用可以使得JVM實例在同一個job中重新使用N次。N的值可以在Hadoop的mapred-site.xml文件中進行配置。通常在10-20之間,具體多少需要根據具體業務場景測試得出。
??我們也可以在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)機制,它根據一定的法則推測出“拖后腿”的任務,并為這樣的任務啟動一個備份任務,讓該任務與原始任務同時處理同一份數據,并最終選用最先成功運行完成任務的計算結果作為最終結果。
mapred.map.tasks.speculative.execution=true mapred.reduce.tasks.speculative.execution=true??關于調優這些推測執行變量,還很難給一個具體的建議。如果用戶對于運行時的偏差非常敏感的話,那么可以將這些功能關閉掉。如果用戶因為輸入數據量很大而需要執行長時間的map或者Reduce task的話,那么啟動推測執行造成的浪費是非常巨大大。數據量過于龐大,備份task有可能直接打垮集群。
優化點十二:分區、分桶表
??1.對于一張比較大的表,將其設計成分區表,避免全表掃描。
??2.當很難在列上創建分區時,我們會使用分桶,比如某個經常被篩選的字段,如果將其作為分區字段,會造成大量的分區。在Hive中,會對分桶字段進行哈希,從而提供了中額外的數據結構,進行提升查詢效率。
??與分區表類似,分桶表的組織方式是將HDFS上的文件分割成多個文件。分桶可以加快數據采樣,也可以提升join的性能(join的字段是分桶字段),因為分桶可以確保某個key對應的數據在一個特定的桶內(文件),所以巧妙地選擇分桶字段可以大幅度提升join的性能。通常情況下,分桶字段可以選擇經常用在過濾操作或者join操作的字段。
??我們可以使用set.hive.enforce.bucketing = true啟用分桶設置。
??當使用分桶表時,最好將bucketmapjoin標志設置為true,具體配置參數為:
優化點十三:對中間數據啟用壓縮
??復雜的Hive查詢通常會轉換為一系列多階段的MapReduce作業,并且這些作業將由Hive引擎連接起來以完成整個查詢。因此,此處的“中間輸出”是指上一個MapReduce作業的輸出,它將用作下一個MapReduce作業的輸入數據。
??壓縮可以顯著減少中間數據量,從而在內部減少了Map和Reduce之間的數據傳輸量。
??我們可以使用以下屬性在中間輸出上啟用壓縮。
為了將最終輸出到HDFS的數據進行壓縮,可以使用以下屬性:
set hive.exec.compress.output=true;優化點十四:謂詞下推
比如下面的查詢:
select a.*,b.* from a join b on a.col1 = b.col1 where a.col1 > 15 and b.col2 > 16??如果沒有謂詞下推,則在完成JOIN處理之后將執行過濾條件(a.col1> 15 and b.col2> 16)。因此,在這種情況下,JOIN將首先發生,并且可能產生更多的行,然后在進行過濾操作。
??使用謂詞下推,這兩個謂詞**(a.col1> 15和b.col2> 16)**將在JOIN之前被處理,因此它可能會從a和b中過濾掉連接中較早處理的大部分數據行,因此,建議啟用謂詞下推。
通過將hive.optimize.ppd設置為true可以啟用謂詞下推。
??如果使用外連接,則謂詞下推會失效
select a.id,a.c1,b.c2 from a left join b on a.id=b.id where b.dt >= '20181201' and b.dt <'20190101'優化點十五:基于成本的優化
??Hive在提交最終執行之前會優化每個查詢的邏輯和物理執行計劃。基于成本的優化會根據查詢成本進行進一步的優化,從而可能產生不同的決策:比如如何決定JOIN的順序,執行哪種類型的JOIN以及并行度等。
??可以通過設置以下參數來啟用基于成本的優化。
??可以使用統計信息來優化查詢以提高性能。基于成本的優化器(CBO)還使用統計信息來比較查詢計劃并選擇最佳計劃。通過查看統計信息而不是運行查詢,效率會很高。
??收集表的列統計信息:
??查看my_db數據庫中my_table中my_id列的列統計信息:
DESCRIBE FORMATTED my_db.my_table my_id優化點十六:insert into操作
1.插入數據量較大
??如果插入過程中有多個union all(union all個數大于2),或者插入的數據量比較大,應該拆成多個insert into 語句并行執行,實際測試過程中,執行時間能提升50%。
2.多次掃描表
INSERT INTO temp_table_20201115 SELECT * FROM my_table WHERE dt ='2020-11-15'; INSERT INTO temp_table_20201116 SELECT * FROM my_table WHERE dt ='2020-11-16';如上面的邏輯,將一張表多次查詢寫入多張表中,多次讀表會消耗很多性能,我們可以優化為讀一次表寫入多個表:
FROM my_tableINSERT INSERT INTO temp_table_20201115 SELECT * WHERE dt ='2020-11-15' INSERT INTO temp_table_20201116 SELECT * WHERE dt ='2020-11-16'優化點十七:好的模型設計
優化點十八:良好的sql開發能力
參考文獻:
https://www.cnblogs.com/swordfall/p/11037539.html
https://blog.csdn.net/weixin_44318830/article/details/103336579
https://baijiahao.baidu.com/s?id=1721265748021502455&wfr=spider&for=pc
https://www.cnblogs.com/liuxinrong/articles/12695181.html
https://www.cnblogs.com/junstudys/p/10056830.html
總結
- 上一篇: 手游挂机工作室 - 二三点科普
- 下一篇: 在SVN安装目录的bin文件夹下没有找到