Phần trước đã có broker chạy và một thông điệp đi qua. Bài này mổ xem thông điệp đó thật ra đi qua những gì — năm khái niệm, và hai trong số đó quyết định hiệu năng của mọi thứ bạn viết sau này.
Năm khái niệm
| Khái niệm | Là gì |
|---|---|
| Connection | Một kết nối TCP tới broker. Đắt, mở lâu |
| Channel | Một phiên làm việc ảo bên trong connection. Rẻ |
| Exchange | Nơi thông điệp đến trước. Nó không lưu gì cả |
| Queue | Nơi thông điệp thật sự nằm chờ |
| Binding | Luật nối exchange với queue. Không có binding thì không có đường đi |
Điều quan trọng nhất: producer không bao giờ gửi vào queue. Nó gửi vào exchange, và exchange dựa vào binding để quyết định chép thông điệp sang những queue nào — không, một, hay nhiều.
Connection đắt hơn channel bao nhiêu
Mở 200 cái mỗi loại, tính giờ:
=== A. MO 200 CAI ===
200 connection : 383,1 ms tổng | 1,915 ms mỗi cái
200 channel : 65,6 ms tổng | 0,328 ms mỗi cái
=> connection đắt gấp 6 lần
channel_max của connection: 2047
Sáu lần nghe không ghê gớm. Nhưng con số đó chỉ đo việc mở. Hãy xem nó đọng lại thế nào khi gửi thật.
Ba cách gửi 1 000 thông điệp
Cùng một broker, cùng một hàng đợi, chỉ khác chỗ tái sử dụng:
=== B. GUI 1000 THONG DIEP ===
mỗi thông điệp một connection mới : 2 658 ms | 376 msg/s
một connection, mỗi thông điệp một channel : 513 ms | 1 948 msg/s
một connection, một channel dùng lại : 2 ms | 424 291 msg/s
newConnection() vào trong hàm gửi vì trông có vẻ "sạch".
Một chú thích cho trung thực: con số 424 291 msg/s là tốc độ phía client, khi basicPublish chỉ ghi vào socket rồi trả về ngay. Broker chưa chắc đã xử lý xong lúc đó — nó không hứa gì cả. Muốn biết broker đã nhận thật hay chưa thì cần publisher confirms, và con số sẽ khác hẳn; đó là phần 17.
Điểm cần rút ra ở đây không phải "424 nghìn", mà là hai bậc thang giữa ba cách viết cùng một việc.
Phía broker trả giá gì
Client trả giá thời gian; broker trả giá bộ nhớ. Giữ 200 cái mở rồi hỏi chính broker:
| 200 connection | 1 connection + 200 channel | |
|---|---|---|
connection_readers |
8,5 MB | 0,2 MB |
connection_channels |
0 MB | 0,6 MB |
connection_other |
4,2 MB | 2,4 MB |
Mỗi connection kéo theo một tiến trình đọc socket riêng — 8,5 MB cho 200 cái, khoảng 43 KB mỗi cái. Hai trăm channel dùng chung một socket thì tốn 0,6 MB, tức khoảng 3 KB mỗi cái.
Còn RSS của container thì đừng đọc ở mức này: giữa hai phép đo nó nhảy từ 191,7 lên 197,7 MiB ở lần một, rồi giảm từ 189,6 xuống 186,6 MiB ở lần hai. Bộ cấp phát của Erlang không trả bộ nhớ về hệ điều hành ngay, nên con số của docker stats nói về thói quen của allocator chứ không nói về tải.
Binding quyết định thông điệp đi đâu
Khai một exchange don-hang kiểu direct, hai hàng đợi kho và ke-toan, nhưng chỉ bind kho với khoá moi. Rồi gửi hai thông điệp, khoá moi và khoá huy:
kho : 1 thông điệp
ke-toan : 0 thông điệp (chưa bind gì)
Thông điệp khoá huy đi đâu? Không đi đâu cả. Nó biến mất. Không ngoại lệ, không log, không đếm ở chỗ nào. Đây là chỗ nuốt thông điệp phổ biến nhất của người mới, và RabbitMQ có hai cơ chế để bắt nó lại — mandatory cùng alternate exchange (phần 13).
Bind thêm ke-toan vào cùng khoá moi rồi gửi một thông điệp:
kho : 2
ke-toan : 1
Một lần gửi, hai bản sao. Exchange không chuyển thông điệp đi — nó chép sang mọi queue có binding khớp. Nếu bạn từng thắc mắc vì sao hai dịch vụ đọc chung một hàng đợi lại giành nhau thông điệp thay vì mỗi bên nhận một bản: vì chúng cần hai queue, không phải hai consumer.
Ba luật rút ra
Một connection cho cả ứng dụng, một channel cho mỗi luồng. Đừng mở connection theo thông điệp, cũng đừng dùng chung một channel cho nhiều luồng — channel không an toàn luồng, và bug kiểu đó biểu hiện thành khung dữ liệu lỗi lúc chạy tải cao chứ không phải một ngoại lệ dễ đọc.
2 047 channel là trần mặc định của một connection. Đủ dùng cho hầu hết ứng dụng, nhưng nếu bạn tạo channel theo request mà không đóng, đây là chỗ nó sẽ nổ.
Exchange không lưu gì. Gửi vào exchange chưa có binding nào thì cũng như gửi vào hư không. Khai binding trước khi gửi, hoặc bật mandatory để biết mình đang mất gì.
Bài sau: mổ từng tham số của basicPublish và basicConsume — vòng đời một thông điệp từ lúc gửi tới lúc được ack.
Thử ba mươi giây
# xem broker đang giữ bao nhiêu connection và channel ngay lúc này
docker exec -u rabbitmq rmq rabbitmqctl -q list_connections name channels
docker exec -u rabbitmq rmq rabbitmqctl -q list_channels connection number consumer_count
Chạy ứng dụng của bạn rồi gõ lại hai lệnh trên. Nếu số connection tăng theo số request, bạn vừa tìm ra 1 128 lần.