Phần 4 đo được rằng consumer chết thì thông điệp quay lại hàng đợi — tính năng cứu dữ liệu. Bài này đo mặt kia của chính tính năng đó: một thông điệp luôn lỗi sẽ quay lại mãi mãi.
Cờ redelivered không đếm gì cả
Cho một thông điệp lỗi và consumer basicNack(requeue=true), chạy 3 giây:
1 thông điệp, 3 giây -> 17 502 vòng (5 834 vòng/giây)
số lần cờ redelivered: 17 501 / 17 502
Cờ bật đúng — từ lần giao thứ hai trở đi. Nhưng nó chỉ có hai giá trị, nên sau 17 501 vòng nó vẫn nói đúng một câu: "cái này đã từng được giao trước đó". Không có cách nào từ cờ đó biết đây là lần thứ hai hay lần thứ mười bảy nghìn.
Vòng lặp ăn hết công suất
5 834 vòng mỗi giây cho một thông điệp. Đó không phải một vòng lặp chậm chạp ở đâu đó — đó là consumer của bạn chạy hết công suất để làm đi làm lại một việc luôn thất bại, và mỗi vòng là một lượt đi về qua broker.
Và nó chặn mọi việc khác
Đây mới là phần tệ. Xếp một thông điệp hỏng đứng đầu, năm thông điệp tốt xếp sau, prefetch=1:
5 thông điệp tốt: xử lý xong 0/5 trong 5 giây (BỊ CHẶN)
thông điệp độc đã quay 30 679 vòng trong lúc đó
basicNack(requeue=true) trả thông điệp về đầu hàng đợi, nên nó lập tức được giao lại trước cả những cái đang xếp sau. Một bản ghi hỏng đủ để làm chết cả đường ống, và biểu đồ giám sát chỉ hiện "hàng đợi không rút" chứ không nói vì sao.
x-delivery-limit: hai giới hạn ít ai nói
RabbitMQ có sẵn bộ đếm số lần giao, đặt bằng x-delivery-limit. Nhưng nó có hai điều kiện.
Thứ nhất, chỉ quorum queue. Classic queue từ chối thẳng:
classic -> 406 PRECONDITION_FAILED - invalid arg 'x-delivery-limit' for queue 'gh-classic'
of queue type rabbit_classic_queue
quorum -> khai báo được
Thứ hai — và đây là chỗ tôi phải đo hai lần mới tin — nó không đếm basicNack. Cùng một hàng đợi quorum với x-delivery-limit=3, hai cách làm thông điệp quay lại:
| Consumer làm gì | x-delivery-count |
Số lần giao | Kết cục |
|---|---|---|---|
basicNack(requeue=true) |
không có header | 3 171 | vẫn nằm trong hàng đợi |
| Chết (đóng connection, không ack) | 1 → 2 → 3 | 4 | vào dead letter exchange |
Với consumer chết, cơ chế chạy hoàn hảo: header x-delivery-count xuất hiện, tăng dần, chạm 3 thì thông điệp bị đẩy sang DLX. Với basicNack thì header không bao giờ xuất hiện và bộ đếm không bao giờ chạy.
Nghĩa là x-delivery-limit bảo vệ bạn khỏi consumer sập, không bảo vệ bạn khỏi mã bắt ngoại lệ rồi requeue — mà cái thứ hai mới là thứ bạn tự viết.
Vậy chặn thế nào
Đừng dùng requeue=true cho lỗi xử lý. Đây là quy tắc quan trọng nhất của bài. requeue=true chỉ dành cho "tôi tạm thời không xử lý được, ai đó khác thử đi" — ví dụ đang tắt máy. Với lỗi dữ liệu thì dùng requeue=false và một dead letter exchange: thông điệp ra khỏi đường đi ngay lần đầu, hàng đợi tiếp tục chảy, và bạn có chỗ để điều tra.
Cần thử lại thì phải có độ trễ. requeue=false cộng một hàng đợi chờ, không phải requeue thẳng — đó là bài sau.
Đếm số lần bằng x-death. Phần 14 đo được rằng broker tự tăng count trong header đó mỗi lần thông điệp đi qua DLX. Đó là bộ đếm thật, dùng được, và không phụ thuộc kiểu hàng đợi.
Dùng quorum queue kèm x-delivery-limit như lưới an toàn cuối — nó bắt được trường hợp consumer sập, thứ mà không bộ đếm nào trong mã của bạn bắt được vì mã đó đã chết cùng tiến trình.
Bài sau: dựng vòng thử lại có độ trễ bằng TTL và DLX, và cái bẫy làm thông điệp hẹn nửa giây phải chờ năm giây.
Thử ba mươi giây
curl -su guest:guest 'localhost:15672/api/queues/%2F/<ten-hang-doi>' \
| jq '{messages, deliver_get: .message_stats.deliver_get,
rate: .message_stats.deliver_get_details.rate}'
Trên hàng đợi đang có vòng lặp độc, số liệu trông thế này:
{ "messages": 1, "deliver_get": 31704, "rate": 6324 }
Một thông điệp, ba mươi mốt nghìn lượt giao. Đó là con số duy nhất phân biệt "vòng lặp thông điệp độc" với "hàng đợi đang bận" — và rabbitmqctl list_queues không có cột nào cho nó, phải hỏi HTTP API.