記一次圖片壓縮引發的服務宕機優化

場景

在作保險在線理賠業務中,用戶申請理賠時,須要上傳理賠單據照片,申請理賠後在理賠詳情頁並列展現上傳的單據縮略圖照片,單擊某張照片後可放大查看該張照片。介於現在的手機拍照後造成的照片基本在2-4MB左右的大小,在展現理賠詳情縮略圖時,若是使用原圖圖片URL進行展現,將會耗費巨大的流量來加載圖片。對於用戶處於網速很差的環境下,會形成圖片加載不出來的問題。同時也爲了節省用戶的流量,故要針對上傳的圖片進行壓縮處理,展現的縮略圖使用壓縮後的圖片展現,點擊圖片放大後由於清晰度要求從而展現原圖的URL來加載圖片。html

前端採用VUE,後端JavaWeb項目。能夠理解爲H5端的圖片上傳。 大致的圖片上傳頁面展現以下: 前端

基本交互邏輯以下:java

壓縮方案調研

阿里雲的OSS對象存儲

根據阿里雲的OSS對象存儲幫助文檔可知,OSS圖片處理詳細介紹,阿里雲針對圖片有多種可選擇的處理方式:後端

  • 獲取圖片信息
  • 圖片格式轉換
  • 圖片縮放、裁剪、旋轉
  • 圖片添加圖片、文字、圖文混合水印
  • 自定義圖片處理樣式
  • 經過管道順序調用多種圖片處理功能

選擇其中的圖片縮放便可實現場景中的功能。bash

騰訊雲的COS對象存儲

騰訊雲的COS對象存儲也提供了圖片的相應處理功能,COS圖片處理詳細介紹,針對上傳至COS的圖片,只需在url中加入一些參數便可實現圖片壓縮。服務器

只不過貌似提供該功能的爲騰訊雲的另外一款產品 數據萬象 CI,若是使用的話,須要購買兩款產品。其功能以下:異步

  • 圖片處理 數據萬象可對存儲在 COS 上的圖片資源直接進行處理,如縮放、裁剪、轉碼、壓縮、水印等。
  • 持久化處理 經過數據萬象,您可在上傳時直接實現圖片處理,也能夠對已存放在 COS 的圖片資源進行處理,並將處理結果持久化保存。
  • 原圖保護 針對圖片這種易被非法盜用的資源,數據萬象提供原圖保護功能,開啓後資源僅能以樣式化 url 訪問,有效防止原圖泄露。
  • 盲水印 數據萬象提供獨有的盲水印功能,可以將水印圖以不可見形式添加到圖片頻域,在圖片資源被攻擊泄露後(裁剪、塗抹等)仍可提取出水印信息,有效鑑權追責。
  • 域名管理 您可經過域名管理選擇是否開啓 CDN 加速功能、設置自定義域名,並可經過設置防盜鏈(黑白名單)來防止流量盜刷。
  • 回源設置 數據萬象的回源設置功能能夠幫助您在不中斷訪問的狀況下,無縫遷移原站內容至騰訊雲對象存儲。
  • 內容審覈 敏感內容審覈功能提供鑑黃、鑑政、鑑暴恐等多種類型的敏感內容檢測,幫助您有效規避違規風險。

Google開源Thumbnailator

Thumbnailator 是一個優秀的圖片處理的Google開源Java類庫。處理效果遠比Java API的好。從API提供現有的圖像文件和圖像對象的類中簡化了處理過程,兩三行代碼就可以從現有圖片生成處理後的圖片,且容許微調圖片的生成方式,同時保持了須要寫入的最低限度的代碼量。還支持對一個目錄的全部圖片進行批量處理操做。ide

其提供的功能以下:工具

  • 圖片縮放
  • 區域裁剪
  • 水印
  • 旋轉
  • 保持比例

java的ImageIO處理圖片

大致就是使用javax.imageio.ImageIO類是讀取圖片後,針對圖片作下壓縮處理後,在寫入文件流中。測試

調研結論

由於公司問題,要求使用公司的基礎服務對象存儲功能(其實就是對騰訊雲作了層封裝),詢問相關負責人得知,沒有針對圖片進行壓縮的API可提供。因此只好選擇 Google開源Thumbnailator 或者 java的ImageIO來本身作圖片壓縮了。PS:若是條件容許的話,仍是推薦使用雲存儲三方提供的圖片處理API來處理圖片。

使用Thumbnailator來作圖片壓縮

使用

引入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問題。

java的ImageIO處理圖片

在使用Thumbnailator時出現了OOM問題,可是其使用方法只有一行代碼,沒法針對其內部使用的對象進行資源釋放,因此使用原生的Java類庫中ImageIO來處理圖片。 關鍵有三個類:ImageIO、BufferedImage、Graphics

  • ImageIO類包含兩個靜態方法:read()和write(),經過這兩個方法便可完成對位圖文件的讀寫,調用write()方法輸出圖形文件時須要指定輸出的圖形格式。
public static BufferedImage read(File input) throws IOException
public static boolean write(RenderedImage im,String formatName,File output)throws IOException
複製代碼
  • Image類表明位圖,但它是一個抽象類,沒法直接建立Image對象,爲此java爲它提供了一個BufferedImage子類,這個子類是一個能夠訪問圖像數據緩衝區的Image實現類。該類提供了一個簡單的構造器:BufferedImage(int width,int height,int imageType):建立指定大小、指定圖像類型的BufferedImage對象。除此以外,還提供一個getGraphics()方法返回該對象的Graphics對象,從而容許經過該Graphics對象向BufferedImage中添加圖形。
  • Graphics是一個抽象的畫筆對象,它能夠在組件上繪製豐富多彩的幾何圖形和位圖。它提供有一個重要方法,將一個img對象的原始圖形寬度縮小爲width,高度縮小爲height,添加到BufferedImage對象的(x,y)處:public abstract boolean drawImage(Image img, int x, int y,int width,int height, ImageObserver observer)

測試關鍵代碼以下:

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。

經過網上查詢資料獲得一些結論:

  • 以上這個方法,在處理相對較小的圖片,好比1M左右的圖片,是能夠的,可是若是 圖片較大,在Image src = javax.imageio.ImageIO.read(_file); 就拋內存溢出;
  • 全部壓縮格式的圖片,都被轉換成像素點陣,存放到內存當中,很是消耗資源的,會按照圖片的像素轉換,若是爲3MB的圖片,處理的數據大小在幾十MB左右。

最終解決方案

儘可能不去用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左右

總結:

  • 一、java對於圖片的處理技術在處理小圖片時,徹底夠用,可是在處理大於1MB以上的圖片時,就再也不推薦自己服務器去處理圖片。
  • 二、有條件的仍是將圖片的處理交給第三方來,調用封裝好的API等來處理圖片的各類要求。
相關文章
相關標籤/搜索