0

[Microservices Series] Bài 16: Object Storage (S3/MinIO) – Thoát khỏi cái bóng của thư mục public/uploads truyền thống

Chào anh em, mình đây! Ở bài 15, chúng ta đã giải cứu Database thành công nhờ binh đoàn Redis Cache. Dữ liệu dạng text và số đã được xử lý êm mượt. Nhưng đời không như là mơ, hệ thống của chúng ta không chỉ có chữ.

Giả sử bạn làm tính năng đăng ký vé tháng cho hành khách tuyến Metro. Khách cần upload ảnh Căn cước công dân (CCCD) và ảnh chân dung. Theo thói quen thời làm Monolith, anh em sẽ dùng Laravel xử lý file, gọi hàm move_uploaded_file, ném thẳng bức ảnh vào thư mục public/uploads/images/, rồi lưu cái đường dẫn /uploads/images/cccd.jpg vào Database. Dễ như ăn kẹo!

Nhưng nếu mang thói quen đó lên Microservices, anh em sẽ tạo ra một thảm họa tàn khốc.

1. Nỗi đau mang tên "Local File System"

Trong kiến trúc Microservices và Docker, các container được thiết kế theo nguyên tắc Stateless (Không lưu trạng thái).

  • Hiệu ứng "Lúc có lúc không": Bạn đang chạy 3 container của Service Đăng Ký (Container A, B, C). Khách hàng bấm Upload, Load Balancer điều hướng request vào Container A. File ảnh được lưu vào ổ cứng cục bộ của Container A. Lát sau, khách F5 tải lại trang để xem ảnh, Load Balancer lại điều hướng request sang Container B. Container B lục ổ cứng của nó và báo lỗi 404 Not Found vì bức ảnh đang nằm bên nhà thằng A.

  • Bốc hơi khi Restart: Đêm đó, Kubernetes quyết định kill Container A để dọn dẹp RAM và tạo ra một Container A' mới. Toàn bộ thư mục public/uploads bên trong Container A cũ tan thành mây khói. Dữ liệu của khách mất trắng!

  • Khó chia sẻ: Service Đăng Ký lưu ảnh, nhưng Service Machine Learning lại cần đọc bức ảnh đó để nhận diện khuôn mặt. Hai service chạy ở hai server khác nhau thì không thể đọc chung một thư mục ổ cứng được.

Đó là lý do thư mục public/uploads phải bị "khai tử" trong Microservices. Giải pháp thay thế chính là Object Storage.

2. Object Storage là gì? (Amazon S3 và MinIO)

Object Storage (Lưu trữ đối tượng) là một dịch vụ chuyên biệt chỉ dùng để chứa file. Thay vì lưu file theo dạng cây thư mục (như ổ C:, ổ D: trên Windows), nó lưu file dưới dạng các "Object" phẳng nằm trong các thùng chứa gọi là "Bucket".

  • AWS S3 (Amazon Simple Storage Service): Là ông trùm của mảng này. Chuẩn API của S3 đã trở thành quy chuẩn chung của thế giới.

  • MinIO: Nếu công ty anh em không muốn tốn tiền xài Cloud của Amazon, hoặc có chính sách bảo mật nội bộ khắt khe (On-Premise), anh em có thể tự cài đặt MinIO lên server công ty. MinIO là mã nguồn mở, miễn phí và có API tương thích 100% với S3. Code Laravel hay Go của anh em cứ dùng thư viện AWS S3 bình thường, chỉ cần đổi cái Endpoint URL trỏ về server MinIO là xong.

3. Thực chiến Upload file tại hệ thống AFC

Trong hệ thống vận hành Metro, mình có một yêu cầu: Nhân viên nhà ga phải upload các biên bản sự cố (file PDF) lên hệ thống, và Service Báo Cáo sẽ dùng các file này để tổng hợp cuối tháng.

Thay vì lưu cục bộ, kiến trúc sẽ chạy như sau:

  1. Frontend gọi API upload file tới Service Quản Lý Sự Cố.

  2. Service này (dùng SDK của AWS/MinIO) sẽ đẩy luồng file thẳng sang máy chủ MinIO Cluster nội bộ.

  3. MinIO nhận file, lưu trữ an toàn và trả về một đường dẫn tuyệt đối (Ví dụ: [https://storage.metro.vn/incidents/2026/report-123.pdf](https://storage.metro.vn/incidents/2026/report-123.pdf)).

  4. Service Quản Lý Sự Cố chỉ việc lưu cái URL chuỗi dài ngoằng đó vào PostgreSQL.

  5. Cuối tháng, Service Báo Cáo chỉ cần lấy URL từ Database và tải file về xử lý. Các container hoàn toàn độc lập, không bị ràng buộc bởi ổ cứng vật lý.

Lời khuyên "xương máu": Tuyệt chiêu Pre-signed URL

Cách upload truyền thống ở trên (Client -> Backend -> S3) có một điểm nghẽn chí tử: Nghẽn cổ chai tại Backend. Nếu khách hàng upload một video báo cáo sự cố nặng 500MB, cái file đó sẽ phải chui qua API Gateway, rồi chui vào RAM của Service Backend, rồi Backend mới từ từ bơm sang S3. RAM của server Backend sẽ bị vắt kiệt, network bị chiếm dụng, trong khi Backend đáng lẽ chỉ nên xử lý Logic chứ không phải đi làm "nhân viên bốc vác" file.

Giải pháp tối thượng trong Microservices: S3 Pre-signed URL

  1. Client (Mobile/Web) gọi API báo cho Backend: "Tôi muốn upload 1 video 500MB tên là loi-cua.mp4".

  2. Backend nói chuyện với S3/MinIO, xin một cái Pre-signed URL (một đường link upload đặc biệt có kèm chữ ký xác thực, chỉ có giá trị trong 5 phút). Backend trả link này về cho Client.

  3. Client cầm cái link đó, đẩy (PUT) thẳng file video trực tiếp lên S3/MinIO, bỏ qua hoàn toàn API Gateway và Backend.

  4. Upload xong, Client gọi API báo cho Backend: "Tôi up xong rồi". Backend chỉ việc lưu data vào DB.

Bằng chiêu này, anh em đã offload (trút gánh nặng) toàn bộ quá trình truyền tải file nặng nề ra khỏi cụm Microservices của mình.

Tạm kết

Việc chuyển dịch từ Local Storage sang Object Storage là một bước ngoặt bắt buộc để hệ thống của anh em đạt được cảnh giới Stateless, cho phép nhân bản (scale) các service lên hàng nghìn container một cách vô tư.

Tuy nhiên, khi file tải lên thành công rồi, chúng ta lại phải đối mặt với một bài toán khác: Xử lý file nặng. Giả sử nhân sự tải lên một file Excel chứa 100.000 dòng dữ liệu cấu hình ga, yêu cầu hệ thống đọc và import vào Database. Nếu anh em cho code chạy import trực tiếp ngay trong API lúc đó, HTTP Request sẽ bị treo suốt 10 phút, timeout, và chết ngắc.

Ở bài tiếp theo (Bài 17), chúng ta sẽ giải quyết bài toán tác vụ nặng nề này với Background Jobs, Workers & Task Queues – Nghệ thuật "nhận việc rồi để đó làm sau".

Dự án hiện tại của anh em đang xài S3 Cloud, MinIO tự host hay vẫn đang ôm bom với thư mục public? Đã bao giờ server báo đầy ổ cứng vì user up quá nhiều ảnh chưa? Cùng bình luận nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí