ZooKeeper
ZooKeeper là dịch vụ điều phối cho hệ phân tán: bầu leader, lưu cấu hình, service discovery và distributed lock dựa trên cây znode, node ephemeral và sequential, đồng thuận bằng ZAB. Bài này giải thích cách nó hoạt động, vì sao Kafka chuyển sang KRaft, và khi nào chọn etcd hay Consul.
Vấn đề điều phối và ZooKeeper là gì
Khi hệ thống có nhiều hơn một máy, sớm muộn bạn sẽ gặp những câu hỏi như:
- Leader election: trong 3 instance của scheduler, ai được phép chạy job? Nếu nó chết, ai lên thay, và làm sao chắc chắn chỉ có một người làm leader? Grokking mô tả đúng tình huống này ở replication primary-secondary: khi primary lỗi, các secondary bầu primary mới bằng một cuộc leader election.1
- Cấu hình chung: đổi một feature flag, làm sao nhiều server cùng thấy giá trị mới nhanh chóng?
- Service discovery: instance nào của
payment-serviceđang sống, ở địa chỉ nào? - Distributed lock: chỉ một worker được xử lý tài khoản X tại một thời điểm.
- Group membership và phân việc: nhiều worker chia nhau các shard, khi một worker chết thì shard của nó chuyển cho ai?
Tự giải quyết những việc này bằng database thường hay bằng Redis là cách dễ sai. Building Microservices cảnh báo: lock đã khó ngay trong một process, phân tán còn khó hơn nhiều (nếu node đang giữ lock biến mất thì sao?), và nên dùng một data store hoặc lock service có sẵn thay vì tự viết hệ nhất quán của riêng mình.2
Apache ZooKeeper là một trong các dịch vụ như vậy. Building Microservices mô tả nó là dịch vụ ra đời từ dự án Hadoop, dùng cho cấu hình, đồng bộ dữ liệu giữa service, leader election, hàng đợi và đặt tên (naming service); có thể coi nó là một cây thông tin được nhân bản mà bạn nhận được thông báo khi nó đổi.3 Nó không tự làm leader election hay lock; nó cung cấp các khối nguyên thuỷ để bạn ghép thành những thứ đó, thường qua thư viện client.3
Nhiều hệ thống lớn dùng ZooKeeper làm nơi theo dõi cluster. Grokking nêu HBase, Kafka và SolrCloud dùng ZooKeeper để lưu bảng ánh xạ partition và báo cho tầng định tuyến biết khi node thêm hoặc bớt.4 Seven Databases cho thấy Neo4j HA dùng ZooKeeper làm coordinator cho cụm: mỗi server Neo4j có một coordinator riêng quản lý vị trí của nó trong cluster.5
Kiến trúc: ensemble, quorum và ZAB
- Một cụm ZooKeeper chạy nhiều node. Building Microservices nói bạn nên chạy ít nhất ba node, và phần "thông minh" của ZooKeeper nằm ở việc nhân bản dữ liệu an toàn giữa các node và giữ nhất quán khi node lỗi.3 Số node thường là số lẻ vì cần quorum là đa số: với n node, ghi cần đa số xác nhận; 3 node chịu được 1 node chết.6
- Một server là leader, các server khác là follower (mô hình tương tự primary-secondary).1 Vai trò observer, cách bố trí RAM và đĩa là chi tiết của ZooKeeper.7
Giao thức đồng thuận của ZooKeeper là ZAB (ZooKeeper Atomic Broadcast), cùng họ ý tưởng với Paxos và Raft. Grokking nói để có strong consistency thường không chỉ cần replication đồng bộ mà còn cần thuật toán đồng thuận như Paxos hay Raft, và quorum-based replication được dùng để tăng availability.8 Chi tiết từng bước của ZAB chưa có sách, bài chỉ nêu ý chính:7
- Client gửi lệnh ghi tới một server; follower chuyển tiếp cho leader.
- Leader gán cho lệnh một số thứ tự tăng dần và gửi proposal tới các follower.
- Khi đa số follower ghi bền và xác nhận, leader gửi commit; lệnh được áp dụng theo cùng thứ tự trên mọi server.
- Khi leader chết, các server còn lại bầu leader mới sao cho không mất lệnh đã commit.
Về đảm bảo nhất quán (nhận định của khoá học, chưa có sách):7 ghi có một thứ tự toàn cục, lệnh của một client được áp dụng theo thứ tự gửi, đọc được phục vụ tại chỗ nên có thể chậm hơn leader một chút.
Trong ngôn ngữ CAP, ZooKeeper chọn CP: phía thiểu số khi mạng bị chia cắt từ chối phục vụ thay vì trả dữ liệu sai. Building Microservices nhắc rằng các hệ nhất quán cần locking để làm việc, và lưu ý đánh đổi giữa consistency và availability.9
Data model: znode, ephemeral, sequential và watch
Building Microservices mô tả lõi của ZooKeeper là một không gian tên phân cấp: client có thể thêm, sửa, truy vấn các nút trong cây và đặt watch để được báo khi nút thay đổi, nhờ đó có thể lưu vị trí service và được thông báo khi nó đổi.3 Mỗi nút gọi là znode:
/
├── config
│ └── payment-service {"timeoutMs": 3000}
├── services
│ └── payment-service
│ ├── 10.0.1.5:8080 (ephemeral)
│ └── 10.0.1.6:8080 (ephemeral)
└── election
├── n-0000000041 (ephemeral, sequential)
└── n-0000000042 (ephemeral, sequential)
Các loại znode và session là khái niệm riêng của ZooKeeper, chưa có sách trong thư viện:7
| Loại znode | Hành vi | Dùng cho |
|---|---|---|
| Persistent | Tồn tại tới khi bị xoá | Cấu hình, metadata |
| Ephemeral | Tự xoá khi session của client tạo ra nó kết thúc | Đăng ký instance đang sống, giữ lock |
| Sequential | ZooKeeper tự thêm số tăng dần vào tên | Hàng đợi công bằng cho lock, election |
- Session: client giữ kết nối và gửi heartbeat. Nếu ZooKeeper không nghe thấy gì trong khoảng session timeout, session hết hạn và mọi ephemeral node của client bị xoá. Đây là cơ chế phát hiện lỗi.
- Watch: client đăng ký nhận thông báo khi một znode thay đổi. Watch thường chỉ bắn một lần, client phải đăng ký lại.
- Mỗi znode có version, cho phép compare-and-set.
Lưu ý: ZooKeeper không được thiết kế để chứa dữ liệu lớn. Chỉ lưu metadata, cấu hình, con trỏ; dữ liệu thật để ở database (khuyến nghị của khoá học).7 Building Microservices cũng nói cá nhân tác giả ngại dùng hệ như ZooKeeper làm nguồn cấu hình vì khó suy luận hành vi của service.3
Các recipe điều phối
Các recipe dưới đây là cách dùng phổ biến của ZooKeeper, kiến thức sản phẩm chưa có sách kiểm chứng.7
Leader election
- Mỗi ứng viên tạo znode ephemeral sequential
/election/n-. - Lấy danh sách con. Ai có số nhỏ nhất là leader.
- Người không phải leader chỉ watch znode đứng ngay trước mình.
- Khi leader chết, session hết hạn, znode của nó biến mất. Chỉ người kế tiếp nhận được thông báo và kiểm tra lại xem mình có nhỏ nhất chưa.
Bước 3 tránh hiệu ứng herd effect: nếu mọi người cùng watch leader, một lần leader chết sẽ làm nhiều client cùng ập vào ZooKeeper.
Thử bằng công cụ dòng lệnh để thấy cơ chế:
create /election ""
create -e -s /election/n- "scheduler-a" # Created /election/n-0000000041
create -e -s /election/n- "scheduler-b" # Created /election/n-0000000042
ls /election # [n-0000000041, n-0000000042]
Đóng session của scheduler-a: znode n-0000000041 tự biến mất và scheduler-b lên làm leader.
Distributed lock
Giống hệt leader election, nhưng ở thư mục /locks/account-42. Ai có số nhỏ nhất thì giữ lock. Xong việc thì xoá znode. Nếu process chết giữa chừng, ephemeral node tự biến mất và lock được nhả.
Có một chi tiết quan trọng: process giữ lock có thể bị GC pause hoặc mất mạng lâu hơn session timeout. ZooKeeper thả lock cho người khác, trong khi process cũ vẫn tưởng mình đang giữ. Cách an toàn là dùng fencing token: số tăng dần gửi kèm mỗi lệnh ghi xuống storage, và storage từ chối token nhỏ hơn token lớn nhất đã thấy. Đây là kỹ thuật chung của lock phân tán, không riêng ZooKeeper. Xem Tranh chấp tài nguyên.
Cấu hình và service discovery
- Cấu hình: lưu ở
/config/<service>, mọi instance đọc và đặt watch; đổi cấu hình bằng một lệnh ghi, mọi instance nhận thông báo. - Service discovery: mỗi instance khi khởi động tạo ephemeral znode
/services/payment/<ip:port>. Client liệt kê con và watch thư mục. Instance chết thì node tự biến mất.
Building Microservices xếp ZooKeeper vào nhóm "dynamic service registries" và nhận xét nó khá chung chung, không cung cấp nhiều chức năng service discovery có sẵn so với các lựa chọn mới hơn, nên bạn thường phải tự xây thêm; đổi lại nó đã được kiểm chứng và dùng rộng rãi.3 Khác với Consul có sẵn DNS server và health check.10
Con số, vận hành và vì sao Kafka chuyển sang KRaft
Con số và sizing
Thư viện không có sách đo hiệu năng ZooKeeper nên bài không đưa throughput, độ trễ hay cấu hình máy cụ thể; hãy đo bằng load test.7 Những gì có nguồn: nên chạy ít nhất ba node, và dữ liệu cần nhân bản an toàn giữa các node.3
Ý chính (nhận định của khoá học): ZooKeeper hợp với đọc nhiều, ghi ít, vì mọi lệnh ghi phải đi qua một leader và chờ đa số xác nhận. Đừng dùng nó làm database, bộ đếm hay hàng đợi tin nhắn.7
Vận hành
- Nhỏ: hầu hết team không cần tự chạy ZooKeeper. Nếu phần mềm bạn dùng yêu cầu nó (như Neo4j HA trong Seven Databases),5 ưu tiên bản đi kèm dịch vụ managed. Nếu tự chạy, đặt 3 node ở 3 vùng lỗi khác nhau.7
- Lớn: theo dõi độ trễ, số request đang chờ, số watch, số znode; tránh GC pause dài làm session của client hết hạn hàng loạt.7
Vì sao Kafka bỏ ZooKeeper (KRaft)
Kafka từng lưu metadata của cluster (danh sách broker, topic, partition, leader của partition) trong ZooKeeper; Grokking cũng nêu Kafka dùng ZooKeeper cho việc theo dõi cluster và định tuyến.4 Phần lịch sử và lý do chuyển sang KRaft là kiến thức về sản phẩm, thư viện chưa có sách, nên chỉ nêu ý chính:7
- Hai hệ thống phân tán phải vận hành: hai kiểu cấu hình, bảo mật, giám sát.
- Controller failover chậm khi metadata lớn, vì controller mới phải đọc lại metadata từ ZooKeeper.
- Giới hạn quy mô: số partition mỗi cluster bị hạn chế bởi cách lưu metadata.
- Metadata có thể không nhất quán giữa ZooKeeper và bộ nhớ của controller.
KRaft đưa một nhóm controller của chính Kafka, dùng Raft để đồng thuận; metadata trở thành một log nội bộ nên controller dự bị luôn có metadata sẵn và failover nhanh hơn. Tên đề xuất và mốc phiên bản bài không nêu vì chưa có nguồn.
Bài học rút ra cho phỏng vấn: một dịch vụ điều phối bên ngoài là lựa chọn tốt khi bắt đầu, nhưng khi metadata lớn và cần phục hồi nhanh, các hệ thống trưởng thành có xu hướng tự nhúng consensus vào bên trong (nhận định của khoá học). Xem thêm Kafka.
So với etcd, Consul và các lựa chọn khác
Building Microservices so sánh ZooKeeper với Consul: Consul cũng hỗ trợ cấu hình và service discovery nhưng đi xa hơn, có giao diện HTTP cho service discovery, DNS server có sẵn (trả bản ghi SRV có cả IP và port), health check trên node, và tách phần quản lý cụm (Serf) ra riêng; Consul cũng có key/value store nhất quán mạnh để chia sẻ cấu hình.102 Eureka của Netflix thì chỉ tập trung vào service discovery, không làm kho cấu hình đa dụng.11
| ZooKeeper | Consul | etcd | Redis lock | |
|---|---|---|---|---|
| Mô hình dữ liệu | Cây phân cấp, có watch3 | Key/value nhất quán + catalog dịch vụ2 | Key-value (chưa có sách)7 | Key-value (chưa có sách)7 |
| Service discovery | Tự xây thêm3 | Có sẵn, kèm DNS và health check10 | Tự xây | Không |
| Ghi chú | Dùng nhiều trong hệ sinh thái Hadoop, HBase, Kafka cũ4 | Đã bao gồm failure detection (Serf)10 | Gắn với Kubernetes (chưa có sách) | Đơn giản nhất, đảm bảo yếu hơn (nhận định của khoá học) |
Gợi ý chọn (nhận định của khoá học, trừ khi có chú thích):
- Đang chạy Kubernetes: bạn đã có một kho điều phối bên dưới; leader election cho service của mình có thể dùng cơ chế sẵn của nền tảng.7
- Cần service discovery, health check, DNS cho nhiều môi trường: Consul.10
- Hệ sinh thái Hadoop, HBase, Solr, hoặc Kafka cũ: ZooKeeper vì phần mềm đó yêu cầu.4
- Lock ngắn, sai sót chấp nhận được (chống hai cron cùng gửi một email, có idempotency phía sau): một key có TTL trong Redis là đủ và đơn giản hơn nhiều.
- Lock bảo vệ dữ liệu quan trọng: ưu tiên lock trong chính database (
SELECT ... FOR UPDATEcủa PostgreSQL, conditional write của DynamoDB). Lock nằm cùng chỗ với dữ liệu thì không cần fencing token riêng.
Điểm nói chuyện trong phỏng vấn
Trong phỏng vấn: Khi bản thiết kế có "chỉ một instance được làm X", hãy nói rõ cơ chế: "dùng ZooKeeper hoặc etcd để bầu leader bằng ephemeral node hoặc lease, kèm fencing token tăng dần để leader cũ bị từ chối khi nó tỉnh lại sau một GC pause". Câu này cho thấy bạn hiểu cả cách làm lẫn cách nó hỏng. Và đừng tự viết hệ đồng thuận: Building Microservices khuyên dùng dịch vụ có sẵn.2
Sai lầm hay gặp: Đưa ZooKeeper vào đường đi của mọi request (ví dụ đọc cấu hình từ ZooKeeper cho mỗi API call, hoặc lấy lock cho mỗi lượt đặt hàng). Ghi qua quorum có giới hạn thông lượng. Hãy cache cấu hình và chỉ dùng điều phối cho những quyết định hiếm.7
Checklist ngắn:
- Nêu bài toán điều phối cụ thể: leader, lock, cấu hình, membership.
- Ensemble nhiều node lẻ (ít nhất 3), quorum đa số.36
- Ephemeral node để phát hiện chết, sequential node để xếp hàng, watch để nhận thay đổi.7
- Fencing token cho lock bảo vệ dữ liệu.
- Biết vì sao Kafka chuyển sang KRaft, và khi nào chọn Consul hoặc etcd.10
Các bài toán thường cần một dịch vụ điều phối:
- Job scheduler: bầu leader cho bộ lập lịch, phân shard job cho worker.
- Distributed cache: lưu danh sách node và bản đồ consistent hashing.
- Web crawler: chia domain cho các worker, phát hiện worker chết.
- Stock trading: một matching engine duy nhất cho mỗi mã, failover có kiểm soát.
- Chat: ánh xạ user tới gateway server đang giữ kết nối (thường kết hợp Redis).
- Rate limiter: đẩy cấu hình hạn mức tới mọi node.
Nguồn trích dẫn
-
Grokking Modern System Design · ch.9 Databases · mục "Single leader/primary-secondary replication" (chọn primary mới bằng leader election). ↩ ↩2
-
Building Microservices (file .zip, tải về giải nén) · ch.11 Microservices at Scale · mục "CAP Theorem" (tr. 232), đoạn nói về lock phân tán và "don't write your own distributed consistent data store". ↩ ↩2 ↩3 ↩4
-
Building Microservices (file .zip, tải về giải nén) · ch.11 Microservices at Scale · mục "Service Discovery", "Dynamic Service Registries", đoạn "Zookeeper" (tr. 238). ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10
-
Grokking Modern System Design · ch.9 Databases · bài Data Partitioning, mục "Request routing" và "ZooKeeper". ↩ ↩2 ↩3 ↩4
-
Seven Databases in Seven Weeks (file .zip, tải về giải nén) · ch.7 Neo4j · Day 3, mục "HA Cluster" và "Building the Cluster" (Zookeeper làm cluster coordinator). ↩ ↩2
-
Grokking Modern System Design · ch.9 Databases · mục "Quorums" (w + r > n). ↩ ↩2
-
Chưa có nguồn trong thư viện: thư viện không có sách riêng về ZooKeeper, etcd hay KRaft (ZAB, znode ephemeral/sequential, session, watch, recipe lock, lịch sử Kafka bỏ ZooKeeper); phần này chỉ nêu khái niệm cơ bản, không đưa số liệu, giới hạn hay phiên bản của sản phẩm. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16
-
Grokking Modern System Design · ch.3 Abstractions · mục về consistency (Paxos, Raft và quorum-based replication). ↩
-
Building Microservices (file .zip, tải về giải nén) · ch.11 Microservices at Scale · mục "CAP Theorem" (tr. 232–233). ↩
-
Building Microservices (file .zip, tải về giải nén) · ch.11 Microservices at Scale · mục "Dynamic Service Registries", đoạn "Consul" (tr. 239). ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Building Microservices (file .zip, tải về giải nén) · ch.11 Microservices at Scale · mục "Dynamic Service Registries", đoạn "Eureka" (tr. 239). ↩