Khi số dịch vụ vượt quá con số bạn nhớ được, chúng cần một chỗ để tìm thấy nhau. Vert.x có sẵn module cho việc đó, và bài này đo hai thứ mà tài liệu không nói: tìm tốn bao nhiêu, và bản ghi cũ sống bao lâu.

Ba kiểu bản ghi

ServiceDiscovery sd = ServiceDiscovery.create(vertx);
sd.publish(HttpEndpoint.createRecord("don-hang", "localhost", 8080, "/"));
sd.publish(EventBusService.createRecord("kho", "dv.kho", KhoService.class));
sd.publish(MessageSource.createRecord("su-kien-don", "don.su-kien"));
don-hang      kiểu=http-endpoint          | {"endpoint":"http://localhost:8080/","host":"localhost","port":8080,...}
kho           kiểu=eventbus-service-proxy | {"endpoint":"dv.kho"}
su-kien-don   kiểu=message-source         | {"endpoint":"don.su-kien"}

Ba kiểu tương ứng ba cách gọi nhau mà sê-ri đã đo: HTTP (phần 11), proxy qua event bus, và luồng sự kiện. Điểm chung là bên gọi chỉ cần biết tên, không cần biết host, cổng hay địa chỉ event bus.

Chi phí tìm tăng tuyến tính

Số bản ghi trong sổ p50 p99
3 18,6 µs 65,1 µs
103 128,5 µs 236,2 µs
1 003 911,6 µs 2 371,1 µs
Gấp ba mươi ba lần số bản ghi thì tìm chậm gấp bốn mươi chín lần. getRecord nhận một hàm lọc và duyệt tuần tự toàn bộ sổ đăng ký — không có chỉ mục theo tên. Ở 1 000 dịch vụ, mỗi lần tra cứu tốn gần một mili giây.

Để so sánh: phần 21 đo một lượt hỏi đáp qua cụm ở 66 µs. Nghĩa là với sổ đăng ký lớn, việc tìm dịch vụ đắt gấp mười bốn lần việc gọi nó.

Đó không phải lỗi thiết kế — sổ đăng ký sinh ra để tra cứu thưa thớt, không phải trên mỗi request. Nhưng nó nói rất rõ cách dùng đúng: tra một lần rồi giữ lại, đừng gọi getRecord trong đường xử lý request.

Và bản ghi đã cầm trong tay không bao giờ tự hết hạn

Đó chính là chỗ lời khuyên trên đâm vào một vấn đề khác.

lấy được: localhost:8090
sau khi dịch vụ biến mất, bản ghi trong tay vẫn trỏ tới: localhost:8090
   (không có gì báo rằng nó đã chết)

getRecord sau khi unpublish trả về null — sổ đăng ký cập nhật đúng. Nhưng bản sao bạn đã lấy về thì không: nó là một đối tượng bình thường, không có hạn dùng, không có sự kiện nào bắn tới.

Hai lời khuyên đúng đắn mâu thuẫn nhau: tra mỗi lần thì tốn tới một mili giây; tra một lần thì bạn giữ một địa chỉ có thể đã chết.

Cách thoát khỏi thế kẹt đó

Tra một lần, và lắng nghe thay đổi. Sổ đăng ký phát sự kiện lên event bus khi có bản ghi được thêm hoặc gỡ; đăng ký nghe địa chỉ đó rồi cập nhật bộ nhớ đệm của bạn. Đây là cách đúng, và nó chỉ tốn vài dòng.

Đừng tin sổ đăng ký thay cho phép chờ. Một dịch vụ có thể chết mà chưa kịp unpublish — máy sập, container bị giết. Bản ghi vẫn nằm đó và trông hoàn toàn khoẻ mạnh. Phép chờ của phần 11 và circuit breaker (bài sau) mới là thứ phát hiện chuyện đó.

Cân nhắc xem bạn có cần nó không. Nếu các dịch vụ của bạn đã nằm sau một hệ thống điều phối — Kubernetes, một bộ cân bằng tải, hay event bus có phân cụm với địa chỉ cố định — thì bạn đã có tra cứu tên rồi, và thêm một sổ đăng ký nữa là thêm một chỗ để lệch nhau.

Bài sau: circuit breaker — thứ duy nhất trong sê-ri này thật sự ngừng gửi việc đi khi hạ nguồn ốm, điều mà phần trước đo được rằng timeout không làm.

Thử ba mươi giây

sd.getRecords(r -> true).onSuccess(ds ->
    System.out.println("so ban ghi trong so: " + ds.size()));

Nhân số đó với 0,9 µs để ước lượng chi phí mỗi lần tra cứu. Nếu mã của bạn gọi getRecord trong handler xử lý request, đó là con số đang cộng vào mọi lời gọi.