跳至主要內容
Skip to content

檔案上傳系統設計 (8) —— 分片上傳的前端排程

當你的應用程式需要支持使用者上傳數百 MB 甚至是數 GB 的檔案(例如影片或大型壓縮檔)時,簡單的單次 POST 請求就顯得非常脆弱。任何網路的閃斷或頁面重新整理都會讓之前的進度付諸流水。

這就是 分片上傳 (Chunked Upload) 大顯身手的時候。


一、 核心原理:Blob.slice()

在 Web 瀏覽器中,File 物件繼承自 BlobBlob 提供了一個非常強大的方法:.slice(),它能讓我們像切割字串一樣切割檔案,且 不會 將檔案內容讀入記憶體(這對大檔案至關重要)。

javascript
const CHUNK_SIZE = 2 * 1024 * 1024; // 每片 2MB
const chunks = [];
let current = 0;

while (current < file.size) {
  chunks.push(file.slice(current, current + CHUNK_SIZE));
  current += CHUNK_SIZE;
}

二、 併發控制:為什麼不能一次傳完?

假設你把一個 1GB 的檔案切成了 500 片,如果你同時發出 500 個請求:

  1. 瀏覽器限制:大多數瀏覽器對同一個網域同時發出的請求數有限制(通常是 6~10 個),剩下的會排隊(Pending)。
  2. 伺服器壓力:突然爆發的請求可能會讓伺服器資源分配失調。

推薦做法:限制併發數 (Promise Pool)

我們應該建立一個「隊列」,確保同時只有 N 個分片在傳送。

javascript
async function uploadWithConcurrency(chunks, limit = 3) {
  const tasks = chunks.map((chunk, index) => ({ chunk, index }));
  const results = [];

  async function run() {
    while (tasks.length > 0) {
      const task = tasks.shift();
      try {
        await uploadChunk(task.chunk, task.index);
        results.push(task.index);
      } catch (err) {
        // 錯誤處理:重新加入隊列或回報
      }
    }
  }

  // 啟動指定數量的併發
  await Promise.all(Array.from({ length: limit }, run));
}

三、 斷點續傳:不用重頭再來

這是分片上傳最迷人的功能。如果上傳中途斷網,使用者重新開始時,系統應該只需補齊還沒傳的部分。

實作流程:

  1. 檢查狀態:在正式上傳前,先將檔案 Hash 發給後端。
  2. 回傳進度:後端根據 Hash 查出已存在的分片索引(例如:[0, 1, 2, 5])。
  3. 過濾與補傳:前端過濾掉已存在的分片,只傳送 [3, 4, 6, ...]

四、 最後一哩路:合併請求 (Merge)

當前端確定所有分片都上傳成功後,必須發送一個「合併通知」給後端。

後端收到通知後,會將所有編號後的分片拼湊成一個完整的檔案,並進行最終的 Hash 校驗以確保內容完整無誤。


總結

分片上傳雖然在前端增加了邏輯複雜度,但它帶來的穩定性與彈性是不可替代的:

  1. 穩定性:不怕網路閃斷。
  2. 靈活性:可隨時暫停與恢復。
  3. 性能:合理利用併發提升傳輸速度。

下一篇,我們將討論進階的流程管理:多媒體處理流水線。


️ 進階挑戰

在併發上傳時,如果其中一個分片失敗了,為了確保最終檔案正確,你應該「立刻停止所有上傳」還是「針對失敗的分片進行有限次數的重試」?這兩種做法分別適用於什麼情境?


延伸閱讀與資源

← 上一章:客戶端預處理的威力 | 返回專案首頁 | 下一章:多媒體處理流水線