正文目錄
- 1潘酗、表層面
1.1 利用分區(qū)表優(yōu)化
1.2 利用分桶表優(yōu)化
1.3 選擇合適的文件存儲(chǔ)格式
1.4 選擇合適的壓縮格式
- 2、HQL層面優(yōu)化
2.1 執(zhí)行計(jì)劃
2.1 列躺涝、行、分區(qū)裁剪
2.2 謂詞下推
2.3 合并小文件
2.4 合理設(shè)置MapTask并行度
2.5 合理設(shè)置ReduceTask并行度
2.6 Join優(yōu)化
2.7 CBO優(yōu)化
2.8 Group By優(yōu)化
2.9 Order By優(yōu)化
2.10 Count Distinct 優(yōu)化
2.11 怎樣寫(xiě)in/exists語(yǔ)句
2.12 使用 vectorization 矢量查詢技術(shù)
2.13 多重插入模式
2.14 啟動(dòng)中間結(jié)果壓縮
- 3扼雏、Hive架構(gòu)層面
3.1 啟用本地抓燃崾取(默認(rèn)開(kāi)啟)
3.2 本地執(zhí)行優(yōu)化
3.3 JVM重用
3.4 并行執(zhí)行
3.5 推測(cè)執(zhí)行
3.6 Hive嚴(yán)格模式
- 4、數(shù)據(jù)傾斜
4.1 不同數(shù)據(jù)類型關(guān)聯(lián)產(chǎn)生數(shù)據(jù)傾斜
4.2 空值過(guò)濾
4.3 group by
4.4 map join
4.5 開(kāi)啟數(shù)據(jù)傾斜是負(fù)載均衡
- 5诗充、調(diào)優(yōu)方案
5.1 日志表和用戶表做鏈接
5.2 位圖法求連續(xù)七天發(fā)朋友圈的用戶
正文
1苍蔬、表層面
1.1 利用分區(qū)表優(yōu)化
分區(qū)表 是在某一個(gè)或者幾個(gè)維度上對(duì)數(shù)據(jù)進(jìn)行分類存儲(chǔ),一個(gè)分區(qū)對(duì)應(yīng)一個(gè)目錄蝴蜓。如果篩選條件里有分區(qū)字段碟绑,那么 Hive 只需要遍歷對(duì)應(yīng)分區(qū)目錄下的文件即可,不需要遍歷全局?jǐn)?shù)據(jù)茎匠,使得處理的數(shù)據(jù)量大大減少格仲,從而提高查詢效率。
也就是說(shuō):當(dāng)一個(gè) Hive 表的查詢大多數(shù)情況下诵冒,會(huì)根據(jù)某一個(gè)字段進(jìn)行篩選時(shí)凯肋,那么非常適合創(chuàng)建為分區(qū)表,該字段即為分區(qū)字段汽馋。
CREATE TABLE page_view
(viewTime INT,
userid BIGINT,
page_url STRING,
referrer_url STRING,
ip STRING COMMENT 'IP Address of the User')
PARTITIONED BY(date STRING, country STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '1'
STORED AS TEXTFILE;
1侮东、當(dāng)你意識(shí)到一個(gè)字段經(jīng)常用來(lái)做where,建分區(qū)表豹芯,使用這個(gè)字段當(dāng)做分區(qū)字段
2悄雅、在查詢的時(shí)候,使用分區(qū)字段來(lái)過(guò)濾铁蹈,就可以避免全表掃描宽闲。只需要掃描這張表的一個(gè)分區(qū)的數(shù)據(jù)即可
1.2 利用分桶表優(yōu)化
跟分區(qū)的概念很相似,都是把數(shù)據(jù)分成多個(gè)不同的類別
1木缝、分區(qū):按照字段值來(lái)進(jìn)行便锨,一個(gè)分區(qū),就只是包含這個(gè)值的所有記錄
不是當(dāng)前分區(qū)的數(shù)據(jù)一定不在當(dāng)前分區(qū)
當(dāng)前分區(qū)也只會(huì)包含當(dāng)前這個(gè)分區(qū)值的數(shù)據(jù)
2我碟、分桶:默認(rèn)規(guī)則放案,Hash的方式
一個(gè)桶中會(huì)有多個(gè)不同的值
如果一個(gè)分桶中,包含了某個(gè)值矫俺,這個(gè)值的所有記錄吱殉,必然都在這個(gè)分桶里面
Hive Bucket掸冤,分桶,是指將數(shù)據(jù)以指定列的值為key進(jìn)行hash友雳,hash到指定數(shù)目的桶里面稿湿,這樣做的目的和分區(qū)表類似,是的篩選時(shí)不用全局遍歷所有的數(shù)據(jù)押赊,只需要遍歷所在的桶就好了饺藤,這樣也只可以支持高效采樣。
其實(shí)最主要的作用就是 采樣流礁、join
如下例就是以 userid 這一列為 bucket 的依據(jù)涕俗,共設(shè)置 32 個(gè) buckets
CREATE TABLE page_view(viewTime INT, userid BIGINT,
page_url STRING, referrer_url STRING,
ip STRING COMMENT 'IP Address of the User')
COMMENT 'This is the page view table'
PARTITIONED BY(dt STRING, country STRING)
CLUSTERED BY(userid) SORTED BY(viewTime) INTO 32 BUCKETS
CLUSTERED BY(userid): 按照userid進(jìn)行分桶
SORTED BY(viewTime): 按照viewTime進(jìn)行桶內(nèi)排序
INTO 32 BUCKETS: 分成多少個(gè)桶
兩個(gè)表以相同方式(相同字段)劃分桶,兩個(gè)表的桶個(gè)數(shù)一定是倍數(shù)關(guān)系神帅,這樣在join的時(shí)候速度會(huì)大大增加
采樣用的不多再姑,也就不過(guò)多闡述了
1.3 選擇合適的文件存儲(chǔ)格式
在 HiveSQL 的 create table 語(yǔ)句中,可以使用 stored as … 指定表的存儲(chǔ)格式找御。Apache Hive支持 Apache Hadoop 中使用的幾種熟悉的文件格式元镀,比如 TextFile、SequenceFile霎桅、RCFile栖疑、Avro、ORC滔驶、ParquetFile等蔽挠。存儲(chǔ)格式一般需要根據(jù)業(yè)務(wù)進(jìn)行選擇,在我們的實(shí)操中瓜浸,絕大多數(shù)表都采用TextFile與Parquet兩種存儲(chǔ)格式之一。TextFile是最簡(jiǎn)單的存儲(chǔ)格式比原,它是純文本記錄插佛,也是Hive的默認(rèn)格式屋群。雖然它的磁盤(pán)開(kāi)銷比較大含懊,查詢效率也低逝钥,但它更多地是作為跳板來(lái)使用狈惫。RCFile仪壮、ORC处面、Parquet等格式的表都不能由文件直接導(dǎo)入數(shù)據(jù)斤斧,必須由TextFile來(lái)做中轉(zhuǎn)捂刺。Parquet和ORC都是Apache旗下的開(kāi)源列式存儲(chǔ)格式冬殃。列式存儲(chǔ)比起傳統(tǒng)的行式存儲(chǔ)更適合批量OLAP查詢囚痴,并且也支持更好的壓縮和編碼。創(chuàng)建表時(shí)审葬,特別是寬表深滚,盡量使用 ORC奕谭、ParquetFile 這些列式存儲(chǔ)格式,因?yàn)榱惺酱鎯?chǔ)的表痴荐,每一列的數(shù)據(jù)在物理上是存儲(chǔ)在一起的血柳,Hive查詢時(shí)會(huì)只遍歷需要列數(shù)據(jù),大大減少處理的數(shù)據(jù)量生兆。
TextFile
1难捌、存儲(chǔ)方式:行存儲(chǔ)。默認(rèn)格式鸦难,如果建表時(shí)不指定默認(rèn)為此格式根吁。,
2明刷、每一行都是一條記錄婴栽,每行都以換行符"\n"結(jié)尾。數(shù)據(jù)不做壓縮時(shí)辈末,磁盤(pán)會(huì)開(kāi)銷比較大愚争,數(shù)據(jù)解析開(kāi)銷也
比較大。
3挤聘、可結(jié)合Gzip轰枝、Bzip2等壓縮方式一起使用(系統(tǒng)會(huì)自動(dòng)檢查,查詢時(shí)會(huì)自動(dòng)解壓),推薦選用可切分的壓
縮算法组去。
Sequence File
1鞍陨、一種Hadoop API提供的二進(jìn)制文件,使用方便从隆、可分割诚撵、個(gè)壓縮的特點(diǎn)。
2键闺、支持三種壓縮選擇:NONE寿烟、RECORD、BLOCK辛燥。RECORD壓縮率低筛武,一般建議使用BLOCK壓縮
RC File
1、存儲(chǔ)方式:數(shù)據(jù)按行分塊挎塌,每塊按照列存儲(chǔ) 徘六。
A、首先榴都,將數(shù)據(jù)按行分塊待锈,保證同一個(gè)record在一個(gè)塊上,避免讀一個(gè)記錄需要讀取多個(gè)block嘴高。
B炉擅、其次辉懒,塊數(shù)據(jù)列式存儲(chǔ),有利于數(shù)據(jù)壓縮和快速的列存取谍失。
2眶俩、相對(duì)來(lái)說(shuō),RCFile對(duì)于提升任務(wù)執(zhí)行性能提升不大快鱼,但是能節(jié)省一些存儲(chǔ)空間颠印。可以使用升級(jí)版的ORC格
式抹竹。
ORC File
1线罕、存儲(chǔ)方式:數(shù)據(jù)按行分塊,每塊按照列存儲(chǔ)
2窃判、Hive提供的新格式钞楼,屬于RCFile的升級(jí)版,性能有大幅度提升袄琳,而且數(shù)據(jù)可以壓縮存儲(chǔ)询件,壓縮快,快速列存取唆樊。
3宛琅、ORC File會(huì)基于列創(chuàng)建索引,當(dāng)查詢的時(shí)候會(huì)很快逗旁。
Parquet File
1嘿辟、存儲(chǔ)方式:列式存儲(chǔ)。
2片效、Parquet對(duì)于大型查詢的類型是高效的红伦。對(duì)于掃描特定表格中的特定列查詢,Parquet特別有用淀衣。Parquet一般使用Snappy色建、Gzip壓縮。默認(rèn)Snappy舌缤。
3、Parquet支持Impala 查詢引擎某残。
4国撵、表的文件存儲(chǔ)格式盡量采用Parquet或ORC,不僅降低存儲(chǔ)量玻墅,還優(yōu)化了查詢介牙,壓縮,表關(guān)聯(lián)等性能
1.4 選擇合適的壓縮格式
Hive 語(yǔ)句最終是轉(zhuǎn)化為 MapReduce 程序來(lái)執(zhí)行的澳厢,而 MapReduce 的性能瓶頸在與 網(wǎng)絡(luò)IO 和 磁盤(pán)IO环础,要解決性能瓶頸囚似,最主要的是 減少數(shù)據(jù)量,對(duì)數(shù)據(jù)進(jìn)行壓縮是個(gè)好方式线得。壓縮雖然是減少了數(shù)據(jù)量饶唤,但是壓縮過(guò)程要消耗 CPU,但是在 Hadoop 中贯钩,往往性能瓶頸不在于 CPU募狂,CPU 壓力并不大,所以壓縮充分利用了比較空閑的 CPU角雷。
常用壓縮方法對(duì)比
壓縮格式 | 是否可拆分 | 是否自帶 | 壓縮率 | 速度 | 是否hadoop自帶 |
---|---|---|---|---|---|
gzip | 否 | 是 | 很高 | 比較快 | 是 |
lzo | 是 | 是 | 比較高 | 很快 | 否 |
snappy | 否 | 是 | 比較高 | 很快 | 否 |
bzip2 | 是 | 否 | 最高 | 慢 | 是 |
壓縮率對(duì)比
如何選擇壓縮方式呢祸穷?
1、壓縮比例
2勺三、解壓縮速度
3雷滚、是否支持split
支持切割的文件可以并行的有多個(gè)mapper程序處理大數(shù)據(jù)文件,一般我們選擇的都是支持切分的吗坚!
壓縮帶來(lái)的缺點(diǎn)和優(yōu)點(diǎn)
1祈远、計(jì)算密集型,不壓縮刻蚯,否則會(huì)進(jìn)一步增加cpu的負(fù)擔(dān)绊含,真實(shí)的場(chǎng)景中hive對(duì)cpu的壓力很小
2、網(wǎng)絡(luò)密集型炊汹,推薦壓縮躬充,減小網(wǎng)絡(luò)數(shù)據(jù)傳輸
# Job 輸出文件按照 Block
## 默認(rèn)值是false
set mapreduce.output.fileoutputformat.compress=true;
## 默認(rèn)值是Record
set mapreduce.output.fileoutputformat.compress.type=BLOCK;
set mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.lzo.LzoCodec;
# Map 輸出結(jié)結(jié)果進(jìn)行壓縮
set mapred.map.output.compress=true;
set mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.lzo.LzoCodec;
# 對(duì) Hive 輸出結(jié)果和中間都進(jìn)行壓縮
set hive.exec.compress.output=true ## 默認(rèn)值是false,不壓縮
set hive.exec.compress.intermediate=true ## 默認(rèn)值是false讨便,為true時(shí)MR設(shè)置的壓縮才啟用
2充甚、HQL層面優(yōu)化
2.1 執(zhí)行計(jì)劃
explain select * from movies;
2.1 列、行霸褒、分區(qū)裁剪
列裁剪就是在查詢時(shí)只讀取需要的列
行裁剪就是在查詢時(shí)只讀取需要的行伴找,也就是提前過(guò)濾
分區(qū)剪裁就是在查詢的時(shí)候只讀取需要的分區(qū)。
set hive.optimize.cp = true; 列裁剪废菱,取數(shù)只取查詢中需要用到的列技矮,默認(rèn)是true
set hive.optimize.pruner=true; ## 分區(qū)剪裁
2.2 謂詞下推
將 SQL 語(yǔ)句中的 where 謂詞邏輯都盡可能提前執(zhí)行,減少下游處理的數(shù)據(jù)量殊轴。對(duì)應(yīng)邏輯優(yōu)化器是PredicatePushDown衰倦。
set hive.optimize.ppd=true; ## 默認(rèn)是true
eg:
select a.*, b.* from a join b on a.id = b.id where b.age > 20;
轉(zhuǎn)換為下面的這樣的
select a.*, c.* from a join (select * from b where age > 20) c on a.id = c.id;
2.3 合并小文件
如果一個(gè)mapreduce job碰到一對(duì)小文件作為輸入,一個(gè)小文件啟動(dòng)一個(gè)Task旁理,這樣的話會(huì)出現(xiàn)maptask爆炸的問(wèn)題樊零。
Map端輸入合并
在執(zhí)行 MapReduce 程序的時(shí)候,一般情況是一個(gè)文件的一個(gè)數(shù)據(jù)分塊需要一個(gè) mapTask 來(lái)處理孽文。但是如果數(shù)據(jù)源是大量的小文件驻襟,這樣就會(huì)啟動(dòng)大量的 mapTask 任務(wù)夺艰,這樣會(huì)浪費(fèi)大量資源〕烈拢可以將輸入的小文件進(jìn)行合并郁副,從而減少 mapTask 任務(wù)數(shù)量。
## Map端輸入厢蒜、合并文件之后按照block的大小分割(默認(rèn))
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
## Map端輸入霞势,不合并
set hive.input.format=org.apache.hadoop.hive.ql.io.HiveInputFormat;
Map/Reduce輸出合并
大量的小文件會(huì)給 HDFS 帶來(lái)壓力,影響處理效率斑鸦°倒保可以通過(guò)合并 Map 和 Reduce 的結(jié)果文件來(lái)消除影響
## 是否合并Map輸出文件, 默認(rèn)值為true
set hive.merge.mapfiles=true;
## 是否合并Reduce端輸出文件,默認(rèn)值為false
set hive.merge.mapredfiles=true;
## 合并文件的大小,默認(rèn)值為256000000 256M
set hive.merge.size.per.task=256000000;
## 每個(gè)Map 最大分割大小
set mapred.max.split.size=256000000;
## 一個(gè)節(jié)點(diǎn)上split的最少值
set mapred.min.split.size.per.node=1; // 服務(wù)器節(jié)點(diǎn)
## 一個(gè)機(jī)架上split的最少值
set mapred.min.split.size.per.rack=1; // 服務(wù)器機(jī)架
hive.merge.size.per.task
和 mapred.min.split.size.per.node
聯(lián)合起來(lái):
1、默認(rèn)情況先把這個(gè)節(jié)點(diǎn)上的所有數(shù)據(jù)進(jìn)行合并巷屿,如果合并的那個(gè)文件的大小超過(guò)了256M就開(kāi)啟另外一個(gè)文件繼續(xù)合并
2固以、如果當(dāng)前這個(gè)節(jié)點(diǎn)上的數(shù)據(jù)不足256M,那么就都合并成一個(gè)邏輯切片嘱巾。
2.4 合理設(shè)置MapTask并行度
Map數(shù)過(guò)大 :當(dāng)輸入文件特別大憨琳,MapTask 特別多,每個(gè)計(jì)算節(jié)點(diǎn)分配執(zhí)行的 MapTask 都很多旬昭,這時(shí)候可以考慮減少 MapTask 的數(shù)量篙螟。增大每個(gè) MapTask 處理的數(shù)據(jù)量。而且 MapTask 過(guò)多问拘,最終生成的結(jié)果文件數(shù)也太多遍略。
1、Map階段輸出文件太小骤坐,產(chǎn)生大量小文件
2绪杏、初始化和創(chuàng)建Map的開(kāi)銷很大
Map數(shù)太小 :當(dāng)輸入文件都很大,任務(wù)邏輯復(fù)雜纽绍,MapTask 執(zhí)行非常慢的時(shí)候蕾久,可以考慮增加MapTask 數(shù),來(lái)使得每個(gè) MapTask 處理的數(shù)據(jù)量減少拌夏,從而提高任務(wù)的執(zhí)行效率僧著。
1、文件處理或查詢并發(fā)度小障簿,Job執(zhí)行時(shí)間過(guò)長(zhǎng)
2盹愚、大量作業(yè)時(shí),容易堵塞集群
一個(gè)MapReduce Job 的 MapTask 數(shù)量是由輸入分片InputSplit 決定的卷谈。而輸入分片是由 FileInputFormat.getSplit() 決定的。一個(gè)輸入分片對(duì)應(yīng)一個(gè)MapTask霞篡,而輸入分片是由三個(gè)參數(shù)決定的:
參數(shù) | 默認(rèn)值 | 意義 |
---|---|---|
dfs.blocksize | 128M | HDFS默認(rèn)數(shù)據(jù)塊大小 |
mapreduce.input.fileinputformat.split.minsize | 1 | 最小分片大小(MR) |
mapreduce.input.fileinputformat.split.maxsize | 256M | 最大分片大小(MR) |
輸入分片大小的計(jì)算是這么計(jì)算出來(lái)的:
long splitSize = Math.max(minSize, Math.min(maxSize, blockSize))
默認(rèn)情況下世蔗,輸入分片大小和 HDFS 集群默認(rèn)數(shù)據(jù)塊大小一致端逼,也就是默認(rèn)一個(gè)數(shù)據(jù)塊,啟用一個(gè)MapTask 進(jìn)行處理污淋,這樣做的好處是避免了服務(wù)器節(jié)點(diǎn)之間的數(shù)據(jù)傳輸顶滩,提高 job 處理效率
兩種經(jīng)典的控制MapTask的個(gè)數(shù)方案:減少M(fèi)apTask數(shù) 或者 增加MapTask數(shù)
1、減少 MapTask 數(shù)是通過(guò)合并小文件來(lái)實(shí)現(xiàn)寸爆,這一點(diǎn)主要是針對(duì)數(shù)據(jù)源
2礁鲁、增加 MapTask 數(shù)可以通過(guò)控制上一個(gè) job 的 reduceTask 個(gè)數(shù)
重點(diǎn)注意:不推薦把這個(gè)值進(jìn)行隨意設(shè)置!
推薦的方式:使用默認(rèn)的切塊大小即可赁豆。如果非要調(diào)整仅醇,最好是切塊的N倍數(shù)
最好的方式就是 NodeManager節(jié)點(diǎn)個(gè)數(shù):N ===》 Task = ( N * 0.95) * MapTask
合理控制 MapTask 數(shù)量
1、減少 MapTask 數(shù)可以通過(guò)合并小文件來(lái)實(shí)現(xiàn)
2魔种、增加 MapTask 數(shù)可以通過(guò)控制上一個(gè) ReduceTask 默認(rèn)的 MapTask 個(gè)數(shù)
輸入文件總大形龆:total_size
HDFS 設(shè)置的數(shù)據(jù)塊大小:dfs_block_size
default_mapper_num = total_size / dfs_block_size
MapReduce 中提供了如下參數(shù)來(lái)控制 map 任務(wù)個(gè)數(shù)节预,從字面上看叶摄,貌似是可以直接設(shè)置 MapTask 個(gè)數(shù)的樣子,但是很遺憾不行安拟,這個(gè)參數(shù)設(shè)置只有在大于 default_mapper_num 的時(shí)候蛤吓,才會(huì)生效
set mapred.map.tasks=10; ## 默認(rèn)值是2
那如果我們需要減少 MapTask 數(shù)量,但是文件大小是固定的糠赦,那該怎么辦呢?可以通過(guò) mapred.min.split.size 設(shè)置每個(gè)任務(wù)處理的文件的大小会傲,這個(gè)大小只有在大于dfs_block_size 的時(shí)候才會(huì)生效
split_size = max(mapred.min.split.size, dfs_block_size)
split_num = total_size / split_size
compute_map_num = Math.min(split_num, Math.max(default_mapper_num,
mapred.map.tasks))
這樣就可以減少 MapTask 數(shù)量了
讓我們來(lái)總結(jié)一下控制mapper個(gè)數(shù)的方法:
1、如果想增加 MapTask 個(gè)數(shù)愉棱,可以設(shè)置 mapred.map.tasks 為一個(gè)較大的值
2唆铐、如果想減少 MapTask 個(gè)數(shù),可以設(shè)置 maperd.min.split.size 為一個(gè)較大的值
3奔滑、如果輸入是大量小文件艾岂,想減少 mapper 個(gè)數(shù),可以通過(guò)設(shè)置 hive.input.format 合并小文
如果想要調(diào)整 mapper 個(gè)數(shù)朋其,在調(diào)整之前王浴,需要確定處理的文件大概大小以及文件的存在形式(是大量小文件,還是單個(gè)大文件)梅猿,然后再設(shè)置合適的參數(shù)氓辣。不能盲目進(jìn)行暴力設(shè)置,不然適得其反袱蚓。
MapTask 數(shù)量與輸入文件的 split 數(shù)息息相關(guān)钞啸,在 Hadoop 源碼org.apache.hadoop.mapreduce.lib.input.FileInputFormat
類中可以看到 split 劃分的具體邏輯。可以直接通過(guò)參數(shù) mapred.map.tasks
(默認(rèn)值2)來(lái)設(shè)定 MapTask 數(shù)的期望值体斩,但它不一定會(huì)生效梭稚。
2.5 合理設(shè)置ReduceTask并行度
如果 ReduceTask 數(shù)量過(guò)多蹬敲,一個(gè) ReduceTask 會(huì)產(chǎn)生一個(gè)結(jié)果文件,這樣就會(huì)生成很多小文件,那么如果這些結(jié)果文件會(huì)作為下一個(gè) Job 的輸入嗅定,則會(huì)出現(xiàn)小文件需要進(jìn)行合并的問(wèn)題,而且啟動(dòng)和初始化ReduceTask 需要耗費(fèi)資源佛南。
如果 ReduceTask 數(shù)量過(guò)少苛白,這樣一個(gè) ReduceTask 就需要處理大量的數(shù)據(jù),并且還有可能會(huì)出現(xiàn)數(shù)據(jù)傾斜的問(wèn)題倡鲸,使得整個(gè)查詢耗時(shí)長(zhǎng)胆敞。默認(rèn)情況下匪燕,Hive 分配的 reducer 個(gè)數(shù)由下列參數(shù)決定:
Hadoop MapReduce 程序中哀澈,ReducerTask 個(gè)數(shù)的設(shè)定極大影響執(zhí)行效率牌借,ReducerTask 數(shù)量與輸出文件的數(shù)量相關(guān)适荣。如果 ReducerTask 數(shù)太多迄本,會(huì)產(chǎn)生大量小文件,對(duì)HDFS造成壓力于樟。如果ReducerTask 數(shù)太少公条,每個(gè)ReducerTask 要處理很多數(shù)據(jù),容易拖慢運(yùn)行時(shí)間或者造成 OOM迂曲。這使得Hive 怎樣決定 ReducerTask 個(gè)數(shù)成為一個(gè)關(guān)鍵問(wèn)題靶橱。遺憾的是 Hive 的估計(jì)機(jī)制很弱,不指定ReducerTask 個(gè)數(shù)的情況下路捧,Hive 會(huì)猜測(cè)確定一個(gè)ReducerTask 個(gè)數(shù)关霸,基于以下兩個(gè)設(shè)定:
參數(shù)1:hive.exec.reducers.bytes.per.reducer (默認(rèn)256M)
參數(shù)2:hive.exec.reducers.max (默認(rèn)為1009)
參數(shù)3:mapreduce.job.reduces (默認(rèn)值為-1,表示沒(méi)有設(shè)置杰扫,那么就按照以上兩個(gè)參數(shù)進(jìn)行設(shè)置)
ReduceTask 的計(jì)算公式為:
N = Math.min(參數(shù)2队寇,總輸入數(shù)據(jù)大小 / 參數(shù)1)
可以通過(guò)改變上述兩個(gè)參數(shù)的值來(lái)控制 ReduceTask 的數(shù)量。也可以通過(guò)
set mapred.map.tasks=10;set mapreduce.job.reduces=10;
通常情況下章姓,有必要手動(dòng)指定 ReduceTask 個(gè)數(shù)佳遣∈堵瘢考慮到 Mapper 階段的輸出數(shù)據(jù)量通常會(huì)比輸入有大幅減少,因此即使不設(shè)定 ReduceTask 個(gè)數(shù)零渐,重設(shè) 參數(shù)2 還是必要的窒舟。依據(jù)經(jīng)驗(yàn),可以將 參數(shù)2 設(shè)定為 M * (0.95 * N) (N為集群中 NodeManager 個(gè)數(shù))诵盼。一般來(lái)說(shuō)惠豺,NodeManage 和 DataNode 的個(gè)數(shù)是一樣的
2.6 Join優(yōu)化
1. Join的整體優(yōu)化原則:
1、優(yōu)先過(guò)濾后再進(jìn)行Join操作风宁,最大限度的減少參與join的數(shù)據(jù)量
2耕腾、小表join大表,最好啟動(dòng)mapjoin杀糯,hive自動(dòng)啟用mapjoin, 小表不能超過(guò)25M,可以更改
3苍苞、Join on的條件相同的話固翰,最好放入同一個(gè)job,并且join表的排列順序從小到大:
select a.*,b.*, c.* from a join b on a.id = b.id join c on a.id = c.i
4羹呵、如果多張表做join, 如果多個(gè)鏈接條件都相同骂际,會(huì)轉(zhuǎn)換成一個(gè)JOb
2. 優(yōu)先過(guò)濾數(shù)據(jù):
盡量減少每個(gè)階段的數(shù)據(jù)量,對(duì)于分區(qū)表能用上分區(qū)字段的盡量使用冈欢,同時(shí)只選擇后面需要使用到的列歉铝,最大限度的減少參與 Join 的數(shù)據(jù)量
3. 小表join大表的原則:
小表 join 大表的時(shí)應(yīng)遵守小表 join 大表原則,原因是 join 操作的 reduce 階段凑耻,位于 join 左邊的表內(nèi)容會(huì)被加載進(jìn)內(nèi)存太示,將條目少的表放在左邊,可以有效減少發(fā)生內(nèi)存溢出的幾率香浩。join 中執(zhí)行順序是從左到右生成 Job类缤,應(yīng)該保證連續(xù)查詢中的表的大小從左到右是依次增加的。
4. 使用相同的連接鍵:
在 hive 中邻吭,當(dāng)對(duì) 3 個(gè)或更多張表進(jìn)行 join 時(shí)餐弱,如果 on 條件使用相同字段,那么它們會(huì)合并為一個(gè)MapReduce Job囱晴,利用這種特性膏蚓,可以將相同的 join on 放入一個(gè) job 來(lái)節(jié)省執(zhí)行時(shí)間。
5. 盡量原子操作:
盡量避免一個(gè)SQL包含復(fù)雜的邏輯畸写,可以使用中間表來(lái)完成復(fù)雜的邏輯驮瞧。
6. 大表join大表:
1、空key過(guò)濾:有時(shí)join超時(shí)是因?yàn)槟承﹌ey對(duì)應(yīng)的數(shù)據(jù)太多枯芬,而相同key對(duì)應(yīng)的數(shù)據(jù)都會(huì)發(fā)送到相同的reducer上剧董,從而導(dǎo)致內(nèi)存不夠幢尚。此時(shí)我們應(yīng)該仔細(xì)分析這些異常的key,很多情況下翅楼,這些key對(duì)應(yīng)的數(shù)據(jù)是異常數(shù)據(jù)尉剩,我們需要在SQL語(yǔ)句中進(jìn)行過(guò)濾。
2毅臊、空key轉(zhuǎn)換:有時(shí)雖然某個(gè)key為空對(duì)應(yīng)的數(shù)據(jù)很多理茎,但是相應(yīng)的數(shù)據(jù)不是異常數(shù)據(jù),必須要包含在join的結(jié)果中管嬉,此時(shí)我們可以表a中key為空的字段賦一個(gè)隨機(jī)的值皂林,使得數(shù)據(jù)隨機(jī)均勻地分不到不同的reducer上
7. 啟用MapJoin:
這個(gè)優(yōu)化措施,只要能用的時(shí)候一定要用蚯撩,根據(jù)數(shù)據(jù)量大小來(lái)調(diào)整小表的大小础倍,一般公司里面可以設(shè)置到512 到1G
MapJoin 是將 join 雙方比較小的表直接分發(fā)到各個(gè) map 進(jìn)程的內(nèi)存中,在 map 進(jìn)程中進(jìn)行 join 操作胎挎,這樣就不用進(jìn)行 reduce 步驟沟启,從而提高了速度。只有 join 操作才能啟用 MapJoin犹菇。
## 是否根據(jù)輸入小表的大小德迹,自動(dòng)將reduce端的common join 轉(zhuǎn)化為map join,將小表刷入內(nèi)存中揭芍。
## 對(duì)應(yīng)邏輯優(yōu)化器是MapJoinProcessor
set hive.auto.convert.join = true;
## 刷入內(nèi)存表的大小(字節(jié))
set hive.mapjoin.smalltable.filesize = 25000000;
## hive會(huì)基于表的size自動(dòng)的將普通join轉(zhuǎn)換成mapjoin
set hive.auto.convert.join.noconditionaltask=true;
## 多大的表可以自動(dòng)觸發(fā)放到內(nèi)層LocalTask中胳搞,默認(rèn)大小10M
set hive.auto.convert.join.noconditionaltask.size=10000000;
Hive 可以進(jìn)行多表 Join。Join 操作尤其是 Join 大表的時(shí)候代價(jià)是非常大的称杨。MapJoin 特別適合大小表join的情況肌毅。在Hive join場(chǎng)景中,一般總有一張相對(duì)小的表和一張相對(duì)大的表姑原,小表叫 build table芽腾,大表叫 probe table。Hive 在解析帶 join 的 SQL 語(yǔ)句時(shí)页衙,會(huì)默認(rèn)將最后一個(gè)表作為 probe table摊滔,將前面的表作為 build table 并試圖將它們讀進(jìn)內(nèi)存。如果表順序?qū)懛吹昀郑琾robe table 在前面艰躺,引發(fā) OOM 的風(fēng)險(xiǎn)就高了。在維度建模數(shù)據(jù)倉(cāng)庫(kù)中眨八,事實(shí)表就是 probe table腺兴,維度表就是 build table。這種 Join 方式在 map 端直接完成 join 過(guò)程廉侧,消滅了 reduce页响,效率很高篓足。而且 MapJoin 還支持非等值連接。當(dāng) Hive 執(zhí)行 Join 時(shí)闰蚕,需要選擇哪個(gè)表被流式傳輸(stream)栈拖,哪個(gè)表被緩存(cache)。Hive 將JOIN 語(yǔ)句中的最后一個(gè)表用于流式傳輸没陡,因此我們需要確保這個(gè)流表在兩者之間是最大的涩哟。如果要在
不同的 key 上 join 更多的表,那么對(duì)于每個(gè) join 集盼玄,只需在 ON 條件右側(cè)指定較大的表
也可以手動(dòng)開(kāi)啟mapjoin:
--SQL方式贴彼,在SQL語(yǔ)句中添加MapJoin標(biāo)記(mapjoin hint)
--將小表放到內(nèi)存中,省去shffle操作
// 在沒(méi)有開(kāi)啟mapjoin的情況下埃儿,執(zhí)行的是reduceJoin
SELECT /*+ MAPJOIN(smallTable) */ smallTable.key, bigTable.value FROM
smallTable JOIN bigTable ON smallTable.key = bigTable.key;
在高版本中器仗,已經(jīng)進(jìn)行了優(yōu)化,會(huì)自動(dòng)進(jìn)行優(yōu)化
8. Sort-Merge-Bucket(SMB) Map Join:
它是另一種Hive Join優(yōu)化技術(shù)童番,使用這個(gè)技術(shù)的前提是所有的表都必須是分桶表(bucket)和分桶排序的(sort)精钮。分桶表的優(yōu)化!
具體實(shí)現(xiàn):
1妓盲、針對(duì)參與join的這兩張做相同的hash散列,每個(gè)桶里面的數(shù)據(jù)還要排序
2专普、這兩張表的分桶個(gè)數(shù)要成倍數(shù)悯衬。
3、開(kāi)啟 SMB join 的開(kāi)關(guān)檀夹!
一些常見(jiàn)的參數(shù)設(shè)置:
## 當(dāng)用戶執(zhí)行bucket map join的時(shí)候筋粗,發(fā)現(xiàn)不能執(zhí)行時(shí),禁止查詢
set hive.enforce.sortmergebucketmapjoin=false;
## 如果join的表通過(guò)sort merge join的條件炸渡,join是否會(huì)自動(dòng)轉(zhuǎn)換為sort merge join
set hive.auto.convert.sortmerge.join=true;
## 當(dāng)兩個(gè)分桶表 join 時(shí)娜亿,如果 join on的是分桶字段,小表的分桶數(shù)是大表的倍數(shù)時(shí)蚌堵,可以啟用
mapjoin 來(lái)提高效率买决。
# bucket map join優(yōu)化,默認(rèn)值是 false
set hive.optimize.bucketmapjoin=false;
## bucket map join 優(yōu)化吼畏,默認(rèn)值是 false
set hive.optimize.bucketmapjoin.sortedmerge=false;
9. Join數(shù)據(jù)傾斜優(yōu)化:
在編寫(xiě) Join 查詢語(yǔ)句時(shí)督赤,如果確定是由于 join 出現(xiàn)的數(shù)據(jù)傾斜,那么請(qǐng)做如下設(shè)置:
# join的鍵對(duì)應(yīng)的記錄條數(shù)超過(guò)這個(gè)值則會(huì)進(jìn)行分拆泻蚊,值根據(jù)具體數(shù)據(jù)量設(shè)置
set hive.skewjoin.key=100000;
# 如果是join過(guò)程出現(xiàn)傾斜應(yīng)該設(shè)置為true
set hive.optimize.skewjoin=false;
如果開(kāi)啟了躲舌,在 Join 過(guò)程中 Hive 會(huì)將計(jì)數(shù)超過(guò)閾值 hive.skewjoin.key(默認(rèn)100000)的傾斜 key 對(duì)應(yīng)的行臨時(shí)寫(xiě)進(jìn)文件中,然后再啟動(dòng)另一個(gè) job 做 map join 生成結(jié)果性雄。通過(guò) hive.skewjoin.mapjoin.map.tasks
參數(shù)還可以控制第二個(gè) job 的 mapper 數(shù)量没卸,默認(rèn)10000羹奉。
例如set hive.skewjoin.mapjoin.map.tasks=10000;
2.7 CBO優(yōu)化
join的時(shí)候表的順序的關(guān)系:前面的表都會(huì)被加載到內(nèi)存中。后面的表進(jìn)行磁盤(pán)掃描
select a.*, b.*, c.* from a join b on a.id = b.id join c on a.id = c.id;
Hive 自 0.14.0 開(kāi)始约计,加入了一項(xiàng) “Cost based Optimizer” 來(lái)對(duì) HQL 執(zhí)行計(jì)劃進(jìn)行優(yōu)化诀拭,這個(gè)功能通過(guò) “hive.cbo.enable” 來(lái)開(kāi)啟。在 Hive 1.1.0 之后病蛉,這個(gè) feature 是默認(rèn)開(kāi)啟的炫加,它可以 自動(dòng)優(yōu)化 HQL中多個(gè) Join 的順序,并選擇合適的 Join 算法铺然。
CBO俗孝,成本優(yōu)化器,代價(jià)最小的執(zhí)行計(jì)劃就是最好的執(zhí)行計(jì)劃魄健。傳統(tǒng)的數(shù)據(jù)庫(kù)赋铝,成本優(yōu)化器做出最優(yōu)化的執(zhí)行計(jì)劃是依據(jù)統(tǒng)計(jì)信息來(lái)計(jì)算的。Hive 的成本優(yōu)化器也一樣。Hive 在提供最終執(zhí)行前舅世,優(yōu)化每個(gè)查詢的執(zhí)行邏輯和物理執(zhí)行計(jì)劃讥蔽。這些優(yōu)化工作是交給底層來(lái)完成的。根據(jù)查詢成本執(zhí)行進(jìn)一步的優(yōu)化良哲,從而產(chǎn)生潛在的不同決策:如何排序連接,執(zhí)行哪種類型的連接助隧,并行度等等筑凫。要使用基于成本的優(yōu)化(也稱為CBO),請(qǐng)?jiān)诓樵冮_(kāi)始設(shè)置以下參數(shù):
set hive.cbo.enable=true;
set hive.compute.query.using.stats=true;
set hive.stats.fetch.column.stats=true;
set hive.stats.fetch.partition.stats=true;
2.8 Group By優(yōu)化
默認(rèn)情況下并村,Map 階段同一個(gè) Key 的數(shù)據(jù)會(huì)分發(fā)到一個(gè) Reduce 上巍实,當(dāng)一個(gè) Key 的數(shù)據(jù)過(guò)大時(shí)會(huì)產(chǎn)生數(shù)據(jù)傾斜。進(jìn)行 group by 操作時(shí)可以從以下兩個(gè)方面進(jìn)行優(yōu)化:
1哩牍、Map端部分預(yù)聚合:
事實(shí)上并不是所有的聚合操作都需要在 Reduce 部分進(jìn)行棚潦,很多聚合操作都可以先在 Map 端進(jìn)行部分聚合,然后在 Reduce 端的得出最終結(jié)果膝昆。
## 開(kāi)啟Map端聚合參數(shù)設(shè)置
set hive.map.aggr=true;
# 設(shè)置map端預(yù)聚合的行數(shù)閾值丸边,超過(guò)該值就會(huì)分拆job,默認(rèn)值100000
set hive.groupby.mapaggr.checkinterval=100000
2荚孵、有數(shù)據(jù)傾斜時(shí)進(jìn)行負(fù)載均衡
當(dāng) HQL 語(yǔ)句使用 group by 時(shí)數(shù)據(jù)出現(xiàn)傾斜時(shí)原环,如果該變量設(shè)置為 true,那么 Hive 會(huì)自動(dòng)進(jìn)行負(fù)載均衡处窥。策略就是把 MapReduce 任務(wù)拆分成兩個(gè):第一個(gè)先做預(yù)匯總嘱吗,第二個(gè)再做最終匯總。
# 自動(dòng)優(yōu)化,有數(shù)據(jù)傾斜的時(shí)候進(jìn)行負(fù)載均衡(默認(rèn)是false) 如果開(kāi)啟設(shè)置為true
set hive.groupby.skewindata=false;
當(dāng)選項(xiàng)設(shè)定為 true 時(shí)谒麦,生成的查詢計(jì)劃有兩個(gè) MapReduce 任務(wù)俄讹。
1、在第一個(gè) MapReduce 任務(wù)中绕德,map 的輸出結(jié)果會(huì)隨機(jī)分布到 reduce 中患膛,每個(gè) reduce 做部分聚合操作,并輸出結(jié)果耻蛇,這樣處理的結(jié)果是相同的`group by key`有可能分發(fā)到不同的 reduce 中踪蹬,從而達(dá)到負(fù)載均衡的目的;
2臣咖、第二個(gè) MapReduce 任務(wù)再根據(jù)預(yù)處理的數(shù)據(jù)結(jié)果按照 group by key 分布到各個(gè) reduce 中跃捣,最后完成最終的聚合操作。
Map 端部分聚合:并不是所有的聚合操作都需要在 Reduce 端完成夺蛇,很多聚合操作都可以先在 Map 端進(jìn)行部分聚合疚漆,最后在 Reduce 端得出最終結(jié)果,對(duì)應(yīng)的優(yōu)化器為 GroupByOptimizer刁赦。
那么如何用 group by 方式同時(shí)統(tǒng)計(jì)多個(gè)列娶聘?
簡(jiǎn)單版:
select t.a, sum(t.b), count(t.c), count(t.d) from some_table t group by t.a;
優(yōu)化版:
select t.a, sum(t.b), count(t.c), count(t.d) from (
select a,b,null c,null d from some_table
union all
select a,0 b,c,null d from some_table group by a,c
union all
select a,0 b,null c,d from some_table group by a,d
) t;
2.9 Order By優(yōu)化
order by 只能是在一個(gè) reduce 進(jìn)程中進(jìn)行,所以如果對(duì)一個(gè)大數(shù)據(jù)集進(jìn)行 order by 甚脉,會(huì)導(dǎo)致一個(gè)reduce 進(jìn)程中處理的數(shù)據(jù)相當(dāng)大丸升,造成查詢執(zhí)行緩慢。
1牺氨、在最終結(jié)果上進(jìn)行order by狡耻,不要在中間的大數(shù)據(jù)集上進(jìn)行排序。如果最終結(jié)果較少波闹,可以在一個(gè)reduce上進(jìn)行排序時(shí)酝豪,那么就在最后的結(jié)果集上進(jìn)行order by涛碑。
2精堕、如果是取排序后的前N條數(shù)據(jù),可以使用distribute by和sort by在各個(gè)reduce上進(jìn)行排序后前N條蒲障,然后再對(duì)各個(gè)reduce的結(jié)果集合合并后在一個(gè)reduce中全局排序歹篓,再取前N條,因?yàn)閰⑴c全局排序的order by的數(shù)據(jù)量最多是reduce個(gè)數(shù) * N揉阎,所以執(zhí)行效率會(huì)有很大提升庄撮。
在Hive中,關(guān)于數(shù)據(jù)排序毙籽,提供了四種語(yǔ)法洞斯,一定要區(qū)分這四種排序的使用方式和適用場(chǎng)景
1、order by:全局排序,缺陷是只能使用一個(gè)reduce
2烙如、sort by:?jiǎn)螜C(jī)排序么抗,單個(gè)reduce結(jié)果有序
3、cluster by:對(duì)同一字段分桶并排序亚铁,不能和sort by連用
4蝇刀、distribute by + sort by:分桶,保證同一字段值只存在一個(gè)結(jié)果文件當(dāng)中徘溢,結(jié)合sort by保證每個(gè)reduceTask結(jié)果有序
Hive HQL 中的 order by 與其他 SQL 方言中的功能一樣吞琐,就是將結(jié)果按某字段全局排序,這會(huì)導(dǎo)致所有 map 端數(shù)據(jù)都進(jìn)入一個(gè) reducer 中然爆,在數(shù)據(jù)量大時(shí)可能會(huì)長(zhǎng)時(shí)間計(jì)算不完站粟。
如果使用 sort by,那么還是會(huì)視情況啟動(dòng)多個(gè) reducer 進(jìn)行排序施蜜,并且保證每個(gè) reducer 內(nèi)局部有序卒蘸。為了控制map 端數(shù)據(jù)分配到 reducer 的 key,往往還要配合 distribute by 一同使用翻默。如果不加distribute by 的話缸沃,map 端數(shù)據(jù)就會(huì)隨機(jī)分配到 reducer.
1、方式一:
-- 直接使用order by來(lái)做修械。如果結(jié)果數(shù)據(jù)量很大趾牧,這個(gè)任務(wù)的執(zhí)行效率會(huì)非常低
select id,name,age from student order by age desc limit 3;
2、方式二:
set mapreduce.job.reduces=3;
select * from student distribute by (case when age > 20 then 0 when age < 18 then 2 else 1 end) sort by (age desc);
關(guān)于分界值的確定肯污,使用采樣的方式翘单,來(lái)估計(jì)數(shù)據(jù)分布規(guī)律。
2.10 Count Distinct 優(yōu)化
當(dāng)要統(tǒng)計(jì)某一列去重?cái)?shù)時(shí)蹦渣,如果數(shù)據(jù)量很大哄芜,count(distinct) 就會(huì)非常慢,原因與 order by 類似柬唯,count(distinct) 邏輯只會(huì)有很少的 reducer 來(lái)處理认臊。這時(shí)可以用 group by 來(lái)改寫(xiě):
-- 先 group by 在 count
select count(1) from (
select age from student
where department >= "MA"
group by age
) t;
2.11 怎樣寫(xiě)in/exists語(yǔ)句
在Hive的早期版本中,in/exists語(yǔ)法是不被支持的锄奢,但是從 hive-0.8x 以后就開(kāi)始支持這個(gè)語(yǔ)法失晴。但是不推薦使用這個(gè)語(yǔ)法。雖然經(jīng)過(guò)測(cè)驗(yàn)拘央,Hive-2.3.6 也支持 in/exists 操作涂屁,但還是推薦使用 Hive 的一個(gè)高效替代方案:left semi join
-- in / exists 實(shí)現(xiàn)
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);
應(yīng)該轉(zhuǎn)換成:
-- left semi join 實(shí)現(xiàn)
select a.id, a.name from a left semi join b on a.id = b.id;
需要注意的是,一定要展示的數(shù)據(jù)只有左表中的數(shù)據(jù)灰伟!
2.12 使用 vectorization 矢量查詢技術(shù)
在計(jì)算類似 scan, filter, aggregation 的時(shí)候拆又, vectorization 技術(shù)以設(shè)置批處理的增量大小為 1024 行單次來(lái)達(dá)到比單條記錄單次獲得更高的效率。
set hive.vectorized.execution.enabled=true ;
set hive.vectorized.execution.reduce.enabled=true;
2.13 多重插入模式
如果你碰到一堆SQL,并且這一堆SQL的模式還一樣帖族。都是從同一個(gè)表進(jìn)行掃描义矛,做不同的邏輯∶巳可優(yōu)化的地方:如果有n條SQL凉翻,每個(gè)SQL執(zhí)行都會(huì)掃描一次這張表
如果一個(gè) HQL 底層要執(zhí)行 10 個(gè) Job,那么能優(yōu)化成 8 個(gè)一般來(lái)說(shuō)捻激,肯定能有所提高制轰,多重插入就是一個(gè)非常實(shí)用的技能。一次讀取胞谭,多次插入垃杖,有些場(chǎng)景是從一張表讀取數(shù)據(jù)后,要多次利用丈屹,這時(shí)可以使用 multi insert 語(yǔ)法:
from sale_detail
insert overwrite table sale_detail_multi partition (sale_date='2019',
region='china' )
select shop_name, customer_id, total_price where .....
insert overwrite table sale_detail_multi partition (sale_date='2020',
region='china' )
select shop_name, customer_id, total_price where .....;
說(shuō)明:multi insert語(yǔ)法有一些限制调俘。
1、一般情況下旺垒,單個(gè)SQL中最多可以寫(xiě)128路輸出彩库,超過(guò)128路,則報(bào)語(yǔ)法錯(cuò)誤先蒋。
2骇钦、在一個(gè)multi insert中:
對(duì)于分區(qū)表,同一個(gè)目標(biāo)分區(qū)不允許出現(xiàn)多次竞漾。
對(duì)于未分區(qū)表眯搭,該表不能出現(xiàn)多次。
3业岁、對(duì)于同一張分區(qū)表的不同分區(qū)鳞仙,不能同時(shí)有insert overwrite和insert into操作,否則報(bào)錯(cuò)返回
Multi-Group by 是 Hive 的一個(gè)非常好的特性笔时,它使得 Hive 中利用中間結(jié)果變得非常方便棍好。例如:
FROM (SELECT a.status, b.school, b.gender FROM status_updates a JOIN profiles b
ON (a.userid = b.userid and a.ds='2019-03-20' )) subq1
INSERT OVERWRITE TABLE gender_summary PARTITION(ds='2019-03-20')
SELECT subq1.gender, COUNT(1) GROUP BY subq1.gender
INSERT OVERWRITE TABLE school_summary PARTITION(ds='2019-03-20')
SELECT subq1.school, COUNT(1) GROUP BY subq1.school;
上述查詢語(yǔ)句使用了 Multi-Group by 特性連續(xù) group by 了 2 次數(shù)據(jù),使用不同的 Multi-Group by糊闽。這一特性可以減少一次 MapReduce 操作梳玫。
2.14 啟動(dòng)中間結(jié)果壓縮
map 輸出壓縮
set mapreduce.map.output.compress=true;
set mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.SnappyCodec;
中間數(shù)據(jù)壓縮
中間數(shù)據(jù)壓縮就是對(duì) hive 查詢的多個(gè) Job 之間的數(shù)據(jù)進(jìn)行壓縮爹梁。最好是選擇一個(gè)節(jié)省CPU耗時(shí)的壓縮方式右犹。可以采用 snappy 壓縮算法姚垃,該算法的壓縮和解壓效率都非常高念链。
set hive.exec.compress.intermediate=true;
set hive.intermediate.compression.codec=org.apache.hadoop.io.compress.SnappyCodec;
set hive.intermediate.compression.type=BLOCK;
結(jié)果數(shù)據(jù)壓縮
最終的結(jié)果數(shù)據(jù)(Reducer輸出數(shù)據(jù))也是可以進(jìn)行壓縮的,可以選擇一個(gè)壓縮效果比較好的,可以減少數(shù)據(jù)的大小和數(shù)據(jù)的磁盤(pán)讀寫(xiě)時(shí)間掂墓;注:常用的 gzip谦纱,snappy 壓縮算法是不支持并行處理的,如果數(shù)據(jù)源是 gzip/snappy壓縮文件大文件君编,這樣只會(huì)有有個(gè) mapper 來(lái)處理這個(gè)文件跨嘉,會(huì)嚴(yán)重影響查詢效率。所以如果結(jié)果數(shù)據(jù)需要作為其他查詢?nèi)蝿?wù)的數(shù)據(jù)源吃嘿,可以選擇支持 splitable 的 LZO 算法祠乃,這樣既能對(duì)結(jié)果文件進(jìn)行壓縮,還可以并行的處理兑燥,這樣就可以大大的提高 job 執(zhí)行的速度了亮瓷。
set hive.exec.compress.output=true;
set mapreduce.output.fileoutputformat.compress=true;
set mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.GzipCodec;
set mapreduce.output.fileoutputformat.compress.type=BLOCK;
Hadoop集群支持的壓縮算法:
org.apache.hadoop.io.compress.DefaultCodec
org.apache.hadoop.io.compress.GzipCodec
org.apache.hadoop.io.compress.BZip2Codec
org.apache.hadoop.io.compress.DeflateCodec
org.apache.hadoop.io.compress.SnappyCodec
org.apache.hadoop.io.compress.Lz4Codec
com.hadoop.compression.lzo.LzoCodec
com.hadoop.compression.lzo.LzopCodec
3、Hive架構(gòu)層面
3.1 啟用本地抓冉低(默認(rèn)開(kāi)啟)
Hive 的某些 SQL 語(yǔ)句需要轉(zhuǎn)換成 MapReduce 的操作嘱支,某些 SQL 語(yǔ)句就不需要轉(zhuǎn)換成 MapReduce 操作,但是同學(xué)們需要注意挣饥,理論上來(lái)說(shuō)除师,所有的 SQL 語(yǔ)句都需要轉(zhuǎn)換成 MapReduce 操作,只不過(guò)Hive 在轉(zhuǎn)換 SQL 語(yǔ)句的過(guò)程中會(huì)做部分優(yōu)化扔枫,使某些簡(jiǎn)單的操作不再需要轉(zhuǎn)換成 MapReduce馍盟,例如:
1、只是 select * 的時(shí)候
2茧吊、where 條件針對(duì)分區(qū)字段進(jìn)行篩選過(guò)濾時(shí)
3贞岭、帶有 limit 分支語(yǔ)句時(shí)
3.2 本地執(zhí)行優(yōu)化
Hive 在集群上查詢時(shí),默認(rèn)是在集群上多臺(tái)機(jī)器上運(yùn)行搓侄,需要多個(gè)機(jī)器進(jìn)行協(xié)調(diào)運(yùn)行瞄桨,這種方式很好的解決了大數(shù)據(jù)量的查詢問(wèn)題。但是在 Hive 查詢處理的數(shù)據(jù)量比較小的時(shí)候讶踪,其實(shí)沒(méi)有必要啟動(dòng)分布式模式去執(zhí)行芯侥,因?yàn)橐苑植际椒绞綀?zhí)行設(shè)計(jì)到跨網(wǎng)絡(luò)傳輸、多節(jié)點(diǎn)協(xié)調(diào)等乳讥,并且消耗資源柱查。對(duì)于小數(shù)據(jù)集,可以通過(guò)本地模式云石,在單臺(tái)機(jī)器上處理所有任務(wù)唉工,執(zhí)行時(shí)間明顯被縮短。
三個(gè)參數(shù):
## 打開(kāi)hive自動(dòng)判斷是否啟動(dòng)本地模式的開(kāi)關(guān)
set hive.exec.mode.local.auto=true;
## map任務(wù)數(shù)最大值汹忠,不啟用本地模式的task最大個(gè)數(shù)
set hive.exec.mode.local.auto.input.files.max=4;
## map輸入文件最大大小淋硝,不啟動(dòng)本地模式的最大輸入文件大小
set hive.exec.mode.local.auto.inputbytes.max=134217728;
3.3 JVM重用
Hive 語(yǔ)句最終會(huì)轉(zhuǎn)換為一系列的 MapReduce 任務(wù)雹熬,每一個(gè)MapReduce 任務(wù)是由一系列的 MapTask和 ReduceTask 組成的,默認(rèn)情況下谣膳,MapReduce 中一個(gè) MapTask 或者 ReduceTask 就會(huì)啟動(dòng)一個(gè)JVM 進(jìn)程竿报,一個(gè) Task 執(zhí)行完畢后,JVM 進(jìn)程就會(huì)退出继谚。這樣如果任務(wù)花費(fèi)時(shí)間很短烈菌,又要多次啟動(dòng)JVM 的情況下,JVM 的啟動(dòng)時(shí)間會(huì)變成一個(gè)比較大的消耗花履,這時(shí)僧界,可以通過(guò)重用 JVM 來(lái)解決
JVM也是有缺點(diǎn)的,開(kāi)啟JVM重用會(huì)一直占用使用到的 task 的插槽臭挽,以便進(jìn)行重用捂襟,直到任務(wù)完成后才會(huì)釋放。如果某個(gè) 不平衡的job 中有幾個(gè) reduce task 執(zhí)行的時(shí)間要比其他的 reduce task 消耗的時(shí)間要多得多的話欢峰,那么保留的插槽就會(huì)一直空閑卻無(wú)法被其他的 job 使用葬荷,直到所有的 task 都結(jié)束了才會(huì)釋放。
根據(jù)經(jīng)驗(yàn)纽帖,一般來(lái)說(shuō)可以使用一個(gè) cpu core 啟動(dòng)一個(gè) JVM宠漩,假如服務(wù)器有 16 個(gè) cpu core ,但是這個(gè)節(jié)點(diǎn)懊直,可能會(huì)啟動(dòng) 32 個(gè)mapTask扒吁,完全可以考慮:?jiǎn)?dòng)一個(gè)JVM,執(zhí)行兩個(gè)Task
3.4 并行執(zhí)行
有的查詢語(yǔ)句室囊,Hive 會(huì)將其轉(zhuǎn)化為一個(gè)或多個(gè)階段雕崩,包括:MapReduce 階段、抽樣階段融撞、合并階段盼铁、limit 階段等。默認(rèn)情況下尝偎,一次只執(zhí)行一個(gè)階段饶火。但是,如果某些階段不是互相依賴致扯,是可以并行執(zhí)行的肤寝。多階段并行是比較耗系統(tǒng)資源的。
一個(gè) Hive SQL 語(yǔ)句可能會(huì)轉(zhuǎn)為多個(gè) MapReduce Job抖僵,每一個(gè) job 就是一個(gè) stage鲤看,這些 Job 順序執(zhí)行,這個(gè)在 cli 的運(yùn)行日志中也可以看到裆针。但是有時(shí)候這些任務(wù)之間并不是是相互依賴的刨摩,如果集群資源允許的話,可以讓多個(gè)并不相互依賴 stage 并發(fā)執(zhí)行世吨,這樣就節(jié)約了時(shí)間澡刹,提高了執(zhí)行速度,但是如果集群資源匱乏時(shí)耘婚,啟用并行化反倒是會(huì)導(dǎo)致各個(gè) Job 相互搶占資源而導(dǎo)致整體執(zhí)行性能的下降罢浇。啟用并行化
## 可以開(kāi)啟并行執(zhí)行。
set hive.exec.parallel=true;
## 同一個(gè)sql允許最大并行度沐祷,默認(rèn)為8嚷闭。
set hive.exec.parallel.thread.number=16;
3.5 推測(cè)執(zhí)行
在分布式集群環(huán)境下,因?yàn)槌绦駼ug(包括Hadoop本身的bug)赖临,負(fù)載不均衡或者資源分布不均等原因胞锰,會(huì)造成同一個(gè)作業(yè)的多個(gè)任務(wù)之間運(yùn)行速度不一致,有些任務(wù)的運(yùn)行速度可能明顯慢于其他任務(wù)(比如一個(gè)作業(yè)的某個(gè)任務(wù)進(jìn)度只有50%兢榨,而其他所有任務(wù)已經(jīng)運(yùn)行完畢)嗅榕,則這些任務(wù)會(huì)拖慢作業(yè)的整體執(zhí)行進(jìn)度。為了避免這種情況發(fā)生吵聪,Hadoop采用了推測(cè)執(zhí)行(Speculative Execution)機(jī)制凌那,它根據(jù)一定的法則推測(cè)出“拖后腿”的任務(wù),并為這樣的任務(wù)啟動(dòng)一個(gè)備份任務(wù)吟逝,讓該任務(wù)與原始任務(wù)同時(shí)處理同一份數(shù)據(jù)帽蝶,并最終選用最先成功運(yùn)行完成任務(wù)的計(jì)算結(jié)果作為最終結(jié)果
# 啟動(dòng)mapper階段的推測(cè)執(zhí)行機(jī)制
set mapreduce.map.speculative=true;
# 啟動(dòng)reducer階段的推測(cè)執(zhí)行機(jī)制
set mapreduce.reduce.speculative=true;
如果用戶對(duì)于運(yùn)行時(shí)的偏差非常敏感的話,那么可以將這些功能關(guān)閉掉块攒。如果用戶因?yàn)檩斎霐?shù)據(jù)量很大而需要執(zhí)行長(zhǎng)時(shí)間的MapTask或者ReduceTask的話励稳,那么啟動(dòng)推測(cè)執(zhí)行造成的浪費(fèi)是非常巨大大。其實(shí)我一般不使用
3.6 Hive嚴(yán)格模式
所謂嚴(yán)格模式囱井,就是強(qiáng)制不允許用戶執(zhí)行有風(fēng)險(xiǎn)的 HiveQL 語(yǔ)句麦锯,一旦執(zhí)行會(huì)直接失敗。但是Hive中為了提高SQL語(yǔ)句的執(zhí)行效率琅绅,可以設(shè)置嚴(yán)格模式扶欣,充分利用Hive的某些特點(diǎn)。
## 設(shè)置Hive的嚴(yán)格模式
set hive.mapred.mode=strict;
set hive.exec.dynamic.partition.mode=nostrict;
注意:當(dāng)設(shè)置嚴(yán)格模式之后千扶,會(huì)有如下限制:
1料祠、對(duì)于分區(qū)表,必須添加where對(duì)于分區(qū)字段的條件過(guò)濾
select * from student_ptn where age > 25
2澎羞、order by語(yǔ)句必須包含limit輸出限制
select * from student order by age limit 100;
3髓绽、限制執(zhí)行笛卡爾積的查詢
select a.*, b.* from a, b;
4、在hive的動(dòng)態(tài)分區(qū)模式下妆绞,如果為嚴(yán)格模式顺呕,則必須需要一個(gè)分區(qū)列式靜態(tài)分區(qū)
4枫攀、數(shù)據(jù)傾斜
4.1 不同數(shù)據(jù)類型關(guān)聯(lián)產(chǎn)生數(shù)據(jù)傾斜
select * from users a
left outer join logs b
on a.usr_id = cast(b.user_id as string)
4.2 空值過(guò)濾
在生產(chǎn)環(huán)境經(jīng)常會(huì)用大量空值數(shù)據(jù)進(jìn)入到一個(gè)reduce中去,導(dǎo)致數(shù)據(jù)傾斜株茶。
解決辦法:
自定義分區(qū)来涨,將為空的key轉(zhuǎn)變?yōu)樽址与S機(jī)數(shù)或純隨機(jī)數(shù),將因空值而造成傾斜的數(shù)據(jù)分不到多個(gè)Reducer启盛。
注意:對(duì)于異常值如果不需要的話蹦掐,最好是提前在where條件里過(guò)濾掉,這樣可以使計(jì)算量大大減少
4.3 group by
采用sum() group by的方式來(lái)替換count(distinct)完成計(jì)算僵闯。
4.4 map join
以上講過(guò)了
4.5 開(kāi)啟數(shù)據(jù)傾斜是負(fù)載均衡
以上也講過(guò)了
5卧抗、調(diào)優(yōu)方案
5.1 日志表和用戶表做鏈接
select * from log a left outer join users b on a.user_id = b.user_id;
users 表有 600w+ (假設(shè)有5G)的記錄,把 users 分發(fā)到所有的 map 上也是個(gè)不小的開(kāi)銷鳖粟,而且MapJoin 不支持這么大的小表社裆。如果用普通的 join,又會(huì)碰到數(shù)據(jù)傾斜的問(wèn)題向图。
改進(jìn)方案:
select /*+mapjoin(x)*/ * from log a
left outer join (
select /*+mapjoin(c)*/ d.*
from ( select distinct user_id from log ) c join users d on c.user_id =d.user_id
) x
on a.user_id = x.user_id;
假如浦马,log 里 user_id 有上百萬(wàn)個(gè),這就又回到原來(lái) MapJoin 問(wèn)題张漂。所幸晶默,每日的會(huì)員 uv 不會(huì)太多,有交易的會(huì)員不會(huì)太多航攒,有點(diǎn)擊的會(huì)員不會(huì)太多磺陡,有傭金的會(huì)員不會(huì)太多等等。所以這個(gè)方法能解決很多場(chǎng)景下的數(shù)據(jù)傾斜問(wèn)題漠畜。
5.2 位圖法求連續(xù)七天發(fā)朋友圈的用戶
每天都要求 微信朋友圈 過(guò)去連續(xù)7天都發(fā)了朋友圈的小伙伴有哪些币他?假設(shè)每個(gè)用戶每發(fā)一次朋友圈都記錄了一條日志。每一條朋友圈包含的內(nèi)容
日期憔狞,用戶ID蝴悉,朋友圈內(nèi)容.....
dt, userid, content, .....
如果 微信朋友圈的 日志數(shù)據(jù),按照日期做了分區(qū)瘾敢。
2020-07-06 file1.log(可能會(huì)非常大)
2020-07-05 file2.log
…
解決方案:
假設(shè)微信有10E用戶拍冠,我們每天生成一個(gè)長(zhǎng)度為10E的二進(jìn)制數(shù)組,每個(gè)位置要么是0簇抵,要么是1庆杜,如果為1,代表該用戶當(dāng)天發(fā)了朋友圈碟摆。如果為0晃财,代表沒(méi)有發(fā)朋友圈。
然后每天:10E / 8 / 1024 / 1024 = 119M左右
求Join實(shí)現(xiàn):兩個(gè)數(shù)組做 求且典蜕、求或断盛、異或罗洗、求反、求新增
</article>
6.Reference
https://cwiki.apache.org/confluence/display/Hive/Vectorized+Query+Execution
https://cwiki.apache.org/confluence/display/Hive/GettingStarted
轉(zhuǎn)自:調(diào)優(yōu)全方位指南