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