Engineering Notes

Message Queue vs Pub/Sub: Hai Pattern, Hai Nhiệm Vụ Khác Nhau

By Ginbok9 min read

Message queue và pub/sub thường được xem là hai tên gọi cho cùng một ý tưởng: "gửi message giữa các service theo kiểu bất đồng bộ." Trên thực tế, chúng giải quyết hai nhóm bài toán hoàn toàn khác nhau. Chọn nhầm pattern cho một use case cụ thể sẽ sinh ra những lỗi khó tái hiện và tốn kém để xử lý trong môi trường production.

Bài viết này phân định rõ ranh giới giữa hai pattern, giải thích cơ chế cốt lõi tạo ra sự khác biệt, và chỉ ra bốn nhóm bài toán mà chỉ message queue mới cung cấp được đúng đắn.


Sự khác biệt cốt lõi

Cả hai pattern đều tách biệt người gửi khỏi người nhận. Sự khác nhau nằm ở điều xảy ra với message sau khi nó được gửi đi.

Trong message queue, một message được đặt vào một cấu trúc có thứ tự và nằm đó cho đến khi đúng một consumer lấy ra và xác nhận (acknowledge). Queue theo dõi quyền sở hữu: trong khi một consumer đang giữ message, không consumer nào khác có thể truy cập nó. Khi consumer xác nhận xử lý thành công, message bị xóa. Nếu consumer thất bại mà không acknowledge, message trở nên khả dụng trở lại.

Trong pub/sub, một message được publish lên một topic và broadcast đến tất cả subscriber đang lắng nghe. Mỗi subscriber nhận một bản sao độc lập. Topic không theo dõi ai đang xử lý cái gì — nó chỉ đơn giản là phân phối rồi tiếp tục. Nếu có hai subscriber, cả hai đều nhận được message. Nếu có mười subscriber, cả mười đều nhận.

Hình dung đơn giản: queue là hệ thống bưu điện — một bưu kiện, một người nhận, giao hàng được xác nhận. Pub/sub là phát sóng radio — một tín hiệu, tất cả ai đang bật đài đều nhận được đồng thời.


Mỗi pattern đảm bảo điều gì

Message queue đảm bảo exactly-once delivery trong một consumer group, thứ tự FIFO trong queue, backpressure tự nhiên thông qua việc buffer các message chưa được xử lý, và visibility isolation — message đang được xử lý bởi một consumer sẽ không hiển thị với các consumer khác.

Pub/sub đảm bảo fan-out — mỗi subscriber đều nhận mọi message — và tách biệt producer khỏi số lượng consumer. Publisher không cần biết có bao nhiêu subscriber hay chúng làm gì với message. Nó publish; việc phân phối là trách nhiệm của broker.

Không có đảm bảo nào vượt trội hơn đảm bảo kia. Chúng phục vụ những nhu cầu khác nhau. Vấn đề phát sinh khi developer dùng pub/sub trong tình huống yêu cầu ngữ nghĩa của queue.


Bốn bài toán chỉ queue giải quyết được đúng

1. Exactly-once job processing

Hãy xem xét một pipeline xử lý thanh toán. Một đơn hàng đến, kích hoạt tính tiền, và tạo hóa đơn. Chuỗi này phải thực thi đúng một lần cho mỗi đơn hàng. Với queue, một worker duy nhất claim message; tất cả worker khác trong pool bị chặn không thể claim cùng message đó bởi cơ chế visibility của broker. Việc xử lý là atomic từ góc độ của broker.

Với pub/sub, mọi subscriber đều nhận event. Nếu có hai billing service đang subscribe — chẳng hạn để đảm bảo redundancy — cả hai đều thực thi tính tiền. Khách hàng bị charge hai lần. Nguyên nhân gốc rễ không phải là lỗi trong billing service; đó là sự không tương thích về kiến trúc. Pub/sub được thiết kế để deliver đến tất cả subscriber, và nó làm điều đó đúng đắn. Nó đơn giản là không thể cung cấp tính exclusive mà payment processing đòi hỏi.

2. Load balancing trên worker pool

Queue phân phối công việc một cách tự nhiên. Nhiều consumer đọc từ cùng một queue, mỗi consumer claim từng message riêng lẻ theo capacity cho phép. Nếu một trăm task đến và ba worker đang chạy, khối lượng công việc được phân phối đều trong pool — không task nào bị xử lý nhiều hơn một lần tính tổng.

Fan-out của pub/sub là nghịch đảo của load balancing. Ba subscriber trên một topic nhận một trăm message nghĩa là ba trăm lần thực thi tổng cộng. Công việc được nhân rộng, không được phân phối. Các hệ thống pub/sub có thể mô phỏng hành vi queue thông qua abstraction consumer group — consumer group của Kafka là ví dụ điển hình — nhưng điều này đòi hỏi cấu hình tường minh và đưa vào độ phức tạp riêng. Khi yêu cầu là phân phối công việc đơn giản, queue diễn đạt ý định đó trực tiếp hơn.

3. Retry logic và dead letter queue

