導讀:分片上傳、斷點續傳,這兩個名詞對于做過或者熟悉檔案上傳的朋友來說應該不會陌生,總結本篇文章希望對從事相關作業的同學能夠有所幫助或者啟發,
當我們的檔案特別大的時候,上傳是不是需要很長的時間啊,這么長時間的長連接,如果網路波動了呢?中間網路斷開了呢?在這么長時間的程序中如果出現不穩定的情況,本次上傳的所有內容就全部失敗了,又要重新上傳,
分片上傳,就是將所要上傳的檔案,按照一定的大小,將整個檔案分隔成多個資料塊(我們稱之為 Part)來進行分別上傳,上傳完之后再由服務端對所有上傳的檔案進行匯總整合成原始的檔案,分片上傳不僅可以避免因網路環境不好導致的一直需要從檔案起始位置還是上傳的問題,還能使用多執行緒對不同分塊資料進行并發發送,提高發送效率,降低發送時間,
一、背景
在系統用戶量突增以后,為了更好適配各群體的定制化需求,業務上慢慢實作了支持 C 端用戶自定義布局和配置,導致配置資料讀取 IO 激增,
為了更好優化此類場景,將用戶自定義配置靜態化管理!也就是將對應的組態檔生成靜態檔案,在生成靜態檔案的程序中遇到棘手的問題,組態檔檔案過大導致在檔案上傳服務器等待時間過長,致使整個業務場景性能整體下滑,

二、生成組態檔
生成檔案三大要素
-
檔案名
-
檔案內容
-
檔案存盤格式
檔案內容、檔案存盤格式都好理解和處理,當然先前整理過微服務中常用的加密方式
-
微服務架構 | 微服務有哪些常用的加密方式 (一)
-
微服務架構 | 資料加密有哪些常用的加密方式(二)
這里做下補充說明,如果要想對檔案內容進行加密可以考慮,但是本文的案例場景對于配置資訊保密程度較低,這里不做拓展,
而對于檔案名的命名規范具體結合業務場景來定,通常都是以檔案概要+時間戳格式為主,但是這類命名規范容易導致檔案名沖突,造成沒有必要的后續麻煩,
所以我這里對于檔案名的命名做了特殊處理,有處理過前端 Route 路由經驗的應該能聯想到,檔案名可以通過基于內容生成 Hash 值來代替,
在Spring 3.0 之后提供了計算摘要的的方法,
DigestUtils#md
復制代碼
回傳給定位元組的 MD5 摘要的十六進制字串表示形式,

md5DigestAsHex 原始碼
/**
* 計算摘要的位元組
* @param 一個十六進制摘要字符
* @return 串回傳給定位元組的 MD5 摘要的十六進制字串表示形式,
*/
public static String md5DigestAsHex(byte[] bytes) {
return digestAsHexString(MD5_ALGORITHM_NAME, bytes);
}
檔案名、內容、后綴(存盤格式)確定后直接生成檔案
/**
* 直接根據內容生成 檔案
*/
public static void generateFile(String destDirPath, String fileName, String content) throws FileZipException {
File targetFile = new File(destDirPath + File.separator + fileName);
//確保父級目錄存在
if (!targetFile.getParentFile().exists()) {
if (!targetFile.getParentFile().mkdirs()) {
throw new FileZipException(" path is not found ");
}
}
//設定檔案編碼格式
try (PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile), ENCODING)))
) {
writer.write(content);
return;
} catch (Exception e) {
throw new FileZipException("create file error",e);
}
}
通過內容生成檔案優點不言而喻,可以極大減少我們主動基于內容比較來生成新的檔案、如果檔案內容較大生成對應的檔案名相同則表示內容未做任何調整,此時我們也就不用做后續的檔案更新操作,
三、分片上傳附件
所謂的分片上傳,就是將所要上傳的檔案,按照一定的大小,將整個檔案分隔成多個資料塊(我們稱之為 Part)來進行分別上傳,上傳完之后再由服務端對所有上傳的檔案進行匯總整合成原始的檔案,分片上傳不僅可以避免因網路環境不好導致的一直需要從檔案起始位置還是上傳的問題,還能使用多執行緒對不同分塊資料進行并發發送,提高發送效率,降低發送時間,

