Mở bất kỳ hướng dẫn RabbitMQ nào, dòng đầu tiên sau khi kết nối đều là thế này:

ch.queueDeclare("hello", false, false, false, null);

Chạy nó trên RabbitMQ 4.3.5 (bản mà phần 2 dựng lên):

541 INTERNAL_ERROR - Feature `transient_nonexcl_queues` is deprecated.
By default, this feature is not permitted anymore.
The feature will be removed from a future major RabbitMQ version...

Và không chỉ lệnh đó hỏng — cả connection bị đóng. Bài này đi qua bốn cờ khai báo, tám tổ hợp của chúng, và bốn mã lỗi mà mỗi cái có phạm vi sát thương khác nhau.

Bốn tham số

ch.queueDeclare(name, durable, exclusive, autoDelete, arguments);
Cờ Nghĩa Sống sót qua
durable Định nghĩa hàng đợi được ghi xuống đĩa Broker khởi động lại
exclusive Chỉ connection khai báo được dùng Không: chết cùng connection
autoDelete Tự xoá khi consumer cuối rời đi Không: chết khi hết consumer
arguments TTL, độ dài tối đa, kiểu hàng đợi, DLX…

Chú ý cột cuối: durable nói về định nghĩa hàng đợi, không nói gì về thông điệp bên trong. Phần sau của sê-ri sẽ đo chuyện đó.

Tám tổ hợp, hai cái không còn được phép

Thử cả tám trên broker sạch:

durable exclusive auto-delete Kết quả
true true true cho phép
true true false cho phép
true false true cho phép
true false false cho phép
false true true cho phép
false true false cho phép
false false true 541, đóng connection
false false false 541, đóng connection

Luật rút gọn: hàng đợi không bền mà lại dùng chung cho nhiều connection thì không còn được phép. Không bền thì phải là của riêng một connection.

Lý do RabbitMQ chặn: một hàng đợi transient dùng chung là thứ không ai gọi lại được sau khi broker khởi động lại, nhưng lại vẫn nhận thông điệp như thường trong lúc chạy — nguồn mất dữ liệu âm thầm. Chặn ở tầng khai báo là cách duy nhất bắt được nó.

Sửa thì đơn giản: đổi false đầu tiên thành true. Hàng đợi bền không tốn gì thêm cho tới khi có thông điệp trong đó.

Bốn mã lỗi, hai phạm vi sát thương

Đây là chỗ đáng nhớ hơn cả danh sách cờ:

Khi nào Giết cái gì
404 NOT_FOUND queueDeclarePassive hàng đợi không có channel
405 RESOURCE_LOCKED Chạm vào hàng đợi exclusive của connection khác channel
406 PRECONDITION_FAILED Khai lại với tham số khác lần trước channel
541 INTERNAL_ERROR Tổ hợp cờ bị cấm cả connection
Lỗi ở tầng channel thì channel đó đã đóng, mọi lời gọi sau trên nó ném AlreadyClosedException. Nhiều mã nguồn bắt ngoại lệ của queueDeclare, ghi log rồi đi tiếp trên cùng channel — và mọi thứ sau đó hỏng vì một lý do chẳng liên quan gì.

406 PRECONDITION_FAILED: khai lại khác tham số

khai lần 1 (durable=true)   : OK
khai lần 2 (auto-delete=true): 406 PRECONDITION_FAILED - inequivalent arg 'auto_delete'
                               for queue 'co-thu': received 'true' but current is 'false'
channel sau đó              : ĐÃ ĐÓNG

Thông báo này rất tử tế — nó nói rõ cờ nào lệch, nhận gì và đang là gì. Đây là lỗi hay gặp nhất khi hai dịch vụ cùng khai một hàng đợi mà chỉ một bên được sửa. Cách tránh: một nơi duy nhất khai báo topology, các nơi còn lại dùng queueDeclarePassive — chỉ hỏi, không tạo.

exclusive: riêng tư và chết cùng connection

chủ sở hữu tự hỏi         : rieng-tu
connection khác chạm vào  : 405 RESOURCE_LOCKED - cannot obtain exclusive access
sau khi chủ đóng kết nối  : KHÔNG CÒN   (dù khai durable=true)

Dòng cuối là chỗ dễ hiểu nhầm nhất: exclusive thắng durable. Khai bền đến mấy thì hàng đợi exclusive vẫn biến mất khi connection tạo ra nó đóng lại. Đó là ý đồ thiết kế — nó dành cho hàng đợi tạm của một client, kiểu hàng đợi trả lời trong mẫu RPC.

autoDelete: chỉ tính từ consumer đầu tiên

khai xong, CHƯA có consumer: CÒN     <- vẫn còn
đang có 1 consumer         : CÒN
consumer cuối rời đi       : KHÔNG CÒN

Hàng đợi autoDelete không tự xoá lúc vừa khai. Nó chờ có ít nhất một consumer rồi mới bắt đầu đếm; consumer cuối cùng rời đi thì nó biến mất. Nếu producer khai hàng đợi rồi gửi trước khi consumer kịp kết nối, thông điệp vẫn nằm đó chờ — và đó là hành vi đúng.

Nên khai thế nào

Mặc định: durable=true, hai cờ kia false. Đây là hàng đợi nghiệp vụ bình thường, và giờ cũng là tổ hợp duy nhất không có bẫy.

exclusive=true cho hàng đợi tạm của một client — hàng đợi trả lời, hàng đợi theo dõi. Để tên rỗng cho broker tự đặt.

autoDelete=true chỉ khi hàng đợi thật sự vô nghĩa lúc không ai nghe — kiểu phát tán sự kiện thời gian thực. Với hàng đợi công việc thì đây là cách mất việc.

Bài sau: một thông điệp gồm những gì, và vì sao deliveryMode=2 đo ra nhanh hơn deliveryMode=1.

Thử ba mươi giây

# xem cờ thật của mọi hàng đợi đang có
docker exec -u rabbitmq rmq rabbitmqctl -q list_queues \
    name durable auto_delete exclusive messages

Nếu có hàng đợi nghiệp vụ nào hiện durable=false, bạn vừa tìm ra thứ sẽ biến mất trong lần bảo trì tiếp theo.