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
Cần đọc trước
Vấn đề
Phần tiêu đề “Vấn đề”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ả |
Cả ba trên một trục thời gian
Phần tiêu đề “Cả ba trên một trục thời gian”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.
Publisher confirm
Phần tiêu đề “Publisher confirm”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ậnbasic.return(đến trướcbasic.ack), hoặc dùng alternate exchange. basic.nacktừ 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.
Consumer ack
Phần tiêu đề “Consumer ack”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.
consumer_timeout: ack quá muộn cũng là lỗi
Phần tiêu đề “consumer_timeout: ack quá muộn cũng là lỗi”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-timeouthoặc argumentx-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ớiPRECONDITION_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 = 3600000Prefetch
Phần tiêu đề “Prefetch”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.
Demo đã chạy
Phần tiêu đề “Demo đã chạy”docker compose -f examples/docker-compose.yml up -d rabbitmqnpx --prefix examples ts-node examples/src/rabbitmq-ack/ack-prefetch-demo.tsOutput 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.
Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Khi nào dùng / không dùng
Phần tiêu đề “Khi nào dùng / không dùng”- 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.
Checklist production
Phần tiêu đề “Checklist production”- Publisher dùng confirm channel; confirm xử lý bất đồng bộ, có trần số publish chưa confirm.
- Event quan trọng:
mandatoryhoặc alternate exchange, và theo dõibasic.return. - Consumer dùng manual ack, ack sau side effect bền.
- Handler idempotent theo message/event ID.
- Đặt prefetch theo workload, không để mặc định không giới hạn cho consumer chậm.
- Biết
consumer_timeout(30 phút), và job dài nhất của bạn nằm dưới ngưỡng đó. - Không dùng global QoS với quorum queue và stream.
- Dashboard riêng cho unconfirmed, ready, unacked, redelivery.
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) 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ì?