前言
Zookeeper 作为分布式系统的“瑞士军刀”,凭借其强一致性、顺序节点、临时节点等特性,成为解决分布式协调问题的首选工具。本文将从 高频到低频 依次解析 Zookeeper 的典型应用场景,并提供 逐行注释的代码实现,带你彻底掌握 Zookeeper 的核心应用!
🔥 高频场景 1:分布式锁
应用场景:秒杀系统、资源独占访问。
核心原理:利用 临时顺序节点 实现公平锁,通过 Watcher 监听 实现锁竞争。
1.1 排他锁(Exclusive Lock)
public class DistributedLock {
private ZooKeeper zk;
private String lockPath = "/exclusive_lock";
private String currentLock;
public void lock() throws Exception {
// 创建临时节点,节点路径格式:/exclusive_lock/lock-
currentLock = zk.create(lockPath + "/lock-",
new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL); // EPHEMERAL_SEQUENTIAL:临时顺序节点
// 获取所有子节点并排序
List<String> children = zk.getChildren(lockPath, false);
Collections.sort(children);
// 判断当前节点是否是最小序号节点(即是否获得锁)
int index = children.indexOf(currentLock.substring(lockPath.length() + 1));
if (index == 0) {
System.out.println("获得锁!");
return;
}
// 监听前一个节点
String prevNode = lockPath + "/" + children.get(index - 1);
zk.exists(prevNode, new Watcher() {
@Override
public void process(WatchedEvent event) {
synchronized (this) {
notifyAll(); // 前一个节点释放锁,唤醒当前线程
}
}
});
synchronized (this) {
wait(); // 阻塞等待前一个节点释放锁
}
}
public void unlock() throws Exception {
zk.delete(currentLock, -1); // 删除节点,释放锁
}
}
注释:
- 临时顺序节点:避免死锁(客户端断开自动删除),顺序节点保证公平性。
- Watcher 监听:通过监听前一个节点的删除事件,实现锁的排队机制。
1.2 共享锁(Shared Lock)
public class SharedLock {
private static final String READ_LOCK_PREFIX = "read-";
private static final String WRITE_LOCK_PREFIX = "write-";
public void acquireReadLock() throws Exception {
// 创建临时顺序节点:/shared_lock/read-0001
String node = zk.create("/shared_lock/" + READ_LOCK_PREFIX,
new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
// 获取所有写锁节点,若无写锁则直接获取读锁
List<String> writeLocks = zk.getChildren("/shared_lock", false)
.stream()
.filter(s -> s.startsWith(WRITE_LOCK_PREFIX))
.collect(Collectors.toList());
if (writeLocks.isEmpty()) {
return;
}
// 监听最近的写锁节点
String lastWriteLock = Collections.max(writeLocks);
zk.exists("/shared_lock/" + lastWriteLock, new Watcher() { /* ... */ });
wait();
}
}
注释:
- 读写分离:读锁可共享,写锁需独占。
- 监听策略:读锁需等待所有写锁释放。
🔧 高频场景 2:配置中心
应用场景:动态调整系统参数(如数据库连接池大小)。
核心原理:通过 Watcher 监听配置节点 实现实时更新。
public class ConfigManager {
private ZooKeeper zk;
private String configPath = "/app/config";
private Properties config = new Properties();
public void init() throws Exception {
// 初始化时获取配置
byte[] data = zk.getData(configPath, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeDataChanged) {
loadConfig(); // 重新加载配置
}
}
}, null);
config.load(new ByteArrayInputStream(data));
}
private void loadConfig() {
// 重新获取配置数据
byte[] newData = zk.getData(configPath, true, null);
config.clear();
config.load(new ByteArrayInputStream(newData));
System.out.println("配置已更新:" + config);
}
}
注释:
- Watcher 回调:配置变更时自动触发
loadConfig。 - 数据持久化:配置节点需为持久节点(
CreateMode.PERSISTENT)。
🌐 高频场景 3:服务注册与发现
应用场景:微服务架构中的服务动态上下线。
核心原理:利用 临时节点 实现服务实例存活检测。
public class ServiceRegistry {
private static final String REGISTRY_PATH = "/services";
private String serviceInstancePath;
public void register(String serviceName, String instanceInfo) throws Exception {
// 创建服务实例节点:/services/order-service/instance-0001(临时节点)
serviceInstancePath = zk.create(REGISTRY_PATH + "/" + serviceName + "/instance-",
instanceInfo.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
}
public List<String> discover(String serviceName) throws Exception {
// 获取所有服务实例
return zk.getChildren(REGISTRY_PATH + "/" + serviceName,
new Watcher() { /* 监听子节点变化 */ });
}
}
注释:
- 临时节点:服务下线时节点自动删除。
- Watcher 监听:客户端可实时感知服务实例变化。
⚙️ 中频场景 4:分布式选主(Leader Election)
应用场景:定时任务调度、主备切换。
核心原理:通过 最小顺序节点 选举 Leader。
public class LeaderElection {
private String electionPath = "/election";
private String currentNode;
public void participate() throws Exception {
// 创建临时顺序节点:/election/node-0001
currentNode = zk.create(electionPath + "/node-",
new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
// 获取所有节点并排序
List<String> nodes = zk.getChildren(electionPath, false);
Collections.sort(nodes);
// 判断当前节点是否为最小节点
if (currentNode.endsWith(nodes.get(0))) {
System.out.println("当选 Leader!");
} else {
// 监听前一个节点
int prevIndex = nodes.indexOf(currentNode.substring(electionPath.length() + 1)) - 1;
String prevNode = electionPath + "/" + nodes.get(prevIndex);
zk.exists(prevNode, new Watcher() { /* ... */ });
}
}
}
注释:
- 最小节点策略:序号最小的节点成为 Leader。
- 故障转移:Leader 下线后,次小节点自动接替。
📉 低频场景 5:分布式队列
应用场景:任务调度、批量处理。
核心原理:利用 顺序节点 实现 FIFO 队列。
public class DistributedQueue {
private String queuePath = "/queue";
public void enqueue(String task) throws Exception {
// 创建顺序节点:/queue/task-0001
zk.create(queuePath + "/task-",
task.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT_SEQUENTIAL); // 持久顺序节点
}
public String dequeue() throws Exception {
// 获取所有任务节点并排序
List<String> tasks = zk.getChildren(queuePath, false);
if (tasks.isEmpty()) {
return null;
}
Collections.sort(tasks);
// 取出最小序号节点并删除
String firstTask = tasks.get(0);
byte[] data = zk.getData(queuePath + "/" + firstTask, false, null);
zk.delete(queuePath + "/" + firstTask, -1);
return new String(data);
}
}
注释:
- FIFO 保证:按节点序号顺序消费。
- 持久化存储:节点需为持久节点,防止任务丢失。
🌟 总结
| 场景 | 使用频率 | 核心 Zookeeper 特性 |
|---|---|---|
| 分布式锁 | ⭐⭐⭐⭐⭐ | 临时顺序节点、Watcher |
| 配置中心 | ⭐⭐⭐⭐ | 持久节点、Watcher |
| 服务注册与发现 | ⭐⭐⭐⭐ | 临时节点、Watcher |
| 分布式选主 | ⭐⭐⭐ | 临时顺序节点 |
| 分布式队列 | ⭐⭐ | 持久顺序节点 |
最佳实践:
- 合理选择节点类型:临时节点用于存活检测,顺序节点保证公平性。
- 精简 Watcher 注册:避免过度监听导致性能下降。
- 异常处理:始终处理
KeeperException和网络中断。
Zookeeper 不是万能的:
- 高频写入场景(如日志存储)不适合 Zookeeper。
- 数据量过大(超过内存限制)需拆分使用。