Lớp tách rời 'ai GỬI việc' khỏi 'ai LÀM việc' — một hàng tin nhắn nơi producer bỏ nhiệm vụ vào và consumer lấy ra khi sẵn sàng.
← / → để chuyển slide · chuyển EN/VI bên dưới
Một hàng đợi first-in-first-out gồm các tin nhắn nhỏ, đứng giữa phần app TẠO việc và phần app LÀM việc.
Các tin nhắn (thường là chuỗi JSON nhỏ) xếp hàng: vào trước, ra trước. Hàng đợi chỉ là nơi lưu — một hàng chờ.
Producer công bố (PUBLISH) tin nhắn; consumer tiêu thụ (CONSUME) sau đó. Chúng không bao giờ gọi thẳng nhau.
Người gửi không cần biết ai xử lý, lúc nào, và có bao nhiêu người xử lý.
Công việc có thể chất đống trong hàng khi consumer bắt kịp — bộ giảm xóc cho hệ thống.
Cùng khoảnh khắc 'user đặt hàng', có và không có hàng đợi ở giữa.
User bấm 'đặt hàng' → app gửi email, gọi API thanh toán, render hóa đơn — tất cả trong cùng một request. User nhìn spinner 8 giây.
Chậm — và nếu API thanh toán hiccup, cả request hỏng.
User bấm 'đặt hàng' → app bỏ MỘT tin nhắn vào hàng, trả lời trong 50 ms, xong. Worker xử lý email/thanh toán/hóa đơn chạy nền.
Trả lời nhanh — và worker chậm không chặn ai cả.
Web app, worker và API bên thứ ba nói chuyện qua hàng — không phải gọi thẳng nhau.
1.000 đơn trong một phút? Chúng xếp hàng; worker gặm dần theo tốc độ của mình.
Email, báo cáo, render video — mọi thứ không cần xong trước khi user thấy 'hoàn tất'.
Tin nhắn được lưu bền trong hàng: worker sập giữa chừng, tin vẫn còn để giao lại.
Ba lý do, đều xoay quanh chuyện không chờ và không mất.
Thời gian trả lời không còn phụ thuộc vào tác vụ chậm nhất. Tám giây việc trở thành 50 ms xếp hàng.
Worker hỏng không đồng nghĩa mất tin — thử lại, hoặc gửi vào dead-letter queue để kiểm tra sau.
Traffic tăng gấp đôi? Thêm một consumer. Hàng là mối nối cao su giữa nhu cầu và năng lực.
publish → lưu → phân phối → tiêu thụ → ack. Năm từ, một vòng lặp.
Theo một đơn hàng từ lúc bấm cho đến khi xong.
App của bạn gọi publish() kèm tin nhắn — ví dụ JSON của một đơn hàng với id 42. Đó là toàn bộ phía gửi.
Server hàng đợi (broker) nhận tin và giữ lại — trong bộ nhớ và/hoặc trên disk, tùy hệ thống.
Tin vào hàng nào? Hệ đơn giản: một hàng mỗi loại việc. Hệ lớn hơn phân phối theo tên hoặc topic.
Worker lấy tin kế tiếp và làm việc: gửi email, gọi API, render hóa đơn.
Xong việc, consumer xác nhận (ack). Hỏng giữa chừng? Tin được giao lại — nguyên tắc 'ít nhất một lần'.
Không ai gọi ai. Mọi thứ là tin nhắn đi dọc theo hàng.
Flask · FastAPI · any app
Tạo ra việc và bỏ xuống hàng: publish(order). Không chờ, không biết ai sẽ lấy.
⟶
publish()
RabbitMQ · Redis · Kafka
Giữ hàng: lưu tin, phân phối vào hàng đúng, giao lại nếu consumer chết. Bộ giảm xóc.
⟶
consume() + ack
worker-1 · worker-2 · worker-3 …
Vòng lặp mãi: lấy tin kế tiếp, làm việc, ack. Bận quá? Thêm worker — hàng không bận tâm.
Producer và worker chỉ gặp hàng đợi — không bao giờ gặp nhau.
Một tin — một consumer, hay nhiều? Đó là ngã ba.
Một tin, một consumer. Dòng việc: worker nào rảnh sẽ lấy nhiệm vụ kế. Tải được chia đều tự động.
Dùng cho NHIỆM VỤ: 'xử lý đơn này'.
Một tin, nhiều consumer. Mỗi người đăng ký nhận bản sao riêng — kiểu pub/sub.
Dùng cho SỰ KIỆN: 'một đơn đã được đặt'.
Nội dung — thường là chuỗi JSON kèm metadata (id, loại, thời gian). Nhỏ và trọn vẹn.
Server hàng đợi lưu và phân phối tin: RabbitMQ, Kafka — hoặc Redis thay thế.
Hai vai trò: ai bỏ tin vào, ai lấy ra. Cùng một app có thể làm cả hai.
Lời 'xong rồi, bỏ đi' của consumer. Chưa có ack, hàng vẫn giữ tin.
Tin hỏng được thử lại, rồi để vào dead-letter queue — bãi đỗ cho việc hỏng.
Hàng đầy khi producer nhanh hơn consumer — tín hiệu rõ ràng, chính xác để tăng năng lực.
Toàn bộ chiêu 'bất đồng bộ' phía web app: một dòng, request rảnh ngay.
import redis
r = redis.Redis(host="localhost", port=6379)
def publish(order):
# put one message onto the queue
r.lpush("orders", order)
print("queued:", order)
publish({"id": 42, "item": "keyboard", "qty": 1})
lpush = đẩy vào đầu danh sách: tin nằm ở đầu list 'orders' — một hàng đợi bằng một lệnh Redis.
Phía worker: lấy tin kế tiếp, làm việc, lặp lại. Không gì mất cả.
import redis, json
r = redis.Redis(host="localhost", port=6379)
while True:
item = r.brpop("orders", timeout=5)
if item is None:
continue # queue empty — keep waiting
order = json.loads(item[1])
print("processing:", order)
# work: send email, call API, render PDF…
# then loop — the next message is up
brpop = lấy ra có chặn: worker ngủ đến khi có tin (timeout = ngủ bao lâu).
LLEN = đang chờ bao nhiêu; LRANGE = liếc xem bên trong; LPOP = lấy một ra — đó là một lần consume.
Ba cái này đủ cho gần như mọi hệ thống nhỏ-vừa bạn sẽ ship.
Broker đa năng kinh điển: hàng, phân phối, ack, thử lại. Khi ai đó nói 'message queue', thường là họ đang nói đến cái này.
Bạn đã biết nó với vai trò cache — list của nó kiêm được hàng đơn giản (LPUSH/BRPOP). Hợp cho hệ nhỏ; không sinh ra để đảm bảo chặt chẽ.
Gã khổng lồ event log: chỉ thêm, chia partition, phát lại được. Chạy các luồng sự kiện khối lượng lớn — một loài khác (lớn hơn).
| RabbitMQ | Redis | Kafka | |
|---|---|---|---|
| Nó thực sự là gì | Broker tin nhắn (hàng + phân phối) | Cache kiêm hàng đơn giản | Nhật ký sự kiện phân tán |
| Đảm bảo | ack + thử lại, vững | cơ bản (mặc định trong bộ nhớ) | log bền, phát lại được |
| Hợp nhất cho | Việc ứng dụng chung: job, tách rời | Hệ nhỏ, async nhanh | Luồng sự kiện khối lượng lớn |
Hàng đợi đi rất sâu. Những thứ này là thật — nhưng không phải bước tiếp theo của bạn.
Phân phối tinh vi của RabbitMQ (direct, fanout, topic). Thừa thãi khi bạn chưa có nhiều hàng và rule.
Cấu trúc nội bộ Kafka: partition, offset, consumer group. Mạnh — đủ một khóa học riêng.
Biến đổi luồng tại chỗ (Kafka Streams, Flink). Đây là vùng tiên phong — không phải bước tiếp theo của bạn.
Message queue là một mối nối cao su trong hệ thống: người gửi bỏ việc xuống hàng và đi tiếp; worker lấy theo tốc độ của mình; và không gì mất khi có ai đó hiccup.
Tiếp theo: khi một máy chưa đủ, hàng đợi là thứ đầu tiên bạn nghĩ đến.