分片上傳主要適用于以下幾種場景:
-
網路環境不好:當出現上傳失敗的時候,可以對失敗的 Part 進行獨立的重試,而不需要重新上傳其他的 Part,
-
斷點續傳:中途暫停之后,可以從上次上傳完成的 Part 的位置繼續上傳,
-
加速上傳:要上傳到 OSS 的本地檔案很大的時候,可以并行上傳多個 Part 以加快上傳,
-
流式上傳:可以在需要上傳的檔案大小還不確定的情況下開始上傳,這種場景在視頻監控等行業應用中比較常見,
-
檔案較大:一般檔案比較大時,默認情況下一般都會采用分片上傳,

分片上傳的整個流程大致如下:
-
將需要上傳的檔案按照一定的分割規則,分割成相同大小的資料塊;
-
初始化一個分片上傳任務,回傳本次分片上傳唯一標識;
-
按照一定的策略(串行或并行)發送各個分片資料塊;
-
發送完成后,服務端根據判斷資料上傳是否完整,如果完整,則進行資料塊合成得到原始檔案
? 定義分片規則大小
默認情況都以檔案達到 20MB 進行強制分片
/**
* 強制分片檔案大小(20MB)
*/
long FORCE_SLICE_FILE_SIZE = 20L* 1024 * 1024;
為了方便除錯,強制分片檔案的閾值調整為 1KB
? 定義分片上傳物件
如上圖紅色序號的檔案碎片,定義分片上傳物件基礎屬性包含附件檔案名、原始檔案大小、原始檔案 MD5 值、分片總數、每個分片大小、當前分片大小、當前分片序號等
定義基礎屬于便于后續對檔案合理分割、分片的合并等業務拓展,當然根據業務場景可以定義拓展屬性,
分片總數
long totalSlices = fileSize % forceSliceSize == 0 ?
fileSize / forceSliceSize : fileSize / forceSliceSize + 1;

每個分片大小
long eachSize = fileSize % totalSlices == 0 ?
fileSize / totalSlices : fileSize / totalSlices + 1;

原始檔案的 MD5 值
MD5Util.hex(file)
復制代碼
如:
當前附件大小為:3382KB,強制分片大小限制為 1024KB
通過上述計算:分片數量為 4 個,每個分片大小為 846KB

? 讀取每個分片的資料位元組
標記當前位元組下標,回圈讀取 4 個分片的資料位元組
try (InputStream inputStream = new FileInputStream(uploadVO.getFile())) {
for (int i = 0; i < sliceBytesVO.getFdTotalSlices(); i++) {
// 讀取每個分片的資料位元組
this.readSliceBytes(i, inputStream, sliceBytesVO);
// 呼叫分片上傳API的函式
String result = sliceApiCallFunction.apply(sliceBytesVO);
if (StringUtils.isEmpty(result)) {
continue;
}
return result;
}
} catch (IOException e) {
throw e;
}

三、總結
所謂的分片上傳,就是將所要上傳的檔案,按照一定的大小,將整個檔案分隔成多個資料塊(我們稱之為 Part)來進行分別上傳,
處理大檔案進行分片主要核心確定三大點
-
檔案分片粒度大小
-
分片如何讀取
-
分片如何存盤
本篇文章主要分析和處理大檔案上傳程序中如何針對大檔案檔案檔案內容比較、進行分片處理,合理設定分片閾值以及如何讀取和標記分片,希望對從事相關作業的同學能夠有所幫助或者啟發,后續會對分片如何存盤、標記、合并檔案進行詳細解讀,
原文:微服務架構 | 怎樣解決超大附件分片上傳?

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/394213.html
標籤:其他
