- vừa được xem lúc

Tăng tốc quá trình upload file lớn với kỹ thuật phân mảnh và tải lên đa luồng

0 0 23

Người đăng: Nguyen Van Tuan

Theo Viblo Asia

I. Giới thiệu

Trong thế giới kỹ thuật số ngày nay, việc tải lên và chia sẻ các file lớn đã trở thành một yêu cầu phổ biến. Tuy nhiên, việc xử lý quá trình upload file lớn có thể gặp nhiều thách thức như giới hạn kích thước tệp tin, tốc độ mạng chậm hay mất kết nối. Để giải quyết vấn đề này, kỹ thuật phân mảnh và tải lên đa luồng ra đời. Trong bài viết này, chúng ta sẽ khám phá cách sử dụng kỹ thuật này để tăng tốc quá trình upload file lớn.

image.png

II. Kỹ thuật phân mảnh và multipart upload

Phân mảnh (chunking) và tải lên đa luồng (multipart upload) là hai khái niệm quan trọng trong việc xử lý quá trình upload file lớn. Dưới đây là giải thích chi tiết về hai khái niệm này:

1. Phân mảnh

Phân mảnh (chunking) là quá trình chia nhỏ một tệp tin lớn thành các phần nhỏ hơn gọi là "chunk". Mỗi chunk có kích thước nhỏ hơn so với tệp tin gốc, điều này giúp quá trình upload trở nên dễ dàng và ổn định hơn. Các chunk được tải lên đồng thời lên máy chủ mục tiêu.

Việc phân mảnh tệp tin giúp chia nhỏ công việc upload thành nhiều phần nhỏ, giảm thiểu tác động của mất kết nối hoặc tốc độ mạng chậm. Nếu một chunk bị lỗi trong quá trình upload, chỉ cần tải lên lại phần đó mà không cần upload lại toàn bộ tệp tin.

2. Tải lên đa luồng

Tải lên đa luồng (multipart upload) là quá trình tải lên một tệp tin lớn bằng cách chia thành nhiều phần nhỏ hơn gọi là "part" và tải lên đồng thời lên máy chủ. Mỗi part được tải lên độc lập và sau đó kết hợp lại thành tệp tin hoàn chỉnh.

Đối với tệp tin lớn, việc sử dụng tải lên đa luồng giúp tăng tốc quá trình upload bằng cách tận dụng khả năng tải lên đồng thời từ nhiều nguồn. Điều này giúp giảm thiểu thời gian hoàn thành quá trình upload và tăng hiệu suất của ứng dụng.

image.png

Kết hợp giữa kỹ thuật phân mảnh và tải lên đa luồng cho phép tối ưu quá trình upload file lớn. Bằng cách chia nhỏ tệp tin thành các phần nhỏ (chunk) và tải lên đồng thời (multipart upload), chúng ta có thể đạt được hiệu suất cao và đảm bảo quá trình upload file lớn được thực hiện một cách nhanh chóng và ổn định.

3. Ưu và nhược điểm

Ưu điểm của kỹ thuật phân mảnh và tải lên đa luồng:

  • Tăng tốc độ upload file lớn: Tận dụng khả năng tải lên nhiều phần của file đồng thời, từ đó giảm thời gian hoàn thành quá trình upload.

  • Đảm bảo độ tin cậy: Nếu một phần trong quá trình upload bị lỗi, chỉ cần tải lên lại phần đó mà không cần upload lại toàn bộ file.

Nhược điểm, thách thức và giải pháp:

  • Xử lý mất kết nối hoặc tốc độ mạng chậm: Giải quyết các vấn đề liên quan đến mất kết nối hoặc tốc độ mạng không ổn định để đảm bảo quá trình upload được hoàn thành một cách thành công.

  • Khi phân mảnh lỗi dẫn đến không ghép được file hoàn chỉnh.

  • Logic của việc ghép file xử lý khá phức tạp và tốn nhân lực.

III. Áp dụng kỹ thuật phân mảnh và tải lên đa luồng

1. Ý tưởng

image.png

Nguồn ảnh lấy từ bài viết: https://abdelhady.net/2018/11/17/uploading-extremely-large-files-the-awesome-easy-way-tus-server/

Hình vẽ mô tả flow để thực hiện phân mảnh và upload file đa luồng. Chúng ta có 1 file lớn 100GB. Áp dụng chúng ta sẽ có các bước thực hiện như sau:

  • Bước 1: Nhận đầu vào file 100GB

  • Bước 2: Phân mảnh file thành các mảnh nhỏ. Mỗi phần 100MB chẳng hạn

  • Bước 3: Call API upload các mảnh file lên server

  • Bước 4: Back end lưu thông tin các mảnh file và thực hiện ghép các mảnh này đủ khi nhận đủ các file phân mảnh.

2. Mã nguồn mẫu front-end

Front-end

Ở FE ta sẽ dựng giao diện upload file. Ở đây mình có 1 button và 1 input kiểu file đơn giản như thế này

<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8"> <title>Upload file</title>
</head>
<body>
<button id="btn-import">Upload</button>
<input id="file-input" type="file" />
<div id="status"></div>
</body>
</html> <script src="upload.js"></script>

Hàm xử lý gọi API để upload file. Đầu vào sẽ là file phân mảnh và số thứ tự của chúng

async function uploadChunk(chunk, chunkIndex) { const formData = new FormData(); formData.append("chunk", chunk); formData.append("chunkIndex", chunkIndex); try { const response = await fetch("http://localhost:3000/upload", { method: "POST", body: formData, }); if (!response.ok) { document.getElementById("status").textContent = "failed"; throw new Error("Error uploading chunk."); } } catch (error) { document.getElementById("status").textContent = "failed"; console.error(error); }
}

Xử lý sự kiện khi click button upload. Khi click chọn file chúng ta sẽ phân từng mảnh nhỏ có kích thước được config cố định. Ở đây mình đang để 100MB. Mỗi lần phân mảnh xong sẽ gọi hàm uploadChunk để tải file lên server BE.

const importInvoices = () => { document.getElementById("file-input").click(); // Example usage const fileInput = document.getElementById("file-input"); fileInput.addEventListener("change", async (event) => { document.getElementById("status").textContent = "uploading"; const file = event.target.files[0]; const chunkSize = 100 * 1024 * 1024; // Set the desired chunk size (100MB in this example) const totalChunks = Math.ceil(file.size / chunkSize); // Iterate over the chunks and upload them sequentially for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { const start = chunkIndex * chunkSize; const end = Math.min(start + chunkSize, file.size); const chunk = file.slice(start, end); // Make an API call to upload the chunk to the backend await uploadChunk(chunk, chunkIndex); } document.getElementById("status").textContent = "successfully"; });
}; document.getElementById("btn-import").addEventListener("click", (event) => { event.preventDefault(); importInvoices();
});

3. Mã nguồn mẫu back-end

Ở API mình sẽ đọc từ form ra giá trị file phân mảnh và số thứ tự của chúng => lưu file dạng video%v với v là giá trị của số thứ tự file phân mảnh.

func uploadHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") index := r.FormValue("chunkIndex") file, _, err := r.FormFile("chunk") if err != nil { http.Error(w, "Failed to retrieve file", http.StatusBadRequest) return } defer func(file multipart.File) { errClose := file.Close() if errClose != nil { return } }(file) // Process and save the chunk to a file outputFile, err := os.Create(fmt.Sprintf("./upload-file/video%v", index)) if err != nil { http.Error(w, "Failed to create file", http.StatusInternalServerError) return } defer func(outputFile *os.File) { errClose := outputFile.Close() if errClose != nil { return } }(outputFile) _, err = io.Copy(outputFile, file) if err != nil { http.Error(w, "Failed to write file", http.StatusInternalServerError) return } w.Write([]byte("Chunk uploaded successfully"))
} func main() { http.HandleFunc("/upload", uploadHandler) // Start the server log.Fatal(http.ListenAndServe(":3000", nil))
}

Tiếp tục xử lý ghép các file lại. Chúng ta sẽ đọc file theo format như ở đã nói ở trên => được 1 list. Loop vòng lặp các file và sử dụng io.Copy để ghép các file vào file output.mp4. Ở đây mình upload file hơn 700MB nên tạm config ghép 8 file nhé.

