Bỏ qua để đến nội dung
Knowledge Hub

RabbitMQ ack, prefetch, publisher confirms: ba ranh giới khác nhau Trung cấp

Thời gian đọc ước tính: 7 phút

Producer gửi hai job tạo hóa đơn. Log ghi “sent”, nhưng sau restart không thấy hóa đơn nào. Ở phía consumer, log có “received job-1” nhưng chưa ghi database; connection đóng và job xuất hiện lần nữa.

Hai hiện tượng này không mâu thuẫn. RabbitMQ có ba ranh giới độc lập, và lỗi thường đến từ việc gộp chúng làm một chữ “ack”.

Ranh giới Ai nói với ai Xác nhận Không xác nhận
Publisher confirm Broker → producer Broker đã nhận trách nhiệm với message (quorum queue: sau khi replicate tới đa số) Message đã route đúng, consumer đã xử lý
Consumer ack Consumer → broker Delivery xong, broker không cần giao lại DB không rollback nếu ack quá sớm
Prefetch Consumer cấu hình với broker Giới hạn số delivery chưa ack Không xác nhận gì cả
Confirm đóng chặng publisher, ack đóng chặng consumer, prefetch điều tiết giữa chúng
Confirm đóng chặng publisher, ack đóng chặng consumer, prefetch điều tiết giữa chúngBước 1/7: Publisher gửi job-1 trên confirm channel. Chưa có gì được bảo đảm.thời gianpublish job-1Publisherconfirm channelQuorum queuebrokerConsumerprefetch=1, manual ack
Bước 1/7

Publisher gửi job-1 trên confirm channel. Chưa có gì được bảo đảm.

Điểm cần nhớ từ sơ đồ: giữa “nhận confirm” và “basic.ack” có cả khoảng thời gian consumer ghi database. Hai mốc này có thể cách nhau vài giây hoặc vài giờ, và không có gì ràng buộc chúng với nhau.

Confirm chạy trên confirm channel. Với message persistent vào durable queue, broker chỉ confirm sau khi đã ghi đĩa; với quorum queue, confirm chỉ được gửi sau khi message được replicate tới đa số replica.

Ba điều hay bị bỏ sót:

  • Unroutable vẫn được confirm. Nếu không queue nào match, broker confirm sau khi exchange quyết định không route. Muốn biết, publish với mandatory để nhận basic.return (đến trước basic.ack), hoặc dùng alternate exchange.
  • basic.nack từ broker hiếm (lỗi nội bộ của process queue). Khi gặp, publisher nên republish.
  • Đừng chờ confirm sau từng message. Docs khuyến nghị xử lý confirm bất đồng bộ hoặc publish theo batch, kèm giới hạn số publish đang chờ confirm.

noAck: true là auto ack: broker coi message đã xong ngay khi gửi đi. Chỉ hợp với dữ liệu chấp nhận mất. Với job quan trọng, dùng manual ack và đặt ack sau side effect bền.

Thời điểm crash Kết quả
Trước DB commit Job được giao lại, không mất việc
Sau DB commit, trước ack Job được giao lại (redelivered = true), cần dedupe
Ack trước DB commit Mất việc: broker không còn lý do giao lại

Hai lỗi giao thức cần tránh: ack hai lần cùng delivery tag, hoặc ack trên channel khác channel nhận delivery. Cả hai đóng channel với PRECONDITION_FAILED - unknown delivery tag.

nack hoặc reject với requeue=true đưa message về hàng chờ ngay, nên lỗi vĩnh viễn sẽ lặp nóng. Hướng xử lý nằm ở dead-letter và retry có đếm. Cách chặn xử lý trùng nằm ở consumer idempotent. Cờ redelivered chỉ là gợi ý (xem bài đó), không thay cho message ID.

