淺談 Hive 性能優(yōu)化

總結(jié)了 Hive 的常用優(yōu)化手段。

列裁剪及分區(qū)裁剪

這是最基本的操作。所謂列裁剪就是在查詢時只讀取需要的列,分區(qū)裁剪就是只讀取需要的分區(qū)儿惫。

比如當(dāng)列很多或者數(shù)據(jù)量很大時,如果使用 select * from order_table; 或者不指定分區(qū)伸但,全列掃描和全表掃描效率都很低肾请。

這個時候我們可以指定列:

select
    uid,
    price
from
    order_table;

或者當(dāng)這個表是分區(qū)表的時候,指定分區(qū):

select
    uid,
    price
from
    order_table
where
    pt_date = "20200901";

Hive 中與列裁剪優(yōu)化相關(guān)的配置項是hive.optimize.cp更胖,與分區(qū)裁剪優(yōu)化相關(guān)的則是hive.optimize.pruner铛铁,默認都是true。

謂詞下推

在關(guān)系型數(shù)據(jù)庫如 MySQL 中却妨,也有謂詞下推(Predicate Pushdown饵逐,PPD)的概念。就是將 SQL 語句中的 where 謂詞邏輯都盡可能提前執(zhí)行彪标,減少下游處理的數(shù)據(jù)量倍权。

select
    a.uid,
    b.user_id,
    ...
from
    user_table a
inner join
    (select
        uid,
        user_id,
        price
    from
        order_table
    where
        pt_date = "20200901"
    and
        paystate = 2)b
on
    a.user_id = b.user_id;

對 order_table 做過濾的 where 語句寫在子查詢內(nèi)部,而不是外部捞烟。Hive 中有謂詞下推優(yōu)化的配置項hive.optimize.ppd薄声,默認值true,與它對應(yīng)的邏輯優(yōu)化器是 PredicatePushDown题画。該優(yōu)化器就是將 OperatorTree 中的 FilterOperator 向上提默辨。

sort by 代替 order by

HiveSQL 中的 order by 與其他 SQL 方言中的功能一樣,就是將結(jié)果按某字段全局排序婴程,這會導(dǎo)致所有 map 端數(shù)據(jù)都進入一個 reducer 中廓奕,在數(shù)據(jù)量大時可能會長時間計算不完抱婉。

如果使用 sort by档叔,那么還是會視情況啟動多個 reducer 進行排序桌粉,并且保證每個 reducer 內(nèi)局部有序。為了控制 map 端數(shù)據(jù)分配到 reducer 的 key衙四,往往還要配合 distribute by 一同使用铃肯。如果不加 distribute by 的話,map 端數(shù)據(jù)就會隨機分配到 reducer传蹈。

舉個例子押逼,假如要以 uid 為 key,以訂單時間倒序惦界、訂單金額倒序輸出記錄數(shù)據(jù):

select
    uid,
    user_id,
    price
from
    order_table
where
    pt_date = "20200901"
and
    paystate = 2
distribute by 
    uid
sort by 
    order_time desc,
    price desc;

group by 代替 distinct

數(shù)據(jù)量較大的情況下挑格,慎用count(distinct),count(distinct)容易產(chǎn)生傾斜問題

當(dāng)要統(tǒng)計某一列的去重數(shù)時沾歪,如果數(shù)據(jù)量很大漂彤,count(distinct) 就會非常慢,原因與 order by 類似灾搏,count(distinct) 邏輯只會有很少的 reducer 來處理挫望。這時可以用 group by 來改寫:

select 
    count(1) 
from 
    (
        select 
            uid 
        from 
            order_table
        where 
            pt_date = "20200901"
        group by 
            uid
    );

但是這樣寫會啟動兩個 MR job(單純 distinct 只會啟動一個),所以要確保數(shù)據(jù)量大到啟動 job 的 overhead 遠小于計算耗時狂窑,才考慮這種方法媳板。當(dāng)數(shù)據(jù)集很小或者 key 的傾斜比較明顯時,group by 還可能會比 distinct 慢泉哈。

