Có ba cách biết một sân vận động đang chứa bao nhiêu người. Bạn có thể đứng ngay cửa soát vé mà đếm từng người bước qua — đó là con số đúng lúc này. Bạn có thể liếc lên bảng điện tử treo giữa sân, cái vài giây mới cập nhật một lần — con số ấy luôn là quá khứ gần. Hoặc bạn chỉ kiểm xem cổng chính đã mở khoá chưa — điều đó cho biết cổng sẵn sàng, chứ không cho biết có ai đã vào. Nhìn vào một broker RabbitMQ y hệt vậy, và chọn nhầm cách nhìn là chỗ người ta hay vấp.

Phần trước dùng rabbitmqctl để đọc trạng thái thông điệp. Bài này nói hẳn về ba cách nhìn vào broker, và về hai con số mà cả ba cách đều dễ làm bạn hiểu sai.

Ba đường vào

Đường Dùng khi Điểm yếu
Giao diện :15672 Nhìn nhanh bằng mắt, nghịch thử Số liệu là ảnh chụp có độ trễ
rabbitmqctl Sự thật ngay lúc này, script trên chính máy chủ Phải vào được container
HTTP API :15672/api Giám sát, tự động hoá từ xa Cùng độ trễ với giao diện

Giao diện quản trị chính là HTTP API — nó chỉ là một trang gọi vào đúng những endpoint đó. Nên mọi điều nói về độ trễ của giao diện cũng đúng với API.

Lệnh kiểm tra nào nói thật

Phần 2 đã cho thấy cổng TCP mở trước khi broker sẵn sàng. Lần này tôi bấm giờ tất cả các cách kiểm tra cùng lúc, trên một container mới khởi động:

Mốc Xanh lúc
TCP tới cổng 5672 0,0 s
rabbitmq-diagnostics ping 1,5 s
Bắt tay AMQP thật 4,2 s
HTTP /api/overview 4,3 s
rabbitmq-diagnostics check_port_connectivity 4,4 s
rabbitmq-diagnostics check_local_alarms 4,4 s
rabbitmq-diagnostics check_running 4,5 s
ping báo xanh sớm hơn 2,7 giây so với lúc broker thật sự nhận được một kết nối AMQP. Nó chỉ hỏi "node Erlang có trả lời không", không hỏi "RabbitMQ đã phục vụ được chưa". Dùng nó làm healthcheck là dựng một cái đèn xanh nói dối.

Ba lệnh cuối bảng mới là thứ đáng dùng, và chúng nói ba điều khác nhau: check_running — ứng dụng RabbitMQ đã khởi động xong; check_port_connectivity — các listener đã nghe; check_local_alarms — node không đang trong tình trạng báo động bộ nhớ hay đĩa. Healthcheck ở phần 2 dùng check_running, và bảng này là lý do.

Lưu ý cách chạy: docker exec mặc định là root, và phần 2 cho thấy gọi công cụ Erlang bằng root lúc broker đang lên sẽ giết chính nó. Luôn thêm -u rabbitmq.

Số trên giao diện chậm hơn CLI bảy lần

Gửi 2 000 thông điệp rồi hỏi cả hai nguồn tới khi chúng đọc ra đủ 2 000:

rabbitmqctl list_queues    đọc được 2000 sau    489 ms
HTTP /api/queues           đọc được 2000 sau  3 448 ms

rabbitmqctl hỏi thẳng tiến trình đang giữ hàng đợi. HTTP API đọc từ kho thống kê mà plugin quản trị gom lại theo chu kỳ — nên nó luôn là ảnh chụp của quá khứ gần. Đúng cái bảng điện tử giữa sân.

Cho nên: đừng viết test hay script tự động chờ một con số trên HTTP API rồi kết luận. Cũng đừng hoảng khi giao diện hiện hàng đợi còn tồn đọng trong khi ứng dụng đã rút sạch — đợi vài giây rồi tải lại.

Bốn lệnh rabbitmqctl đáng thuộc

$ rabbitmqctl -q list_queues name messages consumers memory
name      messages  consumers  memory
do-tre    2000      0          2918168

$ rabbitmqctl -q list_exchanges name type
name          type
              direct      ← default exchange, tên rỗng
amq.direct    direct
amq.fanout    fanout

$ rabbitmqctl -q list_bindings source_name destination_name routing_key
source_name  destination_name  routing_key
             do-tre            do-tre      ← binding ngầm của default exchange

