hive運(yùn)行一個(gè)查詢(xún)呼股,可能會(huì)由于各種原因失敗,但不應(yīng)該出現(xiàn)執(zhí)行成功画恰,但數(shù)據(jù)結(jié)果不正確彭谁。同樣的sql查詢(xún),同樣的數(shù)據(jù)允扇,卻出現(xiàn)了某一次查詢(xún)缠局,沒(méi)有報(bào)錯(cuò),但數(shù)據(jù)異常考润,且只此一次狭园,再也無(wú)法重現(xiàn)了。經(jīng)過(guò)幾天的排查糊治,終于找到了原因妙啃。
經(jīng)過(guò):
2018-04-02凌晨,數(shù)倉(cāng)同學(xué)收到數(shù)據(jù)質(zhì)量報(bào)警俊戳,某個(gè)字段的唯一性檢查沒(méi)有通過(guò)揖赴。一般情況下,這種問(wèn)題是由臟數(shù)據(jù)引起的抑胎。然而這一次排查發(fā)現(xiàn)上游數(shù)據(jù)沒(méi)有問(wèn)題燥滑,于是數(shù)倉(cāng)同學(xué)嘗試直接通過(guò)hive命令進(jìn)行操作,這次結(jié)果正確了阿逃,能通過(guò)數(shù)據(jù)質(zhì)量檢查铭拧。之后我又重試了很多次,再也無(wú)法重現(xiàn)數(shù)據(jù)錯(cuò)誤的那種情況恃锉。
調(diào)研過(guò)程:
1.排除了數(shù)據(jù)源的數(shù)據(jù)變化搀菩,以及查詢(xún)語(yǔ)句的改動(dòng)。
2.看那一次查詢(xún)的運(yùn)行日志破托,找到其中的job的日志肪跋,與正常情況下的job日志對(duì)比
循著explain出來(lái)的執(zhí)行計(jì)劃往前追溯,找到異常最早出現(xiàn)在stage1的job土砂。對(duì)比如下:
在錯(cuò)誤的case下州既,reduce input records 小于map output records。這里combine 的records為0萝映,且有相同數(shù)據(jù)的查詢(xún)做為對(duì)比吴叶,不會(huì)是因?yàn)槟承﹌ey被過(guò)濾而引起的。所以這里這是一個(gè)異常情況序臂。
看一下執(zhí)行計(jì)劃蚌卤,這是一個(gè)普通的join的job
再看一下這個(gè)job的執(zhí)行情況
maps和reduces各有三次被kill
由于speculation execution 被kill是常見(jiàn)的,但是可見(jiàn)有一個(gè)map 和一個(gè)reduce是由于NodeX:8800 不可用引起的奥秆。
到這里變成了兩個(gè)問(wèn)題:1. NodeX:8800 為什么會(huì)不可用逊彭;2.為什么這個(gè)不可用會(huì)造成數(shù)據(jù)結(jié)果有問(wèn)題
對(duì)于第一個(gè)問(wèn)題,首先找到task的日志中不可用的具體時(shí)間吭练,在00:33左右诫龙,然后查看node manager的日志,但是沒(méi)找到線索鲫咽。又想到我們集群設(shè)置了如果某個(gè)datanode的磁盤(pán)使用率如果大于90%的話(huà)签赃,node manager會(huì)通知resource manager當(dāng)前這個(gè)節(jié)點(diǎn)不可用。查看NodeX的當(dāng)前狀態(tài)分尸,果然當(dāng)前的使用率在87%锦聊,已經(jīng)很接近90%。凌晨同時(shí)加上map和reduce任務(wù)箩绍,超90%也在情理之中孔庭,基本上就是這個(gè)原因了。那么為什么這個(gè)節(jié)點(diǎn)負(fù)載這么高,平時(shí)我們hdfs也會(huì)做均衡圆到。經(jīng)過(guò)排查怎抛,發(fā)現(xiàn)有一個(gè)job已經(jīng)運(yùn)行了3天還沒(méi)跑完焊刹,應(yīng)該是數(shù)據(jù)傾斜了长搀,其有一個(gè)reduce task正是運(yùn)行在NodeX上,kill之后負(fù)載就下來(lái)了束凑。
繼續(xù)看第二個(gè)問(wèn)題挣菲,由于是reduce的input records不對(duì)富稻,首先懷疑reduce階段的問(wèn)題。把四個(gè)成功reduce的日志放到一起白胀,統(tǒng)計(jì)reduce的讀寫(xiě)數(shù)據(jù)量椭赋,并與正確情況對(duì)比』蚋埽可惜對(duì)比兩次正確的執(zhí)行哪怔,發(fā)現(xiàn)其中的讀寫(xiě)數(shù)據(jù)量也會(huì)有少量差異,可能與中間結(jié)果的壓縮有關(guān)廷痘。然后考慮對(duì)比每個(gè)reduce的output records蔓涧,找到具體哪個(gè)reduce出的錯(cuò),然而對(duì)比兩次正確的執(zhí)行笋额,其中每個(gè)reduce 的output records量仍然不同元暴,也就是說(shuō)每次執(zhí)行其分區(qū)結(jié)果并不完全一樣。
從這些log中得到信息只有:某一個(gè)reduce讀取的map out put 文件是新的map task生成的兄猩,而其他三個(gè)reduce讀取的是由于NodeX不可用被kill的task生成的茉盏。但是即使如此,也不應(yīng)該有問(wèn)題枢冤,因?yàn)閮蓚€(gè)task生成的map output應(yīng)該是一樣的鸠姨。
到這里沒(méi)線索了,干脆來(lái)理一下當(dāng)時(shí)的場(chǎng)景:
1.這個(gè)job正在執(zhí)行淹真,共19個(gè)map task
2.map task 都正確輸出了output file讶迁,4 個(gè) reduce task 啟動(dòng)了
3.其中三個(gè)reduce task處理的數(shù)據(jù)量較小,已經(jīng)讀取了NodeX上的map task的輸出: attempt_1496659744394_1860537_m_000003_0
4.NodeX由于磁盤(pán)負(fù)載問(wèn)題不可用核蘸,RM通知AM巍糯,AM收到通知后,kill了NodeX上的map task 和reduce task
5.AM啟動(dòng)了新的map task客扎,執(zhí)行結(jié)束后生成output file: attempt_1496659744394_1860537_m_000003_1
6.AM啟動(dòng)了新的reduce task祟峦,讀取attempt_1496659744394_1860537_m_000003_1
其中還有幾個(gè)因?yàn)閟peculation execution被kill的task,以及被map task 搶占的reduce task
在這個(gè)過(guò)程中徙鱼,問(wèn)題只可能出在5宅楞,6步中新建的map和reduce task中,懷疑是新的map和reduce task中的分區(qū)方式不一樣。關(guān)于分區(qū)方式其實(shí)有一個(gè)疑問(wèn)厌衙,就是上文加粗部分距淫,為什么每次執(zhí)行,分區(qū)結(jié)果都是不同的迅箩?把map和reduce的日志級(jí)別改到TRACE溉愁,在日志中尋找關(guān)于分區(qū)的線索,沒(méi)找到饲趋; 又仔細(xì)看了hive的代碼,join的時(shí)候會(huì)以join columns的值生成 partition key撤蟆,然后生成hashcode 奕塑,最后在collect的時(shí)候?qū)ashcode取余進(jìn)行分區(qū),這其中每一步都是deterministic的家肯,固定的輸入最后的分區(qū)結(jié)果一定是一樣的龄砰。問(wèn)題到底出在哪里?
似乎又沒(méi)線索了讨衣,于是把從輸入查詢(xún)語(yǔ)句到執(zhí)行結(jié)束整個(gè)過(guò)程再梳理一遍换棚。分析了查詢(xún)語(yǔ)句之后,終于搞清楚了反镇。查詢(xún)語(yǔ)句中有如下用法:
...
from (select
...
if(x_id>0,cast(concat(substr(x_id,0,2),'0000') as int),cast(ceiling(rand()*-65535) as bigint)) AS y_id,
...
from db.tbl) a
left join(select id,name from db.tbl2)l on a.y_id=l.id
...
為了解決數(shù)據(jù)傾斜問(wèn)題固蚤,y_id中的一部分?jǐn)?shù)據(jù)是隨機(jī)生成的,而y_id又是join key歹茶,所以一個(gè)split中會(huì)有一部分?jǐn)?shù)據(jù)是隨機(jī)分區(qū)的夕玩。在上述這種不幸的場(chǎng)景下,示意圖如下惊豺,一共4個(gè)reduce task燎孟,三個(gè)從attempt_1496659744394_1860537_m_000003_0獲取分區(qū)數(shù)據(jù),一個(gè)從attempt_1496659744394_1860537_m_000003_1獲取分區(qū)數(shù)據(jù)尸昧,由于部分?jǐn)?shù)據(jù)key的隨機(jī)性揩页,這兩個(gè)map output的文件中的分區(qū)是不一致的。結(jié)果就是有的數(shù)據(jù)會(huì)重復(fù)取烹俗,有的數(shù)據(jù)沒(méi)取到爆侣。
那么怎么解決這個(gè)問(wèn)題呢?幾種辦法
1.改MR框架衷蜓,在這種兩次map的output 中分區(qū)不一致的情況累提,當(dāng)某個(gè)map task被kill,執(zhí)行了新的map task以后磁浇,所以的reduce task都需要重新執(zhí)行斋陪,即使是已經(jīng)成功的reduce task
2.改hive,在hive層確保某個(gè)map task即使被執(zhí)行多次,每次執(zhí)行的分區(qū)結(jié)果都要一致
上述兩種改法代價(jià)都是比較大的无虚,這種有隨機(jī)join key生成的job本身就比較少缔赠,而恰好只有部分reduce task成功的間隙,某個(gè)map task所在的data node不可用的概率也是比較小的友题。
3.workaround:改查詢(xún)語(yǔ)句嗤堰,在生成隨機(jī)的列值之后先插入某個(gè)中間表,然后再取出來(lái)做join度宦,這時(shí)map task的分區(qū)就是確定的了踢匣。