檔案上傳系統設計 (8) —— 分片上傳的前端排程
當你的應用程式需要支持使用者上傳數百 MB 甚至是數 GB 的檔案(例如影片或大型壓縮檔)時,簡單的單次 POST 請求就顯得非常脆弱。任何網路的閃斷或頁面重新整理都會讓之前的進度付諸流水。
這就是 分片上傳 (Chunked Upload) 大顯身手的時候。
一、 核心原理:Blob.slice()
在 Web 瀏覽器中,File 物件繼承自 Blob。Blob 提供了一個非常強大的方法:.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 個請求:
- 瀏覽器限制:大多數瀏覽器對同一個網域同時發出的請求數有限制(通常是 6~10 個),剩下的會排隊(Pending)。
- 伺服器壓力:突然爆發的請求可能會讓伺服器資源分配失調。
推薦做法:限制併發數 (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));
}三、 斷點續傳:不用重頭再來
這是分片上傳最迷人的功能。如果上傳中途斷網,使用者重新開始時,系統應該只需補齊還沒傳的部分。
實作流程:
- 檢查狀態:在正式上傳前,先將檔案 Hash 發給後端。
- 回傳進度:後端根據 Hash 查出已存在的分片索引(例如:
[0, 1, 2, 5])。 - 過濾與補傳:前端過濾掉已存在的分片,只傳送
[3, 4, 6, ...]。
四、 最後一哩路:合併請求 (Merge)
當前端確定所有分片都上傳成功後,必須發送一個「合併通知」給後端。
後端收到通知後,會將所有編號後的分片拼湊成一個完整的檔案,並進行最終的 Hash 校驗以確保內容完整無誤。
總結
分片上傳雖然在前端增加了邏輯複雜度,但它帶來的穩定性與彈性是不可替代的:
- 穩定性:不怕網路閃斷。
- 靈活性:可隨時暫停與恢復。
- 性能:合理利用併發提升傳輸速度。
下一篇,我們將討論進階的流程管理:多媒體處理流水線。
️ 進階挑戰
在併發上傳時,如果其中一個分片失敗了,為了確保最終檔案正確,你應該「立刻停止所有上傳」還是「針對失敗的分片進行有限次數的重試」?這兩種做法分別適用於什麼情境?