最近的一個(gè)項(xiàng)目中使用了spark技術(shù)過(guò)程遇到的一些問(wèn)題,下面就以問(wèn)題來(lái)分析原因及解決過(guò)程。
問(wèn)題
1、創(chuàng)建sparkView沒(méi)有加限制條件,導(dǎo)致創(chuàng)建幾十上百萬(wàn)大數(shù)據(jù)量的view時(shí),多庫(kù)的情況下在driver創(chuàng)建了 大量的view,就把driver端的內(nèi)存撐爆了,之前線上沒(méi)有暴露出來(lái)的這個(gè)問(wèn)題原因主要是每一個(gè)小時(shí)都會(huì)處理一次,每次數(shù)據(jù)量都不大,后面任務(wù)有停了幾天,數(shù)據(jù)量突增了很多,這時(shí)就出現(xiàn)很多問(wèn)題
-
如代碼:
2、使用spark過(guò)程中只用于查詢(xún)大數(shù)據(jù)量的源數(shù)據(jù),其中數(shù)據(jù)運(yùn)算過(guò)程都是在使用JAVA方式滿(mǎn)1000條處理,運(yùn)算過(guò)程是逐條進(jìn)行運(yùn)算,代碼中產(chǎn)生過(guò)程多的對(duì)象,沒(méi)有處理完數(shù)據(jù)一直駐留在內(nèi)存,造成嚴(yán)重的FULL-GC,sparkUI上面也發(fā)現(xiàn)大量任務(wù)處于DEAD狀態(tài)。
-
如代碼:
3、運(yùn)算過(guò)程使用大量的JDBC的方式查詢(xún)關(guān)聯(lián)的它表數(shù)據(jù),每條數(shù)據(jù)每次查詢(xún)它表幾毫秒,一旦數(shù)據(jù)量放大時(shí),消耗時(shí)間也是非常大的,而且運(yùn)算過(guò)程中會(huì)按多個(gè)步驟順序查詢(xún)關(guān)聯(lián)表數(shù)據(jù)(每個(gè)步驟都會(huì)做,并沒(méi)有做緩存機(jī)制)。
-
如代碼:8053749-c08ec8489fcb02c0.png
4、每條數(shù)據(jù)進(jìn)行運(yùn)算時(shí),都打印出大量日志信息,有些日志信息可以不用輸出的,打印日志也是導(dǎo)致性能問(wèn)題之一。
-
如部分代碼:
針對(duì)上述這些懷情況已經(jīng)嚴(yán)重影響到處理速度問(wèn)題,進(jìn)行結(jié)構(gòu)上改造,充分利用起spark的技術(shù)優(yōu)勢(shì),如下:
-
1、 重新創(chuàng)建源數(shù)據(jù)view,對(duì)數(shù)據(jù)按一定條件進(jìn)行切分及分批并行拉取出來(lái),即加快了拉取速度,也控制了一次加載數(shù)據(jù)量。如代碼:
-
2、 舍棄每條源數(shù)據(jù)都去數(shù)據(jù)庫(kù)查詢(xún)相關(guān)活動(dòng)的數(shù)據(jù)及它表的數(shù)據(jù)來(lái)進(jìn)行運(yùn)算,創(chuàng)建相關(guān)的view,通過(guò)大sql來(lái)關(guān)聯(lián)數(shù)據(jù)。如:
-
3、 由逐條數(shù)據(jù)進(jìn)行運(yùn)算的方式,改進(jìn)成通過(guò)大SQL中自定義函數(shù)來(lái)實(shí)現(xiàn)匹配運(yùn)算過(guò)程,減少java查詢(xún)方式運(yùn)算時(shí)導(dǎo)致大量數(shù)據(jù)對(duì)象駐留內(nèi)存沒(méi)有釋放,也改善并行運(yùn)算的速度。
如代碼:
-
4、 減少?zèng)]有必要的日志輸出,將日志輸出信息級(jí)別調(diào)整為debug來(lái)減少I(mǎi)O輸出。如代碼:
-
5、 解決上述問(wèn)題后性能提高了幾倍,但是也發(fā)現(xiàn)存在其它問(wèn)題,后面發(fā)現(xiàn)有各別活動(dòng)數(shù)據(jù)量超過(guò)一百萬(wàn),創(chuàng)建view應(yīng)該按源數(shù)據(jù)的條件先過(guò)濾出來(lái)有效數(shù)據(jù)來(lái)進(jìn)行關(guān)聯(lián)。如代碼:
-
6、 自定義累計(jì)器的問(wèn)題,實(shí)現(xiàn)累加計(jì)的方法,必須實(shí)現(xiàn)幾個(gè)方法,當(dāng)時(shí)我只用到其中一個(gè)add方法,就只正確實(shí)現(xiàn)這個(gè)方法,其它的方法就隨意寫(xiě)了下,如代碼:
結(jié)果在運(yùn)算中調(diào)用累加器的時(shí)候就報(bào)出異常信息,說(shuō)必須實(shí)現(xiàn)copy及reset方法,后面才知道調(diào)用累加器時(shí)候,它的實(shí)現(xiàn)方法中會(huì)逐個(gè)會(huì)被調(diào)用到,調(diào)用foreachPartition 的時(shí)候,會(huì)為每個(gè)Partition執(zhí)行一次自定義累加器的copy-》reset-》isZero方法。
-
7、 使用累加器來(lái)收集數(shù)據(jù)還有一個(gè)問(wèn)題,它存儲(chǔ)的數(shù)據(jù)對(duì)象字段非常多是一個(gè)很大的對(duì)象集,它又是一個(gè)共享的數(shù)據(jù)變量,分發(fā)到各個(gè)機(jī)器上進(jìn)行操作,它達(dá)到一定的數(shù)據(jù)量才會(huì)入庫(kù)及清空數(shù)據(jù),這樣導(dǎo)致driver端上經(jīng)常FULL-GC,分片數(shù)據(jù)入庫(kù)依賴(lài)于它。如代碼:
解決此問(wèn)題,將累加器移除掉,任務(wù)分區(qū)的數(shù)據(jù)按一定規(guī)則進(jìn)行排序,保證同源數(shù)據(jù)的id相同的數(shù)據(jù)在一個(gè)分區(qū)里,后面累加器修改成局部的Map集合收集數(shù)據(jù),這樣就將driver端入庫(kù)操作移到excutor端。
8、 在不斷的優(yōu)化過(guò)程中,發(fā)現(xiàn)自己的代碼存在很多問(wèn)題,每次review代碼時(shí),都會(huì)發(fā)現(xiàn)可以?xún)?yōu)化之處,如方法及變量命名不清晰,方法體的代碼過(guò)長(zhǎng),存在冗余的代碼,結(jié)構(gòu)不夠清晰,注釋太少且不清晰等等。
9、 SparkUI的重要性,有什么問(wèn)題都可以在上面看到,從中也可以發(fā)現(xiàn)出潛在的問(wèn)題,也能在上面實(shí)時(shí)觀察出任務(wù)的運(yùn)行情況;SparkUI上面雖然只有六個(gè)菜單,如何在其中找到我想要數(shù)據(jù)分析及技術(shù)分析的信息,還是值得我學(xué)習(xí)及研究的。
總結(jié)
經(jīng)過(guò)一系列的改造后,從之前的每小時(shí)處理200百萬(wàn)數(shù)據(jù)提升到每小時(shí)處理上千萬(wàn)的數(shù)據(jù)量;在該過(guò)程中遇到很多的問(wèn)題及困難,主要是自己對(duì)spark方面的知識(shí)了解不夠深入,在代碼結(jié)構(gòu)及細(xì)節(jié)上處理上面還不夠細(xì)致。通過(guò)同事們幫助下,順利地解決了spark性能上的問(wèn)題
作者:唯品會(huì)-蔣先輝
日期:2018.1.22


















