Khi tách ứng dụng thành nhiều phần, câu hỏi đầu tiên là chúng nói chuyện với nhau bằng gì. Bài này đo hai lựa chọn trên cùng một máy, cùng một tiến trình, cùng một tương tác: hỏi và nhận một câu trả lời.

Đo

eb.request("dv", "x")                                   // event bus
hc.request(GET, "/").compose(send).compose(body)        // HTTP loopback

Cả hai đầu bên kia đều chỉ trả về chuỗi "ok".

p50 p95 p99
Event bus 11,3 µs 25,5 µs 44,3 µs
HTTP loopback 56,5 µs 121,4 µs 176,3 µs
Thông lượng (64 lượt bay song song)
Event bus 1 096 025 lượt/giây
HTTP loopback 95 838 lượt/giây (9%)

Chậm hơn 5 lần về độ trễ, 11 lần về thông lượng. Không có gì bất ngờ: HTTP phải phân tích dòng yêu cầu, dựng header, quản lý pool kết nối, đi qua ngăn xếp TCP — còn event bus trong cùng JVM là một lời gọi hàm cộng một lần chuyển ngữ cảnh.

Nhưng 56 micro giây là rất nhanh

Đây mới là điều đáng nói. Năm mươi sáu micro giây nghĩa là một lời gọi HTTP nội bộ tốn 0,056 mili giây. Nếu dịch vụ hạ nguồn của bạn mất 20 ms để trả lời — con số rất bình thường cho một truy vấn CSDL — thì chi phí giao thức chiếm 0,3% tổng thời gian.

Nghĩa là bảng ở trên gần như không bao giờ là lý do để chọn event bus. Nếu bạn đang cân nhắc chuyển từ HTTP sang event bus để "nhanh hơn 11 lần", hãy đo xem phần công việc thật của bạn tốn bao nhiêu trước. Với gần như mọi ứng dụng, bạn sẽ tối ưu 0,3% và giữ nguyên 99,7%.

Tôi đã tự mắc đúng lỗi này: phần 17 bản đầu viết rằng một lời gọi HTTP nội bộ tốn 20 000 µs, trong khi 20 ms đó là độ trễ tôi cố ý thêm vào để mô phỏng dịch vụ chậm. Chi phí thật của HTTP là 56 µs, và bài đó đã được sửa lại.

Vậy chọn event bus khi nào

Không phải vì tốc độ, mà vì ba tính chất kiến trúc:

Cân bằng tải sẵn có. Phần 17 đo được send chia vòng tròn tuyệt đối đều giữa các consumer, và điều đó vẫn đúng khi các consumer nằm trên những node khác nhau. Với HTTP bạn cần một bộ cân bằng tải.

Cùng một API dù ở gần hay xa. Cùng một dòng eb.request(...) chạy được khi consumer ở cùng JVM và khi nó ở máy khác trong cụm. Không phải đổi mã, không phải biết địa chỉ.

Ghép nối lỏng thật sự. Bên gửi biết một chuỗi địa chỉ, không biết host, cổng, hay đường dẫn. Thêm bản sao, chuyển verticle sang node khác — bên gửi không hay biết.

Khi nào HTTP vẫn đúng hơn

Khi bên kia không phải Vert.x. Event bus là giao thức riêng. Một dịch vụ Python, một hệ thống của đối tác, một trình duyệt — tất cả nói HTTP.

Khi bạn cần hạ tầng quan sát sẵn có. Mọi công cụ APM, mọi bộ thu log truy cập, mọi biểu đồ độ trễ theo endpoint đều hiểu HTTP. Event bus thì bạn phải tự dựng — bài 32 sẽ nói về chuyện đó.

Khi ranh giới là ranh giới tổ chức. Hai đội, hai vòng đời triển khai, hai lịch phát hành. HTTP có phiên bản, có hợp đồng, có mã trạng thái mà ai cũng hiểu. Event bus dùng chung một không gian địa chỉ phẳng và không có gì ép hai bên giữ đúng hợp đồng — phần 18 đo được rằng một codec tự viết còn có thể truyền thẳng tham chiếu object giữa hai bên.

Luật thực dụng

Trong một ứng dụng, giữa các verticle: event bus. Đó là thứ nó sinh ra để làm.

Giữa hai dịch vụ do hai đội sở hữu: HTTP. Chậm hơn 56 micro giây, đổi lại là một ranh giới rõ ràng.

Giữa hai node của cùng một ứng dụng: event bus có phân cụm — và bài 21 sẽ đo xem con số 11 µ giây ở trên thay đổi thế nào khi thông điệp phải đi qua mạng thật.

Thử ba mươi giây

# do chi phi HTTP noi bo cua chinh ban
curl -s -o /dev/null -w "%{time_total}\n" http://localhost:8080/health

Nhân con số đó với số lần dịch vụ của bạn gọi nội bộ trong một request. Nếu tổng vẫn nhỏ hơn 1% thời gian phản hồi, chuyện chọn giao thức không phải vấn đề hiệu năng của bạn.