提升文件上傳性能的 4 種方式,你會嗎?


業務需求

產品經理:小明啊,我們需要做一個附件上傳的需求,內容可能是圖片、pdf 或者視頻。

小明:可以實現的,不過要限制下文件大小。最好別超過 30MB,太大了上傳比較慢,服務器壓力也大。

產品經理:溝通下來,視頻是一定要的。就限制 50MB 以下吧。

小明:可以。

A FEW DAYS LATER

測試同學:這文件上傳也太慢了吧,我試了一個 50mb 的文件,花了一分鍾。

小明:whats up,這么慢。

產品經理:不行,你這太慢了, 想辦法優化下。

優化之路

問題定位

整體的文件上傳調用鏈路如下圖:

調用鏈路

小明發現前端開始上傳,到請求到后端就花費了近 30 秒,應該是瀏覽器解析文件導致的慢。

后端服務請求文件服務也比較慢。

解決方案

小明:文件服務有異步接口嗎?

文件服務:暫時沒有。

小明:這個上傳確實很慢,有優化建議嗎?

文件服務:沒有,看了下就是這么慢。

小明:……

最后小明還是決定把后端的同步返回,調整為異步返回,降低用戶的等待時間。

把后端的實現調整了一番適應業務,前端調用后獲取異步返回標識,后端根據標識查詢文件服務同步返回的結果。

缺點也很明顯,異步上傳失敗,用戶是不知道的

不過礙於時間原因,也就是能權衡利弊,暫時上線了。

最近小明有些時間,於是就想着自己實現一個文件服務。

文件服務

礙於文件服務的功能非常原始,小明就想着自己實現一個,從以下幾個方面優化:

(1)壓縮

(2)異步

(3)秒傳

(4)並發

(5)直連

壓縮

日常開發中,盡可能和產品溝通清楚,讓用戶上傳/下載壓縮包文件。

因為網絡傳輸是非常耗時的

壓縮文件還有一個好處就是節約存儲空間,當然,一般我們不用考慮這個成本。

優點:實現簡單,效果拔群。

缺點:需要結合業務,並且說服產品。如果產品希望圖片預覽,視頻播放,壓縮就不太適用。

異步

對於比較耗時的操作,我們會自然的想到異步執行,降低用戶同步等待的時間。

服務端接收到文件內容后,返回一個請求標識,異步執行處理邏輯。

那如何獲取執行結果呢?

一般有 2 種常見方案:

(1)提供結果查詢接口

相對簡單,但是可能會有無效查詢。

(2)提供異步結果回調功能

實現比較麻煩,可以第一時間獲取執行結果。

秒傳

小伙伴們應該都用過雲盤,雲盤有時候上傳文件,非常大的文件,卻可以瞬間上傳完成。

這是如何實現的呢?

每一個文件內容,都對應唯一的文件哈希值。

我們可以在上傳之前,查詢該哈希值是否存在,如果已經存在,則直接增加一個引用即可,跳過了文件傳輸的環節。

當然,這個只在你的用戶文件數據量很大,且有一定重復率的時候優勢才能體現出來。

偽代碼如下:

public FileUploadResponse uploadByHash(final String fileName,
                                       final String fileBase64) {
    FileUploadResponse response = new FileUploadResponse();

    //判斷文件是否存在
    String fileHash = Md5Util.md5(fileBase64);
    FileInfoExistsResponse fileInfoExistsResponse = fileInfoExists(fileHash);
    if (!RespCodeConst.SUCCESS.equals(fileInfoExistsResponse.getRespCode())) {
        response.setRespCode(fileInfoExistsResponse.getRespCode());
        response.setRespMessage(fileInfoExistsResponse.getRespMessage());
        return response;
    }

    Boolean exists = fileInfoExistsResponse.getExists();
    FileUploadByHashRequest request = new FileUploadByHashRequest();
    request.setFileName(fileName);
    request.setFileHash(fileHash);
    request.setAsyncFlag(asyncFlag);
    // 文件不存在再上傳內容
    if (!Boolean.TRUE.equals(exists)) {
        request.setFileBase64(fileBase64);
    }

    // 調用服務端
    return fillAndCallServer(request, "api/file/uploadByHash", FileUploadResponse.class);
}

並發

另一種方式就是對一個比較大的文件進行切分。

比如 100MB 的文件,切成 10 個子文件,然后並發上傳。一個文件對應唯一的批次號。

下載的時候,根據批次號,並發下載文件,拼接成一個完整的文件。

偽代碼如下:

public FileUploadResponse concurrentUpload(final String fileName,
                                           final String fileBase64) {
    // 首先進行分段
    int limitSize = fileBase64.length() / 10;
    final List<String> segments = StringUtil.splitByLength(fileBase64, limitSize);

    // 並發上傳
    int size = segments.size();
    final ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
    final CountDownLatch lock = new CountDownLatch(size);

    for(int i = 0; i < segments.size(); i++) {
        final int index = i;
        Thread t = new Thread() {
            public void run() {
               // 並發上傳
               // countDown
               lock.countDown();
            }
        };
        t.start();
    }

    // 等待完成
    lock.await();

    // 針對上傳后的信息處理
}

直連

當然,還有一種策略就是客戶端直接訪問服務端,跳過后端服務。

文件直連

當然,這個前提要求文件服務必須提供 HTTP 文件上傳接口。

還需要考慮安全問題,最好是前端調用后端,獲取授權 token,然后攜帶 token 進行文件上傳。

拓展閱讀

提升文件上傳性能的 4 種方式,你會嗎?

異步查詢轉同步的 7 種實現方式

java壓縮歸檔算法框架工具 compress

小結

文件上傳是非常常見的業務需求,上傳的性能問題是肯定要考慮和優化的一個問題。

上面的幾種方法可以靈活的組合使用,結合自己的業務進行更好的實踐。

希望本文對你有所幫助,如果喜歡,歡迎點贊收藏轉發一波。

我是老馬,期待與你的下次重逢。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM