Hai mươi ba bài vừa rồi dùng client Java thuần để thấy rõ từng cơ chế. Từ bài này, mọi thứ đó xuất hiện lại dưới dạng một dòng cấu hình — và ba mặc định của Spring Boot đi ngược lại đúng những gì sê-ri đã đo.
Auto-configuration cho sẵn gì
Thêm spring-boot-starter-amqp, không viết một dòng cấu hình nào, rồi hỏi context có gì:
ConnectionFactory -> CachingConnectionFactory (cache mode CHANNEL, cache 25 channel)
RabbitTemplate -> RabbitTemplate
RabbitAdmin -> RabbitAdmin
MessageConverter -> (không có bean nào)
Ba bean đầu là thứ bạn dùng hằng ngày. CachingConnectionFactory giữ lại channel để tái dùng — đúng thứ phần 3 đo được là quan trọng gấp 1 128 lần việc mở connection mỗi lần gửi.
Dòng cuối mới là chỗ cần chú ý: không có bean MessageConverter, nghĩa là RabbitTemplate dùng cái mặc định bên trong nó.
Mặc định thứ nhất: không có confirms, không có mandatory
publisher confirm type : false
publisher returns : false
Nghĩa là mọi thứ phần 17 đo được đều đang tắt: send() chỉ ghi vào socket rồi trả về, không ai biết broker có nhận hay không. Và phần 4 đo được rằng thiếu mandatory thì thông điệp gửi sai khoá sẽ biến mất im lặng — cũng đang tắt.
Bật bằng ba dòng:
spring:
rabbitmq:
publisher-confirm-type: correlated
publisher-returns: true
template:
mandatory: true
Mặc định thứ hai: convertAndSend dùng Java serialization
Đây là cái tôi nghĩ đáng ngạc nhiên nhất. Gửi một record hai trường bằng convertAndSend, rồi nhận lại và soi:
contentType : application/x-java-serialized-object
độ dài thân : 82 byte
40 byte đầu : ....sr..lab.Tem$DonHang...........I..tie
có phải Java serialization không: CÓ (magic 0xACED)
để so sánh, cùng dữ liệu đó dạng JSON:
{"ma":"DH-001","tien":250000} = 29 byte
lab.Tem$DonHang nằm ngay trong thân thông điệp — nên bên nhận buộc phải có đúng class đó, đúng package. Đổi tên package là mọi thông điệp đang nằm trong hàng đợi thành rác.
Đây là mặc định vì lý do lịch sử, không phải vì nó tốt. Đổi bằng một bean:
@Bean
MessageConverter jsonConverter() {
return new Jackson2JsonMessageConverter();
}
Chi tiết về những gì converter đó mang theo — kể cả một header làm hai dịch vụ không nói chuyện được với nhau — là bài 26.
Mặc định thứ ba: RabbitAdmin tự khai báo topology
RabbitAdmin có sẵn nghĩa là mọi bean Queue, Exchange, Binding trong context sẽ được khai báo lên broker lúc khởi động. Tiện, nhưng phần 15 đã đo cái giá: hai dịch vụ khai cùng một hàng đợi với tham số lệch nhau nhận 406 PRECONDITION_FAILED và channel đóng — và bên xui xẻo là bên khởi động sau, không phải bên khai sai.
RabbitTemplate không tốn gì
Câu hỏi tự nhiên: lớp trừu tượng này có làm chậm không? Đo cả hai trong cùng một lần chạy, cùng hàng đợi, cùng thông điệp persistent, 20 000 cái, trung vị 3 lượt:
| Cách gửi | Thông lượng |
|---|---|
RabbitTemplate.send |
374 660 msg/s |
channel.basicPublish thuần |
391 231 msg/s |
Chênh 4% — nằm trong nhiễu của bộ đo này. Không có lý do hiệu năng nào để bỏ Spring AMQP mà viết client thuần.
Cấu hình tối thiểu nên có
spring:
rabbitmq:
host: ${RABBIT_HOST:localhost}
publisher-confirm-type: correlated # bài 17
publisher-returns: true # bài 4
template:
mandatory: true # bài 4
Cộng thêm một bean Jackson2JsonMessageConverter. Bốn dòng đó vá đúng ba lỗ hổng mà sê-ri này đã đo được hậu quả của từng cái.
Bài sau: @RabbitListener và listener container — nơi có một mặc định còn nguy hiểm hơn, và nó tạo ra đúng vòng lặp thông điệp độc đã đo ở phần 20.
Thử ba mươi giây
# thong diep trong hang doi cua ban dang o dinh dang gi
curl -su guest:guest -X POST 'localhost:15672/api/queues/%2F/<ten>/get' \
-H 'Content-Type: application/json' \
-d '{"count":1,"ackmode":"ack_requeue_true","encoding":"auto"}' \
| jq '.[0].properties.content_type'
Ra "application/x-java-serialized-object" nghĩa là bạn đang dùng converter mặc định, và mọi bên nhận đều phải là Java với đúng class đó.