group by 配置調(diào)整

map 端預(yù)聚合

group by 時蛉幸,如果先起一個 combiner 在 map 端做部分預(yù)聚合,可以有效減少 shuffle 數(shù)據(jù)量丛晦。預(yù)聚合的配置項是 hive.map.aggr巨缘,默認值 true,對應(yīng)的優(yōu)化器為 GroupByOptimizer采呐,簡單方便若锁。
通過 hive.groupby.mapaggr.checkinterval 參數(shù)也可以設(shè)置 map 端預(yù)聚合的行數(shù)閾值,超過該值就會分拆 job斧吐,默認值 100000又固。

傾斜均衡配置項

group by 時如果某些 key 對應(yīng)的數(shù)據(jù)量過大,就會發(fā)生數(shù)據(jù)傾斜煤率。Hive 自帶了一個均衡數(shù)據(jù)傾斜的配置項hive.groupby.skewindata仰冠,默認值false。
其實現(xiàn)方法是在 group by 時啟動兩個 MR job蝶糯。第一個 job 會將 map 端數(shù)據(jù)隨機輸入 reducer洋只,每個 reducer 做部分聚合,相同的 key 就會分布在不同的 reducer 中。第二個 job 再將前面預(yù)處理過的數(shù)據(jù)按 key 聚合并輸出結(jié)果识虚,這樣就起到了均衡的效果肢扯。

join 基礎(chǔ)優(yōu)化

build table(小表)前置

在最常見的 hash join 方法中,一般總有一張相對小的表和一張相對大的表担锤,小表叫 build table蔚晨,大表叫 probe table。

Hive 在解析帶 join 的 SQL 語句時肛循,會默認將最后一個表作為 probe table铭腕,將前面的表作為 build table 并試圖將它們讀進內(nèi)存。如果表順序?qū)懛炊嗫罚琾robe table 在前面累舷,引發(fā) OOM 的風(fēng)險就高了。

在維度建模數(shù)據(jù)倉庫中夹孔,事實表就是 probe table被盈,維度表就是 build table。

多表 join 時 key 相同

這種情況會將多個 join 合并為一個 MR job 來處理:

select
    a.uid,
    b.user_id,
    ...
from
    user_table a
inner join
    (select
        uid,
        user_id,
        price
    from
        order_table
    where
        pt_date = "20200901"
    and
        paystate = 2)b
on
    a.user_id = b.user_id
inner join
    parent_table c
on  
    a.user_id = c.user_id;

如果上面兩個 join 的條件不相同析蝴,比如改成 a.uid = c.uid害捕,就會拆成兩個 MR job 計算。
負責(zé)這個的是相關(guān)性優(yōu)化器 CorrelationOptimizer闷畸,它的功能除此之外還非常多尝盼,邏輯復(fù)雜,可以參考 Hive官方的文檔佑菩。

利用 map join 特性

map join 特別適合大小表 join 的情況盾沫。Hive 會將 build table 和 probe table 在 map 端直接完成 join 過程,消滅了 reduce殿漠,效率很高赴精。

select
    /*+ mapjoin(a) */
    a.uid,
    b.user_id,
    ...
from
    user_table a
inner join
    (select
        uid,
        user_id,
        price
    from
        order_table
    where
        pt_date = "20200901"
    and
        paystate = 2)b
on
    a.user_id = b.user_id;

優(yōu)化 SQL 處理 join 數(shù)據(jù)傾斜

空值或無意義值

空值或無意義值很常見,比如日志類型的數(shù)據(jù)绞幌,要統(tǒng)計每天的活躍手機號蕾哟,但是總有一些日志數(shù)據(jù)沒有收集到手機號,或為空莲蜘、為 NULL 等谭确,這個時候就需要提前將這些無意義的數(shù)據(jù)過濾掉,避免消耗票渠。

build table 過大

有時逐哈,build table 會大到無法直接使用 map join 的地步,比如全量用戶維度表问顷,而使用普通 join 又有數(shù)據(jù)分布不均的問題昂秃。這時就要充分利用 probe table 的限制條件禀梳,削減 build table 的數(shù)據(jù)量,再使用 map join 解決肠骆。代價就是需要進行兩次 join算途。舉個例子:

select
    /*+ mapjoin(aa) */
    aa.uid,
    aa.user_id,
    ...
from
    (select
        /*+ mapjoin(a) */
        a.uid,
        b.user_id,
        ...
    from
        user_table a
    inner join
        (select
            uid,
            user_id,
            price
        from
            order_table
        where
            pt_date = "20200901"
        and
            paystate = 2)b
    on
        a.user_id = b.user_id)aa
inner join
    (select
        uid,
        user_id,
        price
    from
        order_table
    where
        pt_date = "20200902"
    and
        paystate = 2)bb;

MapReduce 優(yōu)化

調(diào)整 mapper 數(shù)

mapper 數(shù)量與輸入文件的 split 數(shù)息息相關(guān),在 Hadoop 源碼org.apache.hadoop.mapreduce.lib.input.FileInputFormat 類中可以看到 split 劃分的具體邏輯哗戈。

可以直接通過參數(shù) mapred.map.tasks(默認值2)來設(shè)定 mapper 數(shù)的期望值郊艘,但它不一定會生效荷科,下面會提到唯咬。
設(shè)輸入文件的總大小為 total_input_size。HDFS 中畏浆,一個塊的大小由參數(shù) dfs.block.size 指定胆胰,默認值 64MB 或 128MB。在默認情況下刻获,mapper數(shù)就是:
default_mapper_num = total_input_size / dfs.block.size蜀涨。

參數(shù) mapred.min.split.size(默認值1B)mapred.max.split.size(默認值64MB)分別用來指定 split 的最小和最大大小。split 大小和 split 數(shù)計算規(guī)則是:
split_size = MAX(mapred.min.split.size, MIN(mapred.max.split.size, dfs.block.size))蝎毡;
split_num = total_input_size / split_size厚柳。

得出 mapper 數(shù):
mapper_num = MIN(split_num, MAX(default_num, mapred.map.tasks))

可見沐兵,如果想減少 mapper 數(shù)别垮,就適當(dāng)調(diào)高 mapred.min.split.size,split 數(shù)就減少了扎谎。如果想增大 mapper 數(shù)碳想,除了降低 mapred.min.split.size 之外,也可以調(diào)高 mapred.map.tasks毁靶。

一般來講胧奔,如果輸入文件是少量大文件,就減少 mapper 數(shù)预吆;如果輸入文件是大量非小文件龙填,就增大 mapper 數(shù);至于大量小文件的情況拐叉,得參考下面“合并小文件”一節(jié)的方法處理岩遗。

調(diào)整 reducer 數(shù)

reducer 數(shù)量的確定方法比 mapper 簡單得多。使用參數(shù)mapred.reduce.tasks可以直接設(shè)定 reducer 數(shù)量巷嚣,不像 mapper 一樣是期望值喘先。但如果不設(shè)這個參數(shù)的話,Hive 就會自行推測廷粒,邏輯如下:

參數(shù)hive.exec.reducers.bytes.per.reducer用來設(shè)定每個 reducer 能夠處理的最大數(shù)據(jù)量窘拯,默認值 1G(1.2版本之前)或 256M(1.2版本之后)红且。

參數(shù) hive.exec.reducers.max 用來設(shè)定每個 job 的最大 reducer 數(shù)量,默認值 999(1.2版本之前)或 1009(1.2版本之后)涤姊。

得出 reducer 數(shù):
reducer_num = MIN(total_input_size / reducers.bytes.per.reducer, reducers.max)暇番。

reducer 數(shù)量與輸出文件的數(shù)量相關(guān)。如果 reducer 數(shù)太多思喊,會產(chǎn)生大量小文件壁酬,對 HDFS 造成壓力。如果 reducer 數(shù)太少恨课,每個 reducer 要處理很多數(shù)據(jù)舆乔,容易拖慢運行時間或者造成 OOM。

