聊聊得物數(shù)據(jù)研發(fā)優(yōu)化策略
1、前言
在離線數(shù)據(jù)研發(fā)中,隨著業(yè)務(wù)的快速發(fā)展以及業(yè)務(wù)復(fù)雜度的不斷提高,數(shù)據(jù)量的不斷增長,尤其得物這種業(yè)務(wù)的高速增長,必然帶來數(shù)據(jù)邏輯復(fù)雜度的提升,數(shù)據(jù)量越大,復(fù)雜度越高,對任務(wù)的性能的要求就越高,因此,任務(wù)性能的優(yōu)化就成了大家必然的話題,在離線數(shù)倉招聘中,這幾乎成了必考題目。
大數(shù)據(jù)領(lǐng)域,為了提高超大數(shù)據(jù)量的計算性能,幾代人不斷在努力,不斷榨取著計算機的CPU、內(nèi)存、磁盤每一個模塊的性能,從早期的縱向擴展(提升計算機性能,如IBM、ORACLE 早期推崇的服務(wù)器到小型機到大型機的演進)到目前的大規(guī)模橫向擴展(分布式集群模式),都是旨在提升大數(shù)據(jù)的性能。
本文重點從在分布式計算模式下,如何來優(yōu)化任務(wù),大家耳熟能詳?shù)某R妰?yōu)化如:mapjoin skewjoin distribute by 等就不多做贅述,本文主要探索技巧、策略及方法。
2、任務(wù)優(yōu)化策略
2.1 優(yōu)化方向
補充說明:目前得物大數(shù)據(jù)在阿里云的dataworks 環(huán)境下,集群層面做了比較多的工作,IO、網(wǎng)絡(luò)、機架感應(yīng)等暫時無需過多關(guān)注,如有自建集群時,可重點關(guān)注,我們重點關(guān)注JOIN 和REDUCE 層面,優(yōu)化細節(jié)也重點基于這兩個方向做細節(jié)展開。
2.2 優(yōu)化手段
對于優(yōu)化手段優(yōu)化方法,我們大多數(shù)習慣性從技術(shù)手段出發(fā),更多的從算子、邏輯兼容等來處理,但是在某些業(yè)務(wù)場景下,如埋點日志,數(shù)據(jù)量一般比較大,這種情況無論技術(shù)手段如何干預(yù),都無法解決存儲和計算帶來的資源消耗,這時候如果要提升SLA,就得從業(yè)務(wù)場景出發(fā),做好業(yè)務(wù)的分類分級以及核心數(shù)據(jù)分流,因此,本文的優(yōu)化手段會從技術(shù)手段和業(yè)務(wù)手段兩方面展開。
- 技術(shù)手段
聚焦于技術(shù)手段來處理任務(wù),參加上述單點任務(wù)優(yōu)化方向,主要是SQL 邏輯、模型規(guī)范、算子優(yōu)化及可能存在的集群優(yōu)化
- 業(yè)務(wù)手段
聚焦于業(yè)務(wù)特性、業(yè)務(wù)邏輯來進行處理,基于不同的業(yè)務(wù)特性及重要程度,從生產(chǎn)、采集、模型、數(shù)據(jù)消費全鏈路進行梳理和架構(gòu)優(yōu)化,同時形成一套數(shù)據(jù)鏈路上的通知及約束機制,避免上游變更帶來的下游數(shù)據(jù)故障及恢復(fù)問題。
3、優(yōu)化實踐案例
優(yōu)化策略中,定義好優(yōu)化方向、優(yōu)化手段,接下來,我們選取一些比較有效的沉淀出來的方案,展開講講如何來做任務(wù)優(yōu)化。
前文講述,目前的得物的數(shù)據(jù)平臺特性(dataworks),我們在IO、網(wǎng)絡(luò)、RPC 通信機制等暫時涉入不深,且對于面向業(yè)務(wù)的數(shù)據(jù)研發(fā)來言,大部分人不會過多關(guān)注底層的實現(xiàn)原理,暫不做過多深入探討。
我們基于上面方向中的技術(shù)手段講述幾個日常常見的優(yōu)化案例
3.1 數(shù)據(jù)重分發(fā)(Distribute &Rand)
3.1.1 數(shù)據(jù)重分發(fā)的要點
日常數(shù)據(jù)研發(fā)中,最常見的且使用較多的就是數(shù)據(jù)傾斜或數(shù)據(jù)量帶來的數(shù)據(jù)重分發(fā)(打散或隨機),對于數(shù)據(jù)的重分發(fā),主要分以下幾點:
- 優(yōu)化小文件
- 數(shù)據(jù)傾斜
- 排序&隨機
小文件過多帶來的MAP 端資源損耗和數(shù)據(jù)傾斜是我們?nèi)粘i_發(fā)過程中最為常見的性能問題,而這兩點大多跟rand()隨機數(shù)有一定的關(guān)系,通過數(shù)據(jù)分發(fā)和打散和規(guī)避掉大部分此場景下的問題。
數(shù)據(jù)重分發(fā)一般代碼操作如下所示
select c1,c2... from tablename distribute by c1[,...]
select c1,c2... from tablename distribute by rand([,seed])[,...]
對于rand() 我們要注意幾點,可讓我們在優(yōu)化任務(wù)時,知其然,更知其所以然。
- rand() 隨機數(shù)的生成規(guī)律跟數(shù)學(xué)概率有莫大的關(guān)系,尤其在算法中,會被經(jīng)常性問到,給定隨機生成的N個數(shù),構(gòu)造等概率事件的發(fā)生器,跑題了,繼續(xù)說回在hive 或odps 場景下,rand() 函數(shù)是隨機生成的0-1 的double 類型的數(shù)字。
- rand(int seed) 函數(shù)可以根據(jù)種子參數(shù),構(gòu)造一個穩(wěn)定的隨機值,加上種子參數(shù),得到的結(jié)果是相對穩(wěn)定的,尤其在處理小文件過程中,這一步很重要。
- Hive 和odps 場景中,隨機函數(shù)多與pmod()、mod()、floor()、ceil() 等函數(shù)結(jié)合使用,可以根據(jù)不同的業(yè)務(wù)場景,來構(gòu)造任意范圍內(nèi)的隨機整數(shù),比如在處理數(shù)據(jù)重分發(fā)解決數(shù)據(jù)傾斜的問題時,同時擔心影響這種重分發(fā)帶來過多的小文件,隨機數(shù)可以這樣來取 floor(rand())*N/ceil(rand())+1,取1-N 之間的整數(shù)。
比如在流量數(shù)據(jù)里面,因為大量空值時,結(jié)合rand函數(shù),解決數(shù)據(jù)傾斜問題:
select *
from a
left join b on a.order_id = nvl(b.order_id ,concat('hive',rand()))
--b中的order_id 存在大量空值 的時候
3.1.2 數(shù)據(jù)重分發(fā)的作用
對于數(shù)據(jù)重分發(fā),我們主要是用來對處理數(shù)據(jù)結(jié)果進行小文件合并以及對數(shù)據(jù)處理中的傾斜問題進行優(yōu)化。在大多數(shù)的處理中,我們習慣于使用Distribute by Rand() *N 的方式,其實這個方式可能存在問題,在處理類似問題時候,我們可以選擇基于seed種子的Rand函數(shù),來維持隨機數(shù)的穩(wěn)定性。這里需要知曉,distribute by 實際上做了一次shuffle的分發(fā),默認是按照給定key進行的hash操作(可以理解為一次repartion重新分區(qū)),這里面是可以進行定制分區(qū)邏輯的,可以通過重寫hive當中partition的接口,實現(xiàn)不同策略的重分發(fā)。
- 處理小文件合并
使用方式一:指定固定分發(fā)列,做一次shuffle的merge操作,DEMO如下:
SELECT column1, column2,column.... FROM TABLEX WHERE ds = '${bizdate}'DISTRIBUTE BY '${bizdate}',columns1....
使用方式二:指定給定的文件數(shù),這里要用到rand()函數(shù)了,一般有兩種寫法:
第一種寫法(上文討論過,這種寫法在一定情況下會出現(xiàn)數(shù)據(jù)問題):
SELECT column1, column2,column.... FROM TABLEX WHERE ds = '${bizdate}'DISTRIBUTE BY FLOOR(RAND()*N)/CEIL(RAND()*N)
第二種寫法(加隨機種子,產(chǎn)生穩(wěn)定的隨機序列):
SELECT column1,column2,column.... FROM ( SELECT column1, column2,column...., FLOOR(RAND(seed)*N) AS rep_partion FROM TABLEX WHERE ds = '${bizdate}')DISTRIBUTE BY rep_partion
- 處理JOIN中的傾斜:與上述邏輯同理,主要是借助一次分發(fā),使得需要shuffle的數(shù)據(jù)能在一個節(jié)點進行數(shù)據(jù)處理。
3.2 數(shù)據(jù)膨脹(Explode)
在join過程中,我們之前提到了一種基于BLOOMFILTER算法的優(yōu)化方法。在某些情況下,當join的表中出現(xiàn)一個表的量級很大,另外一個表無法mapjoin切熱鍵key在概率分布上呈現(xiàn)隨機性,這個時候就可以在一定程度上,對較小表中的join key進行一定程度的膨脹,由于join的發(fā)生是在reduce階段,因此可以構(gòu)造出穩(wěn)定的多條主鍵,在不同的reduce中對數(shù)據(jù)進行jion操作,進而一定程度上解決join傾斜帶來的問題?;驹砣缦聢D所示:
一個小例子,當研發(fā)使用數(shù)組形式存儲數(shù)據(jù)(sku_ids)時,數(shù)倉想要拿到數(shù)組中每一個sku_id,使用 lateral view EXPLODE。代碼如下:
select order_id
from a
lateral view explode(split(order_ids,',')) v1 as order_id
group by order_id
結(jié)果展示:
order_ids order_id
101,102,103 101
101,102,103 102
101,102,103 103
104,105 104
104,105 105
目前,膨脹函數(shù)已經(jīng)有開發(fā)出來有現(xiàn)成的UDTF函數(shù)來支持,可以支撐任意膨脹量級的數(shù)據(jù)進行膨脹。只需要構(gòu)造膨脹區(qū)間對應(yīng)的隨機函數(shù)即可,還是需要用到Rand()函數(shù)來實現(xiàn)。
數(shù)據(jù)膨脹方式帶來的問題:
在解決了數(shù)據(jù)傾斜重新打散的問題之后,在計算層面會增加一定的數(shù)據(jù)計算量。此外,如果能基于分桶進行二次索引分片,也可以在引擎?zhèn)瓤紤]基于該方向的自適應(yīng)傾斜優(yōu)化。
3.3 數(shù)據(jù)分桶(Bucket)
在數(shù)據(jù)量比較大的情況下,單表數(shù)據(jù)做分區(qū)會存在下游使用效率上的限制,而數(shù)據(jù)在某些列上(或者構(gòu)造業(yè)務(wù)列)存在高度聚集,或者存在可以優(yōu)化提升的巨大空間,在此時,我們就可以對列進行散列分桶,在分區(qū)的基礎(chǔ)上進行桶表的設(shè)計,桶上可以對應(yīng)索引向量,將極大的提升數(shù)據(jù)使用上的效率。
在數(shù)據(jù)隨機抽樣、JOIN場景中,也會極大的提升整個數(shù)據(jù)的計算性能和效率。在hive中,該功能默認是關(guān)閉的,需要set hive.enforce.bucketing=true打開支持,odps 下可能無需特別關(guān)注,需要注意一般而言,桶的個數(shù)將與一次作業(yè)中對應(yīng)的reduce數(shù)量一致。
其實,基于分桶的邏輯,在引擎?zhèn)瓤梢宰龈嗟膬?yōu)化(比如引擎?zhèn)瓤梢詢?yōu)化分桶存儲的策略)。在join中,根據(jù)索引進行join層面的動態(tài)優(yōu)化,在超大數(shù)據(jù)join過程中,基于桶進行單位數(shù)據(jù)的本地優(yōu)化等等都是可以做非常多的優(yōu)化操作的,由于在目前的業(yè)務(wù)場景中,較少用到數(shù)據(jù)分桶,因此這里不做更深入的拓展,詳細的可以自行百度,查看關(guān)于桶表的使用,更進一步,合理分桶,加上排序后的索引,能高效優(yōu)化單表查詢使用的效率。
3.4 并發(fā)與并行控制
在計算機入門的時候,我們就經(jīng)常聽到并發(fā)與并行,線程與進程等概念。而在數(shù)據(jù)研發(fā)中,我們發(fā)現(xiàn),其實對于整個作業(yè)來說,同樣遵循類似的調(diào)優(yōu)規(guī)則。一般的,一個作業(yè)最大的map數(shù)是9999,reduce數(shù)最大是1000。雖然可以提高單個任務(wù)吞吐量,但是會消耗更長的時間和資源調(diào)度上的等待。另一方面,當完成一個同類作業(yè),往往需要多個任務(wù)進行,如果任務(wù)下面可以多個作業(yè)并行處理,單個作業(yè)也能夠并發(fā)執(zhí)行,那么就能夠更大程度地榨取整個集群的資源,從而達到突破計算瓶頸和上線的目的。目前在開源HADOOP體系中,我們沒有腳本模式來支持靈活的任務(wù)自動分配和調(diào)度,但是可以采用SHELL/PYTHON腳本+SQL的方式來實現(xiàn)這一目的,其實借助猛犸調(diào)度在一定范圍內(nèi)也能達到同樣的效果。
3.5 多路輸出與物化(Read Once Output More)
這個部分我們主要談?wù)凥IVE(spark)的CTE寫法(WITH...AS...)以及From語法的應(yīng)用。這兩個語法,在日常開發(fā)稍微復(fù)雜的任務(wù)時候,可以大大清晰整個復(fù)雜SQL的邏輯,同時,在多路讀寫中,通過物化的方式還能在一定程度上加速作業(yè)的運行。
- CTE(with.... as ...)使用
- 基本使用非常簡單,cte的語法主要是為了提高代碼的可讀性,雖然在整個性能的優(yōu)化上未必達到很好的效果,但是在一定程度上,能大大提高任務(wù)的邏輯清晰度。很多時候,我們在多個邏輯過程中,通過臨時表的方式進行任務(wù)的串行,使用with...as...能達到類似的效果。同時with...as...可以深層嵌套,因此是比較好的一種選擇方式。無論是線上任務(wù)還是視圖,都可以使用CTE的寫法——目前比較遺憾的是HIVE的CTE目前不支持遞歸。
代碼示例(可以使用多個with,抽出代碼片段):
with a as (
select * from test1
where xxx = xxx
)
,
b as (
select * from a
)
select * from b limit 100;
- 物化設(shè)置
由于with...as...等同于一個SQL片段,下文中會多次引用該片段的別名,相當于視圖的味道。所以,這里面使用是一個虛擬的概念,實際上只是邏輯生效,實際運行是則是翻譯成實際的MR邏輯去執(zhí)行,如果下游引用該SQL片段較多,這時候MR執(zhí)行會多次掃描原始數(shù)據(jù),執(zhí)行多次相同的MR操作邏輯,此時,就可以在第一次執(zhí)行中來物化CTE寫法中定義的SQL片段,從而達到優(yōu)化的目的。在hive之前的版本中,該功能是默認關(guān)閉的,可以通過下面參數(shù)來開啟,在新的hive版本中,該功能是默認開啟,但是默認引用次數(shù)是3次。
社區(qū)版hive 如下所示,我們的ODPS 下,大家無需太多關(guān)注,這部分做技術(shù)擴展和了解即可。
- FROM使用(一讀多寫)
- FROM也是本人在實際研發(fā)中遇到多路輸出時采用比較多的一種手段之一。當有多個不同的分區(qū),或者多個不同的目標輸出,或者有多個不同的子邏輯的過程中,可以將主邏輯全部開發(fā)完成,然后再進行多路輸出。多路輸出操作的使用限制如下:
- 單條 multi insert語句中最多可以寫255路輸出。超過255路,會上報語法錯誤。
- 單條 multi insert語句中,對于分區(qū)表,同一個目標分區(qū)不允許出現(xiàn)多次。
- 單條 multi insert語句中,對于非分區(qū)表,該表不能出現(xiàn)多次。
比如在流量業(yè)務(wù)場景時,需要寫動態(tài)分區(qū),就可以使用from,一個代碼小例子:
from (
select aa,bb,pt,sec_pt from test
)
insert OVERWRITE table du_temp.temp_01 partition (pt = 'xx',sec_pt = 'test1' )
select aa,bb where sec_pt = 'test1'
insert OVERWRITE table du_temp.temp_01 partition (pt = 'xx',sec_pt = 'test2' )
select aa,bb where sec_pt = 'test2'
4、思考&總結(jié)
在數(shù)據(jù)研發(fā)領(lǐng)域,數(shù)據(jù)的技術(shù)手段無論多么豐富,平臺發(fā)展何等完善,都不能說能解決業(yè)務(wù)的所有問題。一定是先有業(yè)務(wù),才會有對應(yīng)的問題。在面對大數(shù)據(jù)量,高時效性,高復(fù)雜計算的場景,我們需要結(jié)合業(yè)務(wù)的特性,模型的改造,鏈路的設(shè)計,甚至打破常規(guī)等方式來產(chǎn)出不同的方案。在另一個方面,數(shù)據(jù)研發(fā)的工作也遠遠不是單點問題的解決和兜底,相反需要各方的配合與共同的智慧。