RabbitMQ đặt thời hạn cho một delivery chưa ack. Mặc định 30 phút. Quá hạn, channel bị đóng với PRECONDITION_FAILED, và mọi delivery chưa ack trên channel đó (của tất cả consumer) được requeue.

  • Kiểm tra khoảng mỗi phút; giá trị dưới 1 phút không được hỗ trợ, dưới 5 phút không khuyến nghị.
  • Có thể đặt theo queue (policy consumer-timeout hoặc argument x-consumer-timeout, từ 3.12).
  • Từ 4.3, timeout này chỉ áp dụng cho quorum queue, và docs quorum queues mô tả hành vi khác: message của consumer quá hạn được trả về queue; consumer AMQP 0-9-1 bị cancel nếu client hỗ trợ consumer_cancel_notify, nếu không thì channel mới bị đóng với PRECONDITION_FAILED. Mô tả “đóng channel” ở trên là hành vi trước 4.3 (trang Consumers vẫn ghi vậy); kiểm tra với đúng phiên bản bạn chạy.
  • Job dài hơn timeout cần đổi thiết kế (chia nhỏ, ack sớm kèm trạng thái bền), không chỉ nới số.
# rabbitmq.conf: đơn vị mili giây (ví dụ 1 giờ)
consumer_timeout = 3600000

Prefetch là số delivery chưa ack tối đa mà broker đẩy trước cho mỗi consumer (basic.qos áp dụng riêng cho từng consumer mới trên channel khi global=false). prefetch=0 nghĩa là không giới hạn.

Quorum queue và stream không hỗ trợ global QoS: consume trên channel đã đặt global sẽ gặp channel error. Dùng prefetch theo consumer.

Tình huống Prefetch gợi ý Lý do
Job dài, nặng, cần chia đều worker 1 Worker rảnh mới nhận job tiếp, không bị “giữ” việc
Job ngắn, latency xử lý thấp, cần throughput Tăng dần (chục tới vài trăm) rồi đo Tránh broker chờ ack mỗi message
Consumer xử lý song song N job Khoảng N trở lên Không để worker rảnh chờ delivery

Cột “gợi ý” là kinh nghiệm vận hành, không phải con số trong tài liệu chính thức. Docs chỉ nói: prefetch giới hạn lượng in-flight, và nếu consumer capacity dưới 100% thì tăng prefetch có thể giúp queue giao nhanh hơn. Hãy đo consumer capacity, memory và độ công bằng trước khi chốt.

Terminal window
docker compose -f examples/docker-compose.yml up -d rabbitmq
npx --prefix examples ts-node examples/src/rabbitmq-ack/ack-prefetch-demo.ts

Output trên broker local (queue tạm, prefetch(1), consumer đợi 200 ms trước khi ack): publisher: broker confirmed two messages, consumer received: job-1, consumer ack: job-1, consumer received: job-2. Nó chứng minh job-2 chỉ tới sau khi job-1 được ack. Queue tạm là exclusive, autoDelete, không durable, nên demo không kiểm chứng persistence hay redelivery qua restart.

Thử bỏ consumer.ack(message) của job đầu: job-2 không tới consumer này vì window còn đầy.

  • Manual ack + confirm: job không được mất (hóa đơn, thanh toán, email giao dịch).
  • Auto ack: telemetry, cache invalidation, dữ liệu mất cũng không sao.
  • Prefetch = 1: job nặng, thời gian xử lý chênh lệch lớn.
  • Prefetch lớn: job nhẹ, đồng đều, ưu tiên throughput.
  1. Publisher dùng confirm channel; confirm xử lý bất đồng bộ, có trần số publish chưa confirm.
  2. Event quan trọng: mandatory hoặc alternate exchange, và theo dõi basic.return.
  3. Consumer dùng manual ack, ack sau side effect bền.
  4. Handler idempotent theo message/event ID.
  5. Đặt prefetch theo workload, không để mặc định không giới hạn cho consumer chậm.
  6. Biết consumer_timeout (30 phút), và job dài nhất của bạn nằm dưới ngưỡng đó.
  7. Không dùng global QoS với quorum queue và stream.
  8. Dashboard riêng cho unconfirmed, ready, unacked, redelivery.

Tự kiểm tra: (1) Confirm đã về, hóa đơn đã được ghi chưa? (2) Vì sao prefetch=1 chặn job-2 tới cùng consumer? (3) Job chạy 45 phút trên queue mặc định sẽ gặp gì?