Phần 17 đo tốc độ event bus. Bài này đo thứ quan trọng hơn khi hệ thống chạy thật: chuyện gì xảy ra khi đầu bên kia không trả lời như mong đợi.
Mặc định là ba mươi giây
sendTimeout mặc định = 30 000 ms
Ba mươi giây. Đây là con số đầu tiên nên đổi trong mọi ứng dụng thật — nó dài hơn phép chờ của gần như mọi client HTTP đứng phía trước bạn.
eb.request("dv", than, new DeliveryOptions().setSendTimeout(2000));
timeout 200 ms -> TIMEOUT sau 212 ms (consumer cần 500 ms)
timeout 1000 ms -> nhận được sau 501 ms
Phép chờ hoạt động chính xác, sai số khoảng chục mili giây.
Ba cách một request hỏng, và chúng khác nhau
Đây là phần đáng nhớ của bài. Cả ba đều làm onFailure chạy — trừ một cái.
Không ai nghe địa chỉ đó:
request -> NO_HANDLERS mã=-1 "No handlers for address trong" sau 3 ms
Báo ngay sau 3 mili giây, không đợi hết timeout. Vert.x biết không có consumer nào nên trả lời liền.
Consumer chủ động báo lỗi:
m.fail(409, "don da bi huy");
request -> RECIPIENT_FAILURE mã=409 "don da bi huy"
Mã và thông điệp đi nguyên vẹn về bên gọi. Đây là cách đúng để báo lỗi nghiệp vụ, và failureCode dùng được như mã HTTP để bên gọi quyết định.
Consumer ném ngoại lệ:
vertx.eventBus().consumer("nem", m -> { throw new IllegalStateException("hong"); });
phía consumer: ERROR io.vertx.core.impl.ContextImpl -- Unhandled exception
phía gọi : KHÔNG có phản hồi nào trong 3 giây — chờ tới khi hết giờ (30 s)
NullPointerException trong một consumer là ba mươi giây treo cho mọi request đi qua nó.
Đây là cùng một hình dạng với thứ phần 6 đo được: ngoại lệ ném trong toán tử tiêu thụ thì biến mất. Ở đây nó còn tệ hơn, vì hậu quả rơi vào bên gọi chứ không phải bên gây lỗi.
Cách chữa là bọc lại và tự trả lời:
eb.consumer("dv", m -> {
try {
m.reply(xuLy(m.body()));
} catch (LoiNghiepVu e) {
m.fail(409, e.getMessage()); // loi nghiep vu, ben goi xu ly duoc
} catch (Exception e) {
log.error("hong", e);
m.fail(500, "loi noi bo"); // loi that, nhung van TRA LOI
}
});
Ba dòng catch này nên có trong mọi consumer trả lời. Không có chúng, mỗi lỗi lập trình biến thành một request treo ba mươi giây.
send và publish thì không báo gì cả
send -> không lỗi
publish -> không lỗi
Cả hai đều là "gửi rồi quên", kể cả khi không ai nghe. Nếu địa chỉ đó quan trọng, dùng request — kể cả khi bạn không cần dữ liệu trả về — chỉ để nhận NO_HANDLERS khi có người gõ sai tên.
Header đi kèm và không tốn gì
eb.request("dv", than, new DeliveryOptions().addHeader("traceId", traceId));
bên nhận thấy: trace-abc-123
| Thông lượng | |
|---|---|
| Không header | 1 082 193 lượt/giây |
| Hai header | 1 102 353 lượt/giây (102%) |
Miễn phí trong phạm vi nhiễu. Đây là đường duy nhất để mang ngữ cảnh qua ranh giới event bus — phần 8 đo được rằng context local không đi qua được, nên trace id phải nằm ở đây.
Bốn dòng nên có trong mọi lời gọi
eb.request("dv", than, new DeliveryOptions()
.setSendTimeout(2000) // dung de mac dinh 30 giay
.addHeader("traceId", traceId)) // ngu canh qua ranh gioi
.onSuccess(m -> ...)
.onFailure(t -> {
if (t instanceof ReplyException r) {
switch (r.failureType()) {
case NO_HANDLERS -> // sai dia chi, hoac dich vu chua len
case TIMEOUT -> // ben kia cham hoac treo
case RECIPIENT_FAILURE-> // loi nghiep vu, doc r.failureCode()
}
}
});
Ba nhánh trong switch là ba sự cố hoàn toàn khác nhau — sai cấu hình, quá tải, và lỗi nghiệp vụ — và gộp chúng vào một khối catch chung là tự bỏ đi thông tin chẩn đoán quý nhất mà event bus cho bạn.
Bài sau: event bus qua nhiều node, và con số 11 micro giây thay đổi thế nào khi thông điệp phải đi qua mạng.
Thử ba mươi giây
# tim consumer khong bat ngoai le
grep -rn "eventBus().consumer\|@ConsumeEvent" src/ -A5 | grep -L "try\|m.fail" | head
Mỗi consumer có m.reply(...) mà không có try bao quanh là một chỗ có thể làm bên gọi treo ba mươi giây.