Để scale hệ thống, các dự án thường sẽ tách rời các thành phần khác nhau bên trong nó.
Khi ấy việc scale từng thành phần có thể diễn ra một cách độc lập.
Và message queue là một chiến lược quan trọng để giải quyết vấn đề này, được sử dụng bởi nhiều hệ thống phân tán trong các dự án thực tế.
Message queue cũng là một trong những ví dụ phổ biến về ứng dụng của Queue (Hàng đợi). Nhưng nhiều bạn đi phỏng vấn lại rất hay bị tạch ở đây, vì chỉ biết lý thuyết mà không hiểu Queue trong thực tế có thể ứng dụng vào việc gì.
Cùng mình tìm hiểu trong bài viết này để hiểu hơn về nó nhé!
🔑 ĐỊNH NGHĨA
Message queue là một thành phần được lưu trữ trong memory, hỗ trợ giao tiếp bất đồng bộ.
Nó hoạt động như một buffer và phân phối các request bất đồng bộ, dựa trên nguyên lý của queue (Hàng đợi), tức là Vào Trước Ra Trước (First In First Out - FIFO)
⛩️ KIẾN TRÚC CƠ BẢN
Kiến trúc cơ bản của message queue rất đơn giản.
Nhìn vào hình minh họa ở trên, các bạn sẽ thấy:
-
Đầu vào của message queue là các Producer (một số tài liệu khác có thể sẽ gọi là Publisher). Tác dụng của nó là tạo message và publish chúng vào message queue.
-
Các message khi được đẩy vào queue lúc này sẽ được sắp xếp theo thứ tự, giống như cách các bạn xếp hàng mua vé xem phim vậy đó.
-
Sau đó, các service hoặc server được gọi là Consumer (một số tài liệu khác có thể sẽ gọi là Subscriber) sẽ subscribe message queue để nhận được các message, và thực hiện các hành động tương ứng với các message đó.
Nhìn thấy từ "subscribe" các bạn có thể quen không nhỉ? Ông nào hay xem Youtube thì chắc hẳn sẽ rất quen thuộc với lời kêu gọi "Nếu bạn thấy hay, thì hãy tặng mình 1 like và 1 subscribe nhé!" của tác giả kênh Youtube đó đúng không nào?
Và mô hình hoạt động của message queue này cũng kiểu như vậy.
Consumer sẽ nhấn nút Subscribe "kênh Youtube" tên là Message queue. Để khi nào tác giả của kênh Youtube đó là Publisher có đăng video mới (message), thì ông người xem Consumer này sẽ nhận được thông báo và bật video để xem (xử lý message)
Mô hình này còn có một tên gọi khác là mô hình Pub/Sub. Viết tắt của Publish/Subscribe.
✅ TÁC DỤNG
Nhờ việc có thể tách rời 2 thành phần là Producer và Consumer, message queue trở thành một kiến trúc rất được ưa chuộng để xây dựng ứng dụng có thể dễ dàng mở rộng (scalable) và đáng tin cậy (reliable).
Với message queue, Producer hoàn toàn có thể publish các message lên queue. Dù cho Consumer chưa sẵn sàng để xử lý, thì message cũng vẫn được lưu trữ ở đó theo thứ tự mà Producer gửi lên.
Và ngược lại, Consumer vẫn có thể đọc message từ queue và xử lý, ngay cả khi Producer đang gặp vấn đề.
Đó chính là sự độc lập mà mình nói ở trên.
❌ NGUY HIỂM!!!
Tuy nhiên, không phải vì thế mà chúng ta có thể chủ quan, khi gặp lỗi ở Producer hoặc Consumer thì cứ mặc kệ nó được.
-
Vì nếu để Producer bị lỗi quá lâu, ứng dụng sẽ không hoạt động theo logic dự kiến nữa, do các message không được tạo ra để đẩy vào queue.
-
Hoặc nếu logic ở phía trước của Producer không được xử lý tốt, thì cũng có thể gây ra tình trạng Producer đẩy quá nhiều message vào queue khiến cho các Consumer không xử lý kịp, dẫn đến hiện tượng tràn queue.
-
Còn nếu để Consumer bị lỗi quá lâu, cũng sẽ có thể dẫn đến hiện tượng tràn queue, do có quá nhiều message được đẩy vào queue mà không được xử lý.
🔰 VÍ DỤ THỰC TẾ
Dự án mình từng triển khai thi công chức năng gửi email marketing.
Ở phiên bản đầu tiên của chức năng này, bọn mình chỉ đơn giản triển khai với API thông thường. Khi người dùng nhấn nút Gửi email, thì sẽ gọi API gửi mail để xử lý và gửi email đó đến địa chỉ người nhận.
Nhưng vấn đề thực sự phát sinh khi team marketing ở công ty mình thực hiện gửi email hàng loạt tới hàng trăm nghìn người trong danh sách khách hàng của công ty, để quảng bá chiến dịch mới trong cùng một thời điểm, service gửi mail của dự án mình ngất lên ngất xuống ngay lập tức.
Khi đó, bọn mình đã sử dụng Kafka để triển khai message queue khắc phục tình trạng này.
Khi người dùng gửi email, email đó sẽ được đẩy vào queue.
Nhờ vậy mà bọn mình có thể cấu hình để kiểm soát được việc cho phép xử lý bao nhiêu email để gửi đi trong một thời điểm, bằng cách giới hạn số message lấy ra từ queue.
Và khi cần thiết, các producer và các consumer cũng có thể được scale một cách độc lập.
Khi số message trong queue nhiều lên, bọn mình có thể bổ sung các consumer để giảm thời gian xử lý.
Ngược lại, khi số message trong queue trống trong hầu hết thời gian, số lượng các consumer có thể giảm xuống để đỡ lãng phí tài nguyên.
LỜI NHẮN
Bạn có thể tham khảo thêm những bài viết trong series "System Design - Thiết kế Hệ thống" của mình trên blog này nhé. Hi vọng kiến thức này hữu ích với bạn.
Follow mình trên Facebook "CLB Lập trình - THPT Ngọc Tảo" hoặc kênh Youtube "Tờ Mờ Sáng học Lập trình" để cùng nhau học tập, chia sẻ những kiến thức công nghệ và lập trình hoàn toàn miễn phí nhé!
Facebook CLB Lập trình - THPT Ngọc Tảo: https://www.facebook.com/clb.it.ngoctao/
Youtube Tờ Mờ Sáng học Lập trình: https://www.youtube.com/@tmsangdev
Hẹn gặp lại 👋
BẠN CÓ THỂ ĐỌC THÊM
Clean Architecture: A Craftsman’s Guide to Software Structure and Design - Robert C. Martin
Designing Data – Insensitive applications - Martin Kleppmann
System Analysis and Design - Alan Dennis, Barbara Haley Wixom, Roberta M. Roth
System Design Interview - Alex Xu
Modern Systems Analysis and Design - Joseph Valacich, Joey George
Head First Design Patterns - Eric Freeman, Elisabeth Robson