Event bus là thứ Vert.x có mà các khung web khác không có gì tương đương: một kênh nhắn tin nằm sẵn trong ứng dụng, dùng chung một API dù người nhận ở cùng JVM hay ở máy khác. Bài này đo ba kiểu gửi của nó.

Ba kiểu, ba ngữ nghĩa

eb.send("viec", "x");                          // MOT nguoi nhan
eb.publish("suKien", "x");                     // TAT CA cung nhan
eb.request("hoi", "x").onSuccess(m -> ...);    // gui va cho tra loi

Đăng ký ba consumer trên cùng một địa chỉ rồi gửi 3 000 thông điệp:

send    3 000 thông điệp tới 3 consumer -> 1000 / 1000 / 1000
publish 1 000 thông điệp tới 3 consumer -> 1000 / 1000 / 1000  (tổng 3000)

send chia vòng tròn tuyệt đối đều. publish nhân bản cho tất cả. Hai dòng này là toàn bộ khác biệt, và nó đúng cả khi các consumer nằm trên những node khác nhau của cụm — bài 21 sẽ đo phần đó.

Gửi vào địa chỉ không ai nghe

send    -> không lỗi, không ngoại lệ (thông điệp biến mất)
request -> NO_HANDLERS (mã -1)

send im lặng nuốt mất — giống hệt điều mà sê-ri RabbitMQ đo được với thông điệp không khớp binding. request thì báo ngay, vì nó đang chờ một câu trả lời sẽ không bao giờ tới.

Hệ quả thực tế: một lỗi gõ sai tên địa chỉ trong send không để lại dấu vết nào. Nếu địa chỉ quan trọng, hãy dùng request kể cả khi bạn không cần dữ liệu trả về — chỉ để biết có ai nghe hay không.

Con số tôi suýt đăng

Phép đo đầu tiên của tôi cho send 40 238 413 thông điệp mỗi giây. Bốn mươi triệu.

Nó sai. Vòng lặp của tôi chỉ gọi send hai trăm nghìn lần rồi đo thời gian — mà send không chờ giao, nó chỉ xếp thông điệp vào hàng của event loop rồi trả về. Bốn mươi triệu là tốc độ xếp hàng, không phải tốc độ giao.

Đo lại, chờ tới khi consumer thật sự nhận đủ 200 000 thông điệp:

Kiểu Thông lượng thật
send (chờ giao đủ) 3 402 692 /giây
request (chờ trả lời) 776 937 /giây
Đây đúng là cái bẫy mà sê-ri RabbitMQ gặp với basicPublish, chỉ đổi thư viện. Mọi API "gửi rồi trả về ngay" đều đo ra một con số đẹp và vô nghĩa nếu bạn không chờ đầu kia. Quy tắc: đo cái gì hoàn tất được, đừng đo cái gì chỉ xếp hàng.

Ba triệu bốn trăm nghìn vẫn là con số rất lớn — cao hơn ba mươi lần thông lượng HTTP tối đa mà phần 16 đo được. Event bus gần như không bao giờ là nút thắt.

Hỏi đáp chậm hơn bốn lần, và đó là điều đương nhiên

776 937 so với 3 402 692 — request chậm hơn 4,4 lần, vì mỗi lượt là hai chặng cộng một Future phải hoàn tất, và phần 6 đã đo mỗi chuỗi Future tốn khoảng 25 ns.

Độ trễ một lượt hỏi đáp trong cùng JVM:

p50 9,3 µs | p95 17,7 µs | p99 25,9 µs

Chín micro giây. Để so sánh, bài sau đo một lời gọi HTTP loopback không làm gì cả ở p50 56,5 µs — tức event bus rẻ hơn khoảng năm lần khi hai bên ở cùng tiến trình. Con số 20 ms mà phần 11 dùng là độ trễ tôi cố ý thêm vào để mô phỏng một dịch vụ chậm, không phải chi phí của bản thân HTTP.

Dùng kiểu nào

Bạn cần Kiểu
Giao việc cho một trong nhiều bản sao send — cân bằng tải sẵn
Báo cho mọi bên quan tâm publish
Cần kết quả, hoặc cần biết có ai nghe không request
Việc quan trọng nhưng không cần kết quả request rồi bỏ qua kết quả — để bắt NO_HANDLERS

Và nhớ rằng cả ba đều chạy trên event loop. Một consumer chặn 50 ms làm đúng những gì phần 3 đo được, chỉ khác là nó không có kết nối HTTP nào để nhìn thấy triệu chứng.

Bài sau: gửi POJO qua event bus, và một loại codec không sao chép gì cả.

Thử ba mươi giây

vertx.eventBus().addOutboundInterceptor(ctx -> {
    System.out.println("gui -> " + ctx.message().address());
    ctx.next();
});

Bốn dòng này in ra mọi địa chỉ đang được gửi tới. Chạy nó một lần trên ứng dụng thật là bạn có ngay bản đồ giao tiếp nội bộ — kể cả những địa chỉ gõ sai mà send đang lặng lẽ nuốt.