package main import ( "fmt" "io" "os"
) func concatenateVideo(length int) bool { // Open the output file for writing outputFile, err := os.Create("./upload-file/output.mp4") if err != nil { fmt.Println("Error:", err) return false } defer func(outputFile *os.File) { errClose := outputFile.Close() if errClose != nil { return } }(outputFile) // List the video chunks in the desired order chunkPaths := make([]string, 0) for i := 0; i < length; i++ { chunkPaths = append(chunkPaths, fmt.Sprintf("upload-file/video%v", i)) } // Concatenate the video chunks for _, chunkPath := range chunkPaths { // Open the current chunk file chunkFile, errChunk := os.Open(chunkPath) if errChunk != nil { fmt.Println("Error:", errChunk) return false } defer func(chunkFile *os.File) { errClose := chunkFile.Close() if errClose != nil { return } }(chunkFile) // Copy the chunk content to the output file _, errCopy := io.Copy(outputFile, chunkFile) if errCopy != nil { fmt.Println("Error:", errCopy) return false } } return true
} func main() { isConcatenate := concatenateVideo(8) if !isConcatenate { fmt.Println("concatenate is failed") return } fmt.Println("concatenate is successfully")
}

Ở phần ghép file này do demo thôi nên mình viết sẵn hàm main rồi chạy. Còn nếu áp dụng vào thực tế, chúng ta sẽ nên có 1 queue để xử lý việc upload file và 1 worker để đi thực hiện ghép các mảnh file lại với nhau.

IV. Kết luận

Kỹ thuật phân mảnh và tải lên đa luồng là một giải pháp hiệu quả để tăng tốc quá trình upload file lớn. Với khả năng chia nhỏ file thành các phần nhỏ hơn và tải lên đồng thời, chúng ta có thể tận dụng tối đa tốc độ và hiệu suất của mạng. Bài viết này hy vọng sẽ giúp bạn hiểu rõ hơn về cơ chế hoạt động và cách áp dụng kỹ thuật này trong các dự án upload file lớn của mình.

Bài viết được trích từ blog của mình : https://tuannguyenhust.hashnode.dev/ và blog careerly: https://careerly.vn/comments/6590?utm_campaign=self-share

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giới thiệu Typescript - Sự khác nhau giữa Typescript và Javascript

Typescript là gì. TypeScript là một ngôn ngữ giúp cung cấp quy mô lớn hơn so với JavaScript.

0 0 525

- vừa được xem lúc

Bạn đã biết các tips này khi làm việc với chuỗi trong JavaScript chưa ?

Hi xin chào các bạn, tiếp tục chuỗi chủ đề về cái thằng JavaScript này, hôm nay mình sẽ giới thiệu cho các bạn một số thủ thuật hay ho khi làm việc với chuỗi trong JavaScript có thể bạn đã hoặc chưa từng dùng. Cụ thể như nào thì hãy cùng mình tìm hiểu trong bài viết này nhé (go).

0 0 433

- vừa được xem lúc

Một số phương thức với object trong Javascript

Trong Javascript có hỗ trợ các loại dữ liệu cơ bản là giống với hầu hết những ngôn ngữ lập trình khác. Bài viết này mình sẽ giới thiệu về Object và một số phương thức thường dùng với nó.

0 0 153

- vừa được xem lúc

Tìm hiểu về thư viện axios

Giới thiệu. Axios là gì? Axios là một thư viện HTTP Client dựa trên Promise.

0 0 145

- vừa được xem lúc

Imports và Exports trong JavaScript ES6

. Giới thiệu. ES6 cung cấp cho chúng ta import (nhập), export (xuất) các functions, biến từ module này sang module khác và sử dụng nó trong các file khác.

0 0 110

- vừa được xem lúc

Bài toán đọc số thành chữ (phần 2) - Hoàn chỉnh chương trình dưới 100 dòng code

Tiếp tục bài viết còn dang dở ở phần trước Phân tích bài toán đọc số thành chữ (phần 1) - Phân tích đề và những mảnh ghép đầu tiên. Bạn nào chưa đọc thì có thể xem ở link trên trước nhé.

0 0 245