Phần 21 đã dựng cụm hai node để đo event bus. Bài này dựng ba node và hỏi những câu của người vận hành: gia nhập mất bao lâu, dữ liệu chung có sống sót khi mất một node, và những API "toàn cụm" của Vert.x có thật sự dùng được không.
Câu cuối cùng cho ra câu trả lời khó chịu nhất.
Vert.x 4.5.11 với vertx-hazelcast, kéo theo Hazelcast 5.5.0. Ba node trên cùng máy, khám phá bằng TCP/IP (không multicast).
Gia nhập cụm
| Thời gian gia nhập | Cụm có mấy node lúc đó | |
|---|---|---|
| Node 0 | 1 635 ms | 1 |
| Node 1 | 626 ms | 2 |
| Node 2 | 612 ms | 3 |
Node đầu tiên tốn gấp 2,6 lần hai node sau, vì nó phải thử nối tới các thành viên trong danh sách, chờ hết hạn, rồi tự lập cụm. Node thứ hai và thứ ba chỉ cần bắt tay với một thành viên đã có.
Con số này đáng nhớ khi bạn triển khai theo kiểu cuốn chiếu: cứ mỗi tiến trình khởi động lại là thêm chừng 0,6 giây trước khi nó nhận việc. Nhân với số bản sao là ra khoảng thời gian năng lực hệ thống bị hụt.
Chia tải và dữ liệu chung
Event bus chia tải theo vòng tròn, và nó chia rất đều — 30 lời gọi phát ra từ node 0:
node0=10 node1=10 node2=10
Đúng một phần ba mỗi node, kể cả node phát ra lời gọi cũng chỉ nhận phần của nó. Thông lượng qua cụm đạt 76 294 req/s với p50 0,6 ms.
AsyncMap cũng hoạt động đúng như mong đợi: ghi ở node 0, đọc được ngay ở node 1 và node 2.
Mất một node
Tôi kill -9 node 2 rồi đo trong 25 giây:
node 0 nhan ra chi con 2 node sau: 3,2 s
trong 25 s do: 1/106 loi khi goi qua event bus
Chỉ một lời gọi hỏng — đúng cái đang bay tới node vừa chết. Sau 3,2 giây, cụm ổn định lại với 2 node và mọi lời gọi tiếp theo chia đều cho hai node còn sống.
Nhưng 3,2 giây đó là nhờ tôi đã chỉnh cấu hình. Đọc thẳng giá trị mặc định từ thư viện:
hazelcast.max.no.heartbeat.seconds = 60
hazelcast.heartbeat.interval.seconds = 5
hazelcast.merge.first.run.delay.seconds = 300
Mặc định là 60 giây trước khi một node im lặng bị coi là đã chết. Trong suốt một phút đó, event bus vẫn gửi một phần ba lưu lượng tới một tiến trình không còn tồn tại. Tôi hạ xuống 6 giây để đo được trong một bài viết:
c.setProperty("hazelcast.heartbeat.interval.seconds", "1");
c.setProperty("hazelcast.max.no.heartbeat.seconds", "6");
Đây là một đánh đổi thật, không phải một nút "làm cho tốt hơn": ngưỡng thấp làm cụm nhạy hơn với một node chỉ đang tạm bận — GC dài, đĩa treo — và đá nhầm một node khoẻ ra khỏi cụm còn tệ hơn là chậm nhận ra một node chết. Con số đúng phụ thuộc vào việc GC của bạn dừng thế giới bao lâu.
Dữ liệu chung thì sống sót. Tôi ghi 5 khoá, giết node 2, rồi đọc lại:
khoa-1 -> node0=gia-tri-1 node1=gia-tri-1
khoa-2 -> node0=gia-tri-2 node1=gia-tri-2
...
Đủ cả 5, vì Hazelcast mặc định giữ một bản sao dự phòng cho mỗi phân vùng. Nghĩa là cụm chịu được mất một node; mất hai node cùng lúc thì mất dữ liệu, và đó là lý do backup-count đáng nhìn lại nếu cụm của bạn quan trọng.
Một cảnh báo về phép đo này: lần thử đầu tiên tôi đọc ra null và suýt viết rằng dữ liệu bị mất khi node chết. Thật ra khoá đó được ghi ở lần dựng cụm trước — tôi đã khởi động lại cả ba node ở giữa. Ghi lại rồi đo lại thì đủ cả 5. Cụm là thứ rất dễ đo nhầm kiểu này, vì trạng thái nằm ở nơi khác chứ không nằm trong tiến trình bạn đang nhìn.
Hai API trong lõi Vert.x không chạy
SharedData của Vert.x cho ba thứ dùng chung toàn cụm: bản đồ, khoá, và bộ đếm. Bản đồ chạy tốt như trên. Hai cái còn lại thì không:
getLock -> UnsupportedOperationException: CP subsystem is a licensed feature.
Please ensure you have an Enterprise license that enables CP.
getCounter -> UnsupportedOperationException: (y het)
Khoá phân tán và bộ đếm phân tán của Hazelcast nằm trong CP subsystem, và CP subsystem là tính năng thương mại. Bản mã nguồn mở mà vertx-hazelcast kéo về không có nó.
Đây không phải lỗi của ai — Vert.x định nghĩa một API chung, mỗi trình quản lý cụm cài đặt theo khả năng của mình. Nhưng hậu quả rất cụ thể: hai phương thức nằm trong tài liệu lõi của Vert.x sẽ hỏng lúc chạy, tuỳ vào việc bạn chọn trình quản lý cụm nào, và không có gì báo cho bạn lúc biên dịch hay lúc khởi động.
Tệ hơn, getCounter ném ngoại lệ đồng bộ thay vì trả về một Future thất bại:
vertx.sharedData().getCounter("dem-do")
.compose(k -> k.incrementAndGet())
.onFailure(e -> ...); // KHONG bao gio chay
onFailure không bao giờ được gọi, vì ngoại lệ nổ ra trước khi Future kịp tồn tại. Nó chui thẳng ra router và thành Unhandled exception in router — client nhận 500 Internal Server Error trần trụi. Nếu bạn có thói quen tin rằng mọi lỗi trong mã bất đồng bộ đều đi qua onFailure, đây là một phản ví dụ đáng nhớ.
Nếu bạn cần khoá phân tán mà không muốn mua giấy phép, các lựa chọn thực dụng là dùng một khoá trong cơ sở dữ liệu (SELECT ... FOR UPDATE), hoặc Redis, hoặc thiết kế lại để không cần khoá — ví dụ dùng đúng một câu UPDATE ... WHERE có điều kiện, cách mà bài này của tôi về thử lại và nhân lên đã nhắc tới ở chỗ khác.
Các trình quản lý cụm khác
Vert.x có bốn trình quản lý cụm chính thức. Tôi chỉ đo Hazelcast, nên phần này là để định hướng chứ không kèm số:
| Ghi chú | |
|---|---|
| Hazelcast | Mặc định thực tế, tài liệu nhiều nhất. Khoá và bộ đếm cần bản Enterprise. |
| Infinispan | Của Red Hat, hợp nếu bạn đã ở trong hệ sinh thái Quarkus/WildFly. |
| Apache Ignite | Nặng hơn, mạnh về lưu trữ phân tán. |
| Zookeeper | Không nhúng — cần cụm Zookeeper riêng. Đổi lại, việc điều phối là nghề của nó. |
Điểm đáng cân nhắc chung: ba cái đầu nhúng thẳng vào tiến trình của bạn, nên chúng dùng chung heap, dùng chung GC, và một sự cố bộ nhớ trong mã nghiệp vụ sẽ kéo cả thành viên cụm theo. Cái cuối tách hẳn ra, đổi lấy việc phải vận hành thêm một cụm nữa.
Thử ba mươi giây
Kiểm tra xem cụm của bạn mất bao lâu để nhận ra một node chết — con số này gần như không ai biết cho tới lúc cần:
# cua so 1: goi lien tuc va dem loi
while true; do curl -s -o /dev/null -w "%{http_code} " localhost:8080/goi-qua-event-bus; sleep 0.2; done
# cua so 2: giet mot node
kill -9 <pid-node-khac>
Đếm xem bao nhiêu giây trôi qua trước khi dòng mã lỗi thôi xuất hiện. Nếu con số gần 60 thì bạn đang chạy mặc định của Hazelcast, và bạn vừa biết mình sẽ mất một phút lưu lượng cho mỗi lần một tiến trình chết đột ngột.
Phần sau bàn về kiểm thử ứng dụng Vert.x, và vì sao test bất đồng bộ hay xanh giả.