$ rabbitmq-diagnostics -q memory_breakdown
reserved_unallocated: 0.0897 gb (52.05%)
other_system: 0.0274 gb (15.92%)

Hai dòng có mũi tên xác nhận điều phần 3 nói: default exchange là một exchange thật, tên rỗng, và mỗi hàng đợi tự động có một binding tới nó với routing key trùng tên mình. Một broker mới có sẵn tám exchange — bảy cái amq.* cộng cái tên rỗng.

HTTP API để viết script

$ curl -su guest:guest localhost:15672/api/overview
rabbitmq_version : 4.3.5
erlang_version   : 27.3.4.16
queue_totals     : {'messages': 2000, 'messages_ready': 2000, 'messages_unacknowledged': 0}
object_totals    : {'channels': 0, 'consumers': 0, 'exchanges': 8, 'queues': 1, 'connections': 0}

/api/queues/%2F/<tên> cho một hàng đợi cụ thể — %2F là vhost / đã mã hoá URL, và đây là chỗ mọi script vấp lần đầu.

Trong đó có hai trường dễ nhầm nhau:

message_bytes : 8 890        ← tổng thân của 2 000 thông điệp
memory        : 1 145 920    ← bộ nhớ tiến trình hàng đợi

Dữ liệu thật chưa tới 9 KB, còn tiến trình giữ nó ăn hơn một megabyte. Chỉ số để đặt cảnh báo "hàng đợi đang phình" là message_bytes và messages, không phải memory — con số kia nói về bộ đệm và cấu trúc nội bộ, và nó dao động cả khi hàng đợi đứng yên. Bài 38 quay lại chuyện chọn chỉ số nào để cảnh báo.

Muốn thấy tận mắt độ lệch giữa hai nguồn, cho chúng chạy cạnh nhau ngay trên máy bạn:

# so hai nguon ngay tren may ban: cai nao doi truoc?
watch -n1 'docker exec -u rabbitmq rmq rabbitmqctl -q list_queues name messages; \
           curl -su guest:guest "localhost:15672/api/queues/%2F/do-tre" | jq .messages'

Bơm thông điệp vào rồi nhìn hai dòng đó lệch nhau. Khi đã thấy tận mắt độ lệch ấy, bạn sẽ không bao giờ viết một script chờ đợi dựa trên HTTP API nữa.

Mẫu số chung

Một con số đọc ra từ kho thống kê gom theo chu kỳ thì mãi mãi là ảnh chụp của quá khứ gần — không phải vì nó sai, mà vì đó là bản chất của nó, và không cách chỉnh nào làm nó tức thời được. Muốn sự thật ngay bây giờ thì phải hỏi thẳng cái đang giữ sự thật (rabbitmqctl gõ vào tiến trình hàng đợi), đừng hỏi cái bảng tổng hợp treo bên ngoài. Cùng khoảng-lệch ấy có mặt ở khắp nơi: đọc số từ một read-replica có độ trễ nhân bản rồi tưởng đó là hiện tại, đợi một COUNT đã cache, dựng logic đồng bộ trên chu kỳ scrape của Prometheus, chờ một chỉ số trong SHOW STATUS. Nguyên tắc: đừng bao giờ xây logic chờ-đợi hay quyết-định trên một con số tổng hợp có độ trễ — con số đó để nhìn xu hướng, còn để đồng bộ thì hỏi nguồn có thẩm quyền.

Điều thứ hai, cùng một họ: một phép kiểm tra sức khoẻ phải chạm đúng cái bạn thật sự phụ thuộc, chứ không phải một cái đại diện rẻ tiền hơn. ping báo "node Erlang còn thở", TCP mở báo "có ai đó ở cổng" — cả hai xanh trước khi broker phục vụ được lấy một kết nối, đúng như cổng sân đã mở khoá không có nghĩa là khán giả đã vào. Cùng cái bẫy proxy-thay-cho-thật: một liveness probe HTTP trả 200 trong khi pool kết nối CSDL đã cạn, một SELECT 1 xanh trong khi bảng bạn cần đang bị khoá, một cổng lắng nghe mở trước khi app nạp xong cấu hình. Chọn cái kiểm tra gần nhất với công việc thật — check_running, một truy vấn chạm đúng bảng bạn dùng — chứ đừng chọn cái tín hiệu bật xanh sớm nhất.

Bài sau: khai báo hàng đợi — bốn cờ durable, exclusive, auto-delete, và tổ hợp làm channel của bạn đóng sập với PRECONDITION_FAILED.