在作保險在線理賠業務中,用戶申請理賠時,須要上傳理賠單據照片,申請理賠後在理賠詳情頁並列展現上傳的單據縮略圖照片,單擊某張照片後可放大查看該張照片。介於現在的手機拍照後造成的照片基本在2-4MB左右的大小,在展現理賠詳情縮略圖時,若是使用原圖圖片URL進行展現,將會耗費巨大的流量來加載圖片。對於用戶處於網速很差的環境下,會形成圖片加載不出來的問題。同時也爲了節省用戶的流量,故要針對上傳的圖片進行壓縮處理,展現的縮略圖使用壓縮後的圖片展現,點擊圖片放大後由於清晰度要求從而展現原圖的URL來加載圖片。html
前端採用VUE,後端JavaWeb項目。能夠理解爲H5端的圖片上傳。 大致的圖片上傳頁面展現以下: 前端
基本交互邏輯以下:java
根據阿里雲的OSS對象存儲幫助文檔可知,OSS圖片處理詳細介紹,阿里雲針對圖片有多種可選擇的處理方式:後端
選擇其中的圖片縮放便可實現場景中的功能。bash
騰訊雲的COS對象存儲也提供了圖片的相應處理功能,COS圖片處理詳細介紹,針對上傳至COS的圖片,只需在url中加入一些參數便可實現圖片壓縮。服務器
只不過貌似提供該功能的爲騰訊雲的另外一款產品 數據萬象 CI,若是使用的話,須要購買兩款產品。其功能以下:異步
Thumbnailator 是一個優秀的圖片處理的Google開源Java類庫。處理效果遠比Java API的好。從API提供現有的圖像文件和圖像對象的類中簡化了處理過程,兩三行代碼就可以從現有圖片生成處理後的圖片,且容許微調圖片的生成方式,同時保持了須要寫入的最低限度的代碼量。還支持對一個目錄的全部圖片進行批量處理操做。ide
其提供的功能以下:工具
大致就是使用javax.imageio.ImageIO類是讀取圖片後,針對圖片作下壓縮處理後,在寫入文件流中。測試
由於公司問題,要求使用公司的基礎服務對象存儲功能(其實就是對騰訊雲作了層封裝),詢問相關負責人得知,沒有針對圖片進行壓縮的API可提供。因此只好選擇 Google開源Thumbnailator 或者 java的ImageIO來本身作圖片壓縮了。PS:若是條件容許的話,仍是推薦使用雲存儲三方提供的圖片處理API來處理圖片。
引入Thumbnailator的依賴包
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
複製代碼
實現圖片壓縮的代碼很簡單,只有一行
//圖片尺寸不變,壓縮圖片文件大小outputQuality實現,參數1爲最高質量
Thumbnails.of("原圖文件的路徑")
.scale(1f)
.outputQuality(0.5f)
.toFile("壓縮後文件的路徑");
複製代碼
發佈到dev環境中,使用壓測,上傳個3MB的圖片,起10個線程,循環5次,10秒內依次啓動完全部線程,測試結果以下:
結果很慘,50個請求,Error率達到百分之80。 返回的結果爲503,服務直接不可用了此次改用小圖片進行壓縮上傳處理,大小:300KB左右,壓測指標仍是按照上面的來。 壓測結果以下:
錯誤率0%,初步懷疑爲圖片過大,在進行壓縮時,致使了OOM問題。在壓縮方法裏打印出內存的佔用狀況,加入以下日誌:
log.info("內存剩餘:{},總內存:{},百分比:{}",runtime.freeMemory(),runtime.totalMemory(),new BigDecimal(runtime.freeMemory()).divide(new BigDecimal(runtime.totalMemory()),2,RoundingMode.HALF_DOWN));
Thumbnails.of(executeFile.getAbsolutePath()).scale(0.5f).outputQuality(0.25f).toFile(executeFile1);
log.info("壓縮後內存剩餘:{},總內存:{},百分比:{}",runtime.freeMemory(),runtime.totalMemory(),new BigDecimal(runtime.freeMemory()).divide(new BigDecimal(runtime.totalMemory()),2,RoundingMode.HALF_DOWN));
複製代碼
再次壓測3MB的圖片,一樣的,失敗率達到百分之80多,同時服務進入宕機重啓狀態。
查看日誌狀況: 物理內存16G能由於圖片壓縮致使剩餘可用內存佔用率不到百分之30,基本能夠確認是由於大圖片處理耗盡了內存致使OOM問題。在使用Thumbnailator時出現了OOM問題,可是其使用方法只有一行代碼,沒法針對其內部使用的對象進行資源釋放,因此使用原生的Java類庫中ImageIO來處理圖片。 關鍵有三個類:ImageIO、BufferedImage、Graphics
public static BufferedImage read(File input) throws IOException
public static boolean write(RenderedImage im,String formatName,File output)throws IOException
複製代碼
測試關鍵代碼以下:
BufferedImage bufferedImage=new BufferedImage(217,190,BufferedImage.TYPE_INT_RGB);
Graphics graphics=bufferedImage.getGraphics();
//讀取原始位圖
Image srcImage= ImageIO.read(executeFile);
//將原始位圖縮小後繪製到bufferedImage對象中
graphics.drawImage(srcImage,0,0,217,190,null);
//將bufferedImage對象輸出到磁盤上
ImageIO.write(bufferedImage,"jpg",executeFileNew);
複製代碼
壓測指標仍是按照上傳個3MB的圖片,起10個線程,循環5次,10秒內依次啓動完全部線程來。 結果一樣不理想,一樣會形成OOM。
經過網上查詢資料獲得一些結論:
儘可能不去用javax.imageio.ImageIO.read(_file)來讀取圖片,java還提供了使用Toolkit這個工具包能夠用來處理圖片。 因此調整代碼以下:
Toolkit toolkit = Toolkit.getDefaultToolkit();
srcImage = toolkit.getImage(executeFile.getAbsolutePath());
int wideth = -1;
int height = -1;
boolean flag = true;
//Toolkit加載是異步的,它有一個觀察器,要等待它回加載完成才能再draw出去。
while (flag) {
wideth = srcImage.getWidth(null); // 獲得源圖寬
height = srcImage.getHeight(null); // 獲得源圖長
if (wideth > 0 && height > 0) {
flag = false;
} else {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
bufferedImage=new BufferedImage(300,400,BufferedImage.TYPE_INT_RGB);
graphics=bufferedImage.getGraphics();
graphics.drawImage(srcImage,0,0,300,400,null);
ImageIO.write(bufferedImage,suffix.substring(1,suffix.length()),executeFile1);
if(srcImage != null) {
srcImage.flush();
}
if(bufferedImage != null) {
bufferedImage.flush();
}
if(graphics!= null) {
graphics.dispose();
}
複製代碼
壓測指標上傳個3MB的圖片,起10個線程,循環5次,10秒內依次啓動完全部線程來。
測試結果所有壓縮成功,日誌打印的內存佔用狀況: 基本維持在百分之60沒有發生太大的變化壓測指標增到到起10個線程,每一個線程循環10次,5秒內依次啓動完全部線程。 結果報告以下:
結果所有壓縮成功。每秒吞吐量在3.2,由於是dev環境,反襯在生產環境中徹底夠用。
內存狀況:一樣維持在百分之60左右