單表500萬要一個小時?也許只要5分鐘就夠了

近期我同事將原本一個多小時的批處理任務優化成了 5分鐘
讓我啞然不已,於是連忙請教..............

緣起:

近期因爲業務變動的原因,導致原本的批處理任務需要調整, 這一塊是由我同事負責. 本來批處理需要 30分鐘 加上本次新增的業務處理完需要20分鐘, 所以改動完成的批處理將近一個小時

而衆所周知批處理這一點向來都是性能殺手,有時因爲一點不注意導致性能嚴重下滑.可是一個小時這個結果也是萬萬不能接受的. 那麼該如何是好呢?




從 60 變成 30 分鐘:

首先 當前數據量大概在 500萬左右,並且一張表具有70多個字段(爲什麼會有這種表?!!(╯‵□′)╯︵┻━┻ )。

由於 全批量的 select 小部分insert 與大部分的update 於是就將目光放在了 數據庫之上。

insert 與 update:

因爲當前的情況是 單表 ,字段多(70多個字段), 數據量大。

而提起解決方案,想必大多數人都是 四字方針 “分庫,分表”。

這不能說不對,這確實是解決方案之一,而且近乎可以解決大部分場景。

對於當前來說也是可以解決的,但是使用此方案所付出的代價確實我們目前萬萬承受不起的. 因爲當前表牽扯甚廣, 使用此方案將直接導致 上線點延期.

所以當前方案排除.
在這裏插入圖片描述

因爲數據量龐大,所以 存儲過程,存儲函數、內存表等方案 因爲其 參數長度,內存大小 都是有限的,不能很好的減少jdbc 交互次數並且改動麻煩所以不做考慮.

那麼將所有數據生成到臨時表,然後使用sql統一更新如何呢?

事實上這個方案是可行的,將所有數據拼接成一條sql, 調整MySql 的sql傳輸大小參數, 然後直接傳入一條insert 語句, 插入到臨時表。

再去執行第二條更新語句, 爲了保證數據量不在繼續瘋狂遞增,將定時移植數據到臨時表 如此方可執行當前方案, 但是效果並未達到預期目標。


所以還沒完,只能再換一種方案,因爲在插入數據的時候還是很費時間,而且更新一樣耗時, 所以只能開大招了,

開始多線程處理 並且一開始就先下猛藥將數據十個分區,並且交由十個線程同時處理,一起執行insert與update, 由於當前業務不存在競爭資源,所以多線程的風險倒是並不高。

但是說實話: 理想是 500萬條數據 十個線程平均每個線程 50萬,可實際上並非如此,總有線程數據多,總有線程數據少,這是分區算法的問題。所以十個線程並沒有充分利用,但是已經達到了 30分鐘的效果。

接下來可以考慮優化分區算法和適當的減少線程消耗。




從30 變成 27 分鐘:

select

因爲將目標聚焦到 數據庫之上,已經優化完 insert與update 那麼接下來就是select 了, 減少查詢字段,削減不需要的字段,使用索引查詢,去掉一些可能導致索引不生效的判斷條件
包括但是不限於:

  • 去掉where 後的 or 、!=、is null等邏輯
  • 減少查詢字段
  • 建立關鍵索引
  • 去掉in
  • 不用like
  • 檢查表鎖

在基於當前細節優化上 將線程與分區數量擴展到 20 但是受限與 電腦性能無法達到預期結果, 可實際上這一步就算可以也不會是最終的解決方案,因爲按當前生產資源來算 10個線程 要想上線起碼得在減少幾個

從30分鐘到27分鐘是耗時最長,但是收益最小,得不償失. 但是也不算浪費時間,因爲做的都是細節上的優化。

從27 變成 5 分鐘:

由於發現數據庫並非性能瓶頸, 那麼 這麼長時間消耗到哪去呢?

那麼最後剩下的就是真相, 性能被消耗到業務邏輯代碼中了, 於是對代碼邏輯進行了反覆的核查,與排查. 對代碼結構進行優化,包括但不限於:

  • 將continue,return 判斷語句提前、

  • 減少循環(實施上業務邏輯中的所有 for 幾乎被優化完了,除了變量數據的總for)

  • 將參數提前初始化完畢

但是以上手段,用處均不大,性能提升微乎其微.

平均每條數據處理的時間上,經過日誌打印發現處理速度大概是在 266/秒 , 八萬條數據近乎 5分鐘, 何況全表將近500W 的存量數據

那就只能向辦法,把現在的 266/秒 變成 600/秒、 800/秒 乃至 1600/秒 縮短每條數據的處理時間。

於是我們把目光放到了實體類之中, 準確來說是放到了 BigDecimal 類型的字段上, 整個表將近70個字段, 整個實體類也是將近 70 多個屬性

而其中需要保證數據精確性的字段不少, 於是 放眼望去將近十幾個 BigDecimal 個屬性在做運算,

此時就只能從業務邏輯上出發了, 將需要高精度的數據保持 BigDecimal 類型不變,而有些數據只需要雙精度,也就是隻保留兩位小數的數據. 於是把注意打在了這些雙精度的數據上

因爲JAVA中基本類型double 默認也是雙精度, 於是我們做了個實驗 來測試BigDecimal 所耗時間, 那就是 將原本的運算代碼全部臨時強轉成 double類型做運算,然後在new BigDecimal 並賦值

結果是在當前場景下 一次減法運算 = 三次double強轉 和一次 new BigDecimal() 對象, 於是就對實體類起了心思,

本來是準備把 一些BigDecimal 改成 double類型,但是在這過程中卻發現大部分字段在當前業務代碼塊其實是用不上的。

於是就將原本的 70個字段,去掉冗餘,將類型修改爲基本類型 最後只保留了十幾個字段,於是每條數據在處理的時候耗時以下就降了下來。

此時才發現原來最耗費性能的不是BigDecimal 的加減乘除, 而是每次調用公共函數初始化當前實體類, 至此由原本的 30 分鐘批次變成了5分鐘

由 將近 30分鐘變成了 5分鐘, 優化將近 6倍,說到底不過是削減了每個類創建時所耗資源,優化了原本的類對象, 由原本的 70 多個字段 篩選出必備的幾個字段.

減少了每條數據初始化所耗資源,與運算時的資源消耗, 但是如果沒有之前的sql 數據庫優化 ,使得數據庫不在是瓶頸,相信也不會有如此效果。



面對大數據的時候, 確保

  1. 對象最小
  2. 通訊次數最少,
  3. 線程走起,

一般都不會慢,


謹以此文- 紀念那逝去的三天

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章