RabbitMQ exchange và binding: message đến queue nào? Cơ bản
Thời gian đọc ước tính: 8 phút
Vấn đề
Phần tiêu đề “Vấn đề”Order service phát order.paid. Billing phải nhận để ghi hóa đơn; Audit cũng phải nhận để lưu dấu vết. Hai cách ngây thơ đều hỏng:
- Hai consumer cùng nghe một queue: mỗi message thường được một consumer xử lý, không phải cả hai.
- Producer tự gửi hai lần vào hai queue: nó phải biết tên queue của từng service và sửa code mỗi khi có subscriber mới.
RabbitMQ giải bài toán định tuyến bằng exchange và binding: producer gửi vào exchange, broker quyết định queue nào nhận theo quy tắc đã khai báo.
Nhưng exchange không phải kho lưu message. Nếu không queue nào match, một publish có thể không tới queue nào. “Publish không ném lỗi” không đồng nghĩa “đã có consumer nhận”, thậm chí chưa chắc có queue giữ message.
Bài đi từ order.paid vào hai queue và so sánh bốn exchange type thường gặp trong AMQP 0-9-1.
Khái niệm cốt lõi
Phần tiêu đề “Khái niệm cốt lõi”- Producer publish một message vào exchange kèm routing key (một chuỗi như
order.paid). - Binding là quan hệ giữa exchange và queue kèm quy tắc match.
- Queue giữ message để consumer nhận; queue type và durability quyết định các đặc tính lưu trữ khác.
Một exchange có thể định tuyến cùng một message tới nhiều queue; khi đó mỗi queue có bản giao riêng cho consumer của nó. Nhiều consumer trên cùng một queue lại chia việc, không tự tạo fan-out cho từng consumer.
Đang tải diagram…
Với direct exchange, binding key phải bằng routing key chính xác. Ví dụ binding order.paid nhận order.paid, không nhận order.created. Nhiều queue cùng binding key vẫn đều có thể nhận; direct không có nghĩa “chỉ một queue”.
Với topic exchange, routing key gồm các segment tách bằng dấu chấm. * match đúng một segment; # match không hoặc nhiều segment. Binding order.* match order.paid nhưng không match order.payment.failed; order.# match cả hai.
Với fanout exchange, routing key bị bỏ qua: tất cả queue bound vào exchange nhận một bản. Hợp cho broadcast đơn giản, nhưng không lọc theo loại event.
Với headers exchange, điều kiện route dựa trên header của message và binding arguments như x-match=all hoặc any, thay vì chỉ routing key. Nó có thể hữu ích khi điều kiện không biểu diễn gọn bằng một key, nhưng config và quan sát phức tạp hơn; với event type thông thường, topic/direct thường dễ hiểu hơn.
Default exchange là direct exchange đặc biệt có tên rỗng ""; mỗi queue được tự động bind bằng tên queue. Vì thế API sendToQueue('billing', ...) thực chất publish qua default exchange. Nó tiện cho work queue đơn giản, nhưng nếu producer cần decouple khỏi tên queue của consumer thì tạo exchange riêng và binding rõ hơn.
| Exchange | Điều kiện route | order.paid tới queue nào? |
|---|---|---|
| Direct | Key bằng chính xác binding key | Queue bind order.paid |
| Topic | Pattern segment với *, # |
Queue bind order.paid, order.*, order.# |
| Fanout | Không xét key | Mọi queue đang bound |
| Headers | Match header theo arguments | Queue có binding header phù hợp |
Topic exchange: bảng khớp key và pattern
Phần tiêu đề “Topic exchange: bảng khớp key và pattern”Routing key là các từ tách bằng dấu chấm (tối đa 255 byte). * thay đúng một từ; # thay không hoặc nhiều từ. Queue khớp qua nhiều binding vẫn nhận message một lần.
| Routing key | order.paid |
order.* |
order.# |
*.paid |
#.failed |
|---|---|---|---|---|---|
order.paid |
khớp | khớp | khớp | khớp | không |
order.created |
không | khớp | khớp | không | không |
order.payment.failed |
không | không | khớp | không | khớp |
order |
không | không | khớp | không | không |
payment.paid |
không | không | không | khớp | không |
Hai trường hợp đặc biệt: binding # một mình nhận mọi message (như fanout), và binding không có wildcard hoạt động như direct. Message không khớp binding nào bị bỏ.
Mặc định nên chọn topic exchange cho event nghiệp vụ: nó bao trùm direct và fanout, và thêm subscriber không cần sửa producer. Dùng fanout chỉ khi thật sự broadcast, headers khi điều kiện không biểu diễn được bằng key.
Cách hoạt động
Phần tiêu đề “Cách hoạt động”Trước khi phát, ứng dụng khai báo topology: exchange orders, queue billing, queue audit, binding billing với order.paid, binding audit với order.#.
- Quản lý topology bằng code/config/deployment có version, không để một môi trường chỉ được cấu hình thủ công trên UI rồi quên ghi lại.
- Cấp quyền
configure,write,readphù hợp cho service; “producer publish được” không có nghĩa nó được phép tự tạo mọi queue.
Producer gửi order.paid. Topic exchange so routing key với từng binding: order.paid match chính xác binding của billing; order.# của audit cũng match. Broker đặt message vào cả hai queue.
Billing consumer ack delivery của billing queue không làm message trong audit queue biến mất. Hai queue có backlog, retry, consumer count và chính sách dead-letter riêng.
| Routing key publish | billing (order.paid) |
audit (order.#) |
|---|---|---|
order.paid |
nhận | nhận |
order.created |
không | nhận |
payment.paid |
không | không (trừ khi topology còn queue khác) |
Mặc định, unroutable message có thể bị drop. Ba cơ chế liên quan, không thay thế nhau:
mandatoryflag (AMQP): broker trảbasic.returntới publisher khi không route được.- Publisher confirm: xác nhận broker đã nhận trách nhiệm publish ở phạm vi của nó, không tự chứng minh có queue match.
- Alternate exchange: xử lý unroutable message theo policy.
Với event quan trọng, cần test trường hợp không binding, theo dõi returns và audit topology.
Exchange/queue durable và message persistent là các cấu hình khác nhau. Một message persistent vào transient queue không tự trở nên bền qua restart. Ngược lại, durable queue không có nghĩa mọi message gửi vào đều persistent.
Với quorum queue, cơ chế replication và confirm còn có quy tắc riêng. Demo chỉ dùng queue tạm để không để lại dữ liệu local; không phải mẫu cấu hình production.
Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Chạy demo với RabbitMQ local:
docker compose -f examples/docker-compose.yml up -d rabbitmqnpx --prefix examples ts-node examples/src/rabbitmq-routing/routing-demo.tsDemo tạo topic exchange và hai queue tạm exclusive, bind như sơ đồ, publish event-42 với key order.paid. Kết quả trên rabbitmq:4-management-alpine: billing: event-42 và audit: event-42.
Queue tạm biến mất khi connection đóng, nên demo không nói gì về durability. Demo dùng exclusive: true vì transient non-exclusive queue đã deprecated và từ RabbitMQ 4.3.0 mặc định không declare được (deprecated_features.permit.transient_nonexcl_queues).
Thử đổi binding billing sang order.created: billing in missing, audit vẫn nhận. Đổi key publish sang payment.paid: cả hai in missing. Demo dùng basic.get cho ngắn gọn; ứng dụng thật dùng consumer subscription, manual ack và prefetch.
Checklist
Phần tiêu đề “Checklist”- Mỗi service consume có queue riêng bind vào exchange; không cho hai service nghe chung một queue.
- Event nghiệp vụ mặc định dùng topic exchange; fanout chỉ khi thật sự broadcast, headers khi điều kiện không biểu diễn được bằng key.
- Topology (exchange, queue, binding) quản lý bằng code/config có version; quyền
configure,write,readcấp đúng cho từng service. - Event quan trọng publish với
mandatoryhoặc có alternate exchange, và theo dõi số message bị return/unroutable. - Test routing key tại biên (
*đúng một segment,#không hoặc nhiều) và trường hợp không binding nào match. - Cấu hình durability ở cả hai phía: queue
durablevà messagepersistent. - Đổi tên routing key theo giai đoạn tương thích; không đổi key một phía khi binding chưa được cập nhật.
Trade-offs
Phần tiêu đề “Trade-offs”Routing qua exchange
- Producer không phải biết tên queue của từng consumer.
- Một event có thể đến nhiều queue, mỗi service giữ tiến độ riêng.
- Direct/topic/fanout/headers cho các mức lọc khác nhau.
Chi phí thiết kế
- Topology và routing key là contract cần version/test.
- Unroutable message có thể bị drop nếu không dùng mandatory/alternate exchange và monitoring.
- Nhiều binding rộng (
#) có thể tăng fan-out và chi phí lưu/consume ngoài dự tính.
Sai lầm thường gặp
Phần tiêu đề “Sai lầm thường gặp”Tóm tắt
Phần tiêu đề “Tóm tắt”Tự kiểm tra: (1) Hai consumer cùng nghe một queue có đều thấy order.paid không? (2) order.* có match order.payment.failed không? (3) Publisher confirm có chứng minh queue đã match không?