大資料量面前,毫秒必爭。一次上億資料量程式最佳化後的感想

最近在搞一個開發一個程式,資料量很大,預計大概有上億的資料量。程式中需要依次遍歷原始資料,然後根據原始資料進行資料計算以及資料整合。

大資料量面前,毫秒必爭。一次上億資料量程式最佳化後的感想

一開始專案組接到這個任務以後,一直在猶豫,生產伺服器是否可以跑得動,畢竟在開發環境很難實現像生產環境那樣的資料量以及伺服器配置。專案組一直在論證,是否可以實現此功能,萬一上線以後跑不動,或者需要跑好長時間,根本就沒有辦法交代。

最終專案組決定還是試試看。

首先,我們找到了一臺2C4G的資料庫伺服器以及應用伺服器,生產環境資料庫是16C64G,應用伺服器是4C8G。在測試環境大概造了1000萬的資料量,以此資料量作為開發測試目標,於生產伺服器的上億資料量作為對標。採用多執行緒的方式完成。

大資料量面前,毫秒必爭。一次上億資料量程式最佳化後的感想

第一次開發完成後,程式根本跑不動,應用伺服器屢屢的記憶體溢位。不停的報java。lang。OutOfMemoryError: PermGen space。透過查詢發現,mysql在執行查詢操作時候,會把結果集資料首先拉到本地伺服器,如此大的資料量,記憶體不掛才怪。所以,趕緊修改程式。

p = conn。prepareStatement(sql,ResultSet。TYPE_FORWARD_ONLY, ResultSet。CONCUR_READ_ONLY);

p。setFetchSize(Integer。MIN_VALUE);

p。setFetchDirection(ResultSet。FETCH_REVERSE);

大資料量面前,毫秒必爭。一次上億資料量程式最佳化後的感想

修改完成後,記憶體溢位的問題已經解決了,但是發現在執行

p。executeBatch()

的時候特別的慢,慢到令人髮指,不可思議。後來調整了mysql jdbc連線引數,在連線引數中添加了

rewriteBatched

Statement=true

引數,開啟了批量出來,原來不新增的話,jdbc還是一條一條向伺服器提交。

大資料量面前,毫秒必爭。一次上億資料量程式最佳化後的感想

再次發動執行,發現雖然

p。executeBatch()

執行速度快了,但是還是不是讓人特別滿意,所以又修改了sql程式拼接方法,最佳化insert執行語句,由原來的

insert into tables (name1,name2,。。。) values (?,?,。。。。)

insert into tables (name1,name2,。。。) values (?,?,。。。。)

insert into tables (name1,name2,。。。) values (?,?,。。。。)

修改為

insert into tables (name1,name2,。。。) values (?,?,。。。。),(?,?,。。。。),(?,?,。。。。),(?,?,。。。。)

效率提升了很多。

大資料量面前,毫秒必爭。一次上億資料量程式最佳化後的感想

程式處理過程中,需要有update操作,如果資料存在,需要update,之前的設計是先按照主鍵查詢,如資料存在,則update,不存在則執行insert操作。但是效率還是不滿意。所以再次修改程式,在insert語句後添加了ON DUPLICATE KEY UPDATE引數。

insert into tables (name1,name2,。。。) values (?,?,。。。。),(?,?,。。。。),(?,?,。。。。),(?,?,。。。。) ON DUPLICATE KEY UPDATE name1=values(name1)……

修改後,程式不用再執行先查詢後判斷是否需要更新等等。

程式修改到這裡,其實就可以順利完成了,1000萬的資料在測試伺服器6個併發的時候需要140分鐘,程式每分鐘可以處理7。1萬筆資料。

大資料量面前,毫秒必爭。一次上億資料量程式最佳化後的感想

但是,我一直在思考,是否可以再快一些,而且我發現,6個併發同時執行時,應用伺服器的CPU飈的非常高,每次都可以到95%以上,而資料庫伺服器其實並不是非常的繁忙。這個不正常,按說資料庫伺服器應該很忙才對,所以可能還是有最佳化的地方。

出於探索的精神,專案組再次啟程,首先就從CPU下手。

大資料量面前,毫秒必爭。一次上億資料量程式最佳化後的感想

透過阿里巴巴的arthas工具,我們成功的開始監控某個執行緒操作,每隔10秒鐘抓一次執行緒正在執行的內容,我們驚奇的發現,大部分的時候,執行緒都在執行同一個操作,就是Spring EL表示式解析、計算工作。

為了方便程式開發和配置,我們此採用了配置檔案配置的方式,對於一些計算項,採用Spring EL表示式配置,方便專案組除錯、調整。難道真的是EL表示式拖了後腿?

我們首先把所有的EL表示式都去掉,改用預設值的方式測試了一次,當程式啟動時候,日誌的重新整理速度讓我們目不暇接,20分鐘就完成了批次。專案組頓時興奮不已,就是它拖了後腿。

大資料量面前,毫秒必爭。一次上億資料量程式最佳化後的感想

透過前後計算,我們處理每條資料執行了5個EL表示式,1000萬行資料就是5000萬次,平均計算了下,每個EL表示式需要執行7毫秒,其實也不長看起來,但是乘以5000萬,那就是120分鐘。

然後,專案組立即決定取消EL表示式配置的模式,採用java程式計算的方式完成需要計算的業務資料來完成,雖然可能損失了靈活配置,但是提交了執行效率,這筆賬划得來。

修改後,程式在22分鐘就執行完成,每分鐘完成45。5萬筆資料,CPU峰值降到了60%。

7毫秒,看起來真的不多,但是架不住幾千萬上億次的呼叫。

這次調優讓專案組變得很興奮,現在專案組成員在日常開發過程中,已經養成了非常好的調優的好習慣,執行稍微有點慢或者不滿意,就不停的查詢、定位、解決,我彷彿已經可以看到一批高階的人才已經在冉冉升起了。因為調優真的很複雜,涉及到網路、IO、應用程式設計、邏輯梳理、資料庫等等各方各面,只有不斷的追求、學習才能用的好,用的精。