Message queue hỗ trợ xử lý thất bại có cấu trúc. Khi một worker không xử lý được message — do lỗi network tạm thời, service phụ thuộc không khả dụng, hoặc exception không mong đợi — nó có thể negative acknowledge message đó. Broker trả message về queue để xử lý lại. Sau một số lần thất bại có thể cấu hình, message được chuyển vào dead letter queue để kiểm tra và can thiệp thủ công.

Cơ chế retry này phụ thuộc vào message ownership. Broker phải biết consumer nào đang giữ message nào, và trong bao lâu, để phát hiện sự bỏ cuộc và phân công lại. Pub/sub broker không duy trì mô hình ownership per-message này. Dù một số hệ thống pub/sub có cung cấp acknowledgment và retry ở cấp subscriber, chúng không thể ngăn duplicate delivery trên nhiều subscriber — điều này đưa chúng ta trở lại bài toán exactly-once đã đề cập ở trên.

4. Rate limiting và backpressure

Queue là pull-based. Consumer lấy message theo nhịp độ của chính nó, bị giới hạn bởi capacity xử lý. Nếu producer gửi một nghìn message mỗi giây nhưng worker chỉ có thể xử lý mười, queue hấp thụ phần chênh lệch. Worker không bao giờ bị quá tải; nó đơn giản là xử lý backlog theo nhịp tự nhiên.

Pub/sub thường là push-based. Broker deliver message đến subscriber khi chúng đến. Subscriber không đủ capacity nhận message nhanh hơn khả năng xử lý, dẫn đến áp lực bộ nhớ, message bị drop, hoặc cascading failure. Hành vi buffering của queue không phải là ngẫu nhiên — đó chính là cơ chế làm cho hai pattern hoạt động khác nhau dưới tải.


Khi nào pub/sub là lựa chọn đúng

Pub/sub là pattern phù hợp khi một event cần được nhiều hệ thống độc lập biết đến, và việc tất cả chúng cùng hành động đồng thời là chấp nhận được — hoặc mong muốn.

Một event đăng ký người dùng có thể cần kích hoạt xác nhận email, tạo entry CRM, ghi analytics event, và khởi động onboarding sequence. Đây là những concern độc lập. Không service nào nên sở hữu event này. Email system không cần biết về CRM; analytics pipeline không cần phối hợp với onboarding. Pub/sub diễn đạt fan-out này một cách rõ ràng, và việc thêm subscriber mới về sau không đòi hỏi thay đổi publisher hay các subscriber hiện có.

Phép kiểm tra đơn giản: nếu xử lý trùng lặp là vấn đề về tính đúng đắn — tính tiền hai lần, gửi hai email xác nhận, đặt chỗ cùng một slot inventory hai lần — thì cần dùng queue. Nếu cùng một event hợp lý cần được nhiều hệ thống độc lập xử lý, pub/sub là công cụ phù hợp.


Kết hợp cả hai pattern

Các hệ thống production thường sử dụng pub/sub và message queue kết hợp với nhau. Một pattern phổ biến là fan-out queue: một pub/sub topic broadcast event đến nhiều queue, mỗi queue thuộc sở hữu của một service riêng biệt. Mỗi queue sau đó serialize delivery trong service của nó, cung cấp ngữ nghĩa exactly-once cho việc xử lý của service đó trong khi vẫn giữ hành vi fan-out ở tầng phân phối.

AWS SNS kết hợp với SQS là hiện thực điển hình của pattern này. Google Pub/Sub với các subscription per-subscriber cung cấp đảm bảo tương tự. Hai pattern không phải là những lựa chọn cạnh tranh — chúng hoạt động ở các tầng khác nhau của một kiến trúc messaging.


Tham chiếu công cụ thực tế

Với các team đang chọn infrastructure: RabbitMQAmazon SQS là message queue được thiết kế chuyên dụng với đảm bảo exactly-once và ordering mạnh. BullMQ (Node.js, Redis-backed) và asynq (Go, Redis-backed) phù hợp cho job queue ở tầng ứng dụng. Apache KafkaNATS chủ yếu là hệ thống pub/sub, dù consumer group model của Kafka hỗ trợ hành vi giống queue trong một group. Google Cloud Pub/SubAWS SNS là managed pub/sub service phù hợp kết hợp với downstream queue cho pattern fan-out-then-serialize.

Firebase không cung cấp message queue native hay hệ thống pub/sub đầy đủ tính năng. Cloud Pub/Sub có thể dùng như một Google Cloud service riêng biệt và đòi hỏi tích hợp tường minh.


Sự phân biệt giữa hai pattern này không phải là lý thuyết suông. Đó là sự khác biệt giữa một lần tính tiền và hai lần tính tiền, giữa một task được xử lý bởi một worker và được xử lý bởi tất cả. Hiểu rõ mỗi pattern đảm bảo điều gì — và điều gì nó không đảm bảo — là điều kiện tiên quyết để thiết kế hệ thống phân tán hoạt động đúng dưới tải và khi có sự cố.

#message-queue#pub-sub#system-design#distributed-systems#backend
← Back to Articles
So sánh rõ ràng giữa message queue và pub/sub: mỗi pattern đảm bảo điều gì, chúng khác nhau ở đâu, và bốn nhóm bài toán mà chỉ queue mới giải quyết được đúng. - Ginbok