合并小文件

輸入階段合并

需要更改 Hive 的輸入文件格式剂公,即參數(shù)hive.input.format希俩,默認值是org.apache.hadoop.hive.ql.io.HiveInputFormat,我們改成org.apache.hadoop.hive.ql.io.CombineHiveInputFormat纲辽。這樣比起上面調(diào)整 mapper 數(shù)時颜武,又會多出兩個參數(shù),分別是mapred.min.split.size.per.nodemapred.min.split.size.per.rack拖吼,含義是單節(jié)點和單機架上的最小 split 大小鳞上。如果發(fā)現(xiàn)有 split 大小小于這兩個值(默認都是 100MB),則會進行合并吊档。具體邏輯可以參看 Hive 源碼中的對應(yīng)類篙议。

輸出階段合并

直接將 hive.merge.mapfileshive.merge.mapredfiles 都設(shè)為 true 即可,前者表示將 map-only 任務(wù)的輸出合并籍铁,后者表示將 map-reduce 任務(wù)的輸出合并涡上。另外,hive.merge.size.per.task 可以指定每個task輸出后合并文件大小的期望值拒名,hive.merge.size.smallfiles.avgsize 可以指定所有輸出文件大小的均值閾值吩愧,默認值都是 1GB。如果平均大小不足的話增显,就會另外啟動一個任務(wù)來進行合并雁佳。

啟用壓縮

壓縮 job 的中間結(jié)果數(shù)據(jù)和輸出數(shù)據(jù),可以用少量 CPU 時間節(jié)省很多空間同云。壓縮方式一般選擇 Snappy糖权,效率最高。

要啟用中間壓縮炸站,需要設(shè)定hive.exec.compress.intermediate 為 true星澳,同時指定壓縮方式hive.intermediate.compression.codecorg.apache.hadoop.io.compress.SnappyCodec。另外旱易,參數(shù)hive.intermediate.compression.type 可以選擇對塊(BLOCK)還是記錄(RECORD)壓縮禁偎,BLOCK的壓縮率比較高腿堤。
輸出壓縮的配置基本相同,打開hive.exec.compress.output即可如暖。

JVM 重用

在 MR job 中笆檀,默認是每執(zhí)行一個 task 就啟動一個 JVM。如果 task 非常小而碎盒至,那么 JVM 啟動和關(guān)閉的耗時就會很長酗洒。可以通過調(diào)節(jié)參數(shù)mapred.job.reuse.jvm.num.tasks來重用枷遂。例如將這個參數(shù)設(shè)成 5樱衷,那么就代表同一個 MR job 中順序執(zhí)行的 5 個 task 可以重復(fù)使用一個 JVM,減少啟動和關(guān)閉的開銷登淘。但它對不同 MR job 中的 task 無效箫老。

并行執(zhí)行與本地模式

并行執(zhí)行

Hive 中互相沒有依賴關(guān)系的 job 間是可以并行執(zhí)行的封字,最典型的就是多個子查詢 union all黔州。在集群資源相對充足的情況下,可以開啟并行執(zhí)行阔籽,即將參數(shù)hive.exec.parallel設(shè)為true流妻。另外hive.exec.parallel.thread.number可以設(shè)定并行執(zhí)行的線程數(shù),默認為8笆制,一般都夠用绅这。

本地模式

Hive 也可以不將任務(wù)提交到集群進行運算,而是直接在一臺節(jié)點上處理在辆。因為消除了提交到集群的 overhead证薇,所以比較適合數(shù)據(jù)量很小,且邏輯不復(fù)雜的任務(wù)匆篓。
設(shè)置 hive.exec.mode.local.auto 為 true 可以開啟本地模式浑度。但任務(wù)的輸入數(shù)據(jù)總量必須小于 hive.exec.mode.local.auto.inputbytes.max(默認值128MB) ,且 mapper 數(shù)必須小于 hive.exec.mode.local.auto.tasks.max(默認值4) 鸦概,reducer 數(shù)必須為 0 或 1箩张,才會真正用本地模式執(zhí)行。

嚴格模式

所謂嚴格模式窗市,就是強制不允許用戶執(zhí)行 3 種有風(fēng)險的 HiveSQL 語句先慷,一旦執(zhí)行會直接失敗。這 3 種語句是:

  • 查詢分區(qū)表時不限定分區(qū)列的語句咨察;
  • 兩表 join 產(chǎn)生了笛卡爾積的語句论熙;
  • 用 order by 來排序但沒有指定 limit 的語句。

要開啟嚴格模式摄狱,需要將參數(shù)hive.mapred.mode設(shè)為 strict脓诡。

采用合適的存儲格式

在 HiveSQL 的 create table 語句中素跺,可以使用 stored as ... 指定表的存儲格式。Hive 表支持的存儲格式有 TextFile誉券、SequenceFile指厌、RCFile、Avro踊跟、ORC踩验、Parquet 等。

存儲格式一般需要根據(jù)業(yè)務(wù)進行選擇商玫,在我們的實操中箕憾,絕大多數(shù)表都采用 TextFile 與 Parquet 兩種存儲格式之一。

TextFile 是最簡單的存儲格式拳昌,它是純文本記錄袭异,也是 Hive 的默認格式。雖然它的磁盤開銷比較大炬藤,查詢效率也低御铃,但它更多地是作為跳板來使用。RCFile沈矿、ORC上真、Parquet 等格式的表都不能由文件直接導(dǎo)入數(shù)據(jù),必須由 TextFile 來做中轉(zhuǎn)羹膳。

Parquet 和 ORC 都是 Apache 旗下的開源列式存儲格式睡互。列式存儲比起傳統(tǒng)的行式存儲更適合批量 OLAP 查詢,并且也支持更好的壓縮和編碼陵像。我們選擇 Parquet 的原因主要是它支持 Impala 查詢引擎就珠,并且我們對 update、delete 和事務(wù)性操作需求很低醒颖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妻怎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子图贸,更是在濱河造成了極大的恐慌蹂季,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疏日,死亡現(xiàn)場離奇詭異偿洁,居然都是意外死亡,警方通過查閱死者的電腦和手機沟优,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門涕滋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挠阁,你說我怎么就攤上這事宾肺∷荻” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵锨用,是天一觀的道長丰刊。 經(jīng)常有香客問我,道長增拥,這世上最難降的妖魔是什么啄巧? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮掌栅,結(jié)果婚禮上秩仆,老公的妹妹穿的比我還像新娘。我一直安慰自己猾封,他們只是感情好澄耍,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著晌缘,像睡著了一般齐莲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上枚钓,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天铅搓,我揣著相機與錄音,去河邊找鬼搀捷。 笑死,一個胖子當(dāng)著我的面吹牛多望,可吹牛的內(nèi)容都是我干的嫩舟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼怀偷,長吁一口氣:“原來是場噩夢啊……” “哼家厌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起椎工,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤饭于,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后维蒙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掰吕,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年颅痊,在試婚紗的時候發(fā)現(xiàn)自己被綠了殖熟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡斑响,死狀恐怖菱属,靈堂內(nèi)的尸體忽然破棺而出钳榨,到底是詐尸還是另有隱情,我是刑警寧澤纽门,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布薛耻,位于F島的核電站,受9級特大地震影響赏陵,放射性物質(zhì)發(fā)生泄漏昭卓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一瘟滨、第九天 我趴在偏房一處隱蔽的房頂上張望候醒。 院中可真熱鬧,春花似錦杂瘸、人聲如沸倒淫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽敌土。三九已至,卻和暖如春运翼,著一層夾襖步出監(jiān)牢的瞬間返干,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工血淌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留矩欠,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓悠夯,卻偏偏與公主長得像癌淮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子沦补,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355