Skip to content

ZooKeeper 应用场景


场景:多台服务器需要读取同一份配置
问题:修改配置需要同步到所有服务器
解决:ZooKeeper 集中式配置管理
ZooKeeper:
/config
├── /app1 # 应用 1
│ ├── database.url # 数据库地址
│ ├── database.user # 用户名
│ └── timeout # 超时时间
└── /app2 # 应用 2
└── thread.pool.size # 线程池大小
流程:
1. 配置存储在 ZooKeeper 节点
2. 客户端 Watch 监控节点变化
3. 配置变更时,ZooKeeper 通知所有客户端
4. 客户端获取最新配置
public class ZKConfigDemo {
private static final String ZK_SERVERS = "localhost:2181";
private static final String CONFIG_PATH = "/config/app/database";
// 读取配置
public String getConfig() throws Exception {
try (ZooKeeper zk = new ZooKeeper(ZK_SERVERS, 3000, event -> {})) {
byte[] data = zk.getData(CONFIG_PATH, true, null);
return new String(data);
}
}
// 更新配置
public void updateConfig(String config) throws Exception {
try (ZooKeeper zk = new ZooKeeper(ZK_SERVERS, 3000, event -> {})) {
Stat stat = zk.setData(CONFIG_PATH, config.getBytes(), -1);
}
}
}

场景:服务消费者需要找到服务提供者
问题:提供者可能随时变化(上线/下线/扩容)
解决:ZooKeeper 服务注册与发现
ZooKeeper 结构:
/services
├── /order-service
│ ├── /192.168.1.1:8080 # 服务提供者 1(临时节点)
│ └── /192.168.1.2:8080 # 服务提供者 2(临时节点)
└── /user-service
└── /192.168.1.3:8080
流程:
1. 服务提供者启动时,在 ZooKeeper 注册临时节点
2. 服务消费者从 ZooKeeper 获取提供者列表
3. 提供者故障时,临时节点自动删除
4. 消费者 Watch 监控提供者变化
public class ServiceRegistry {
private static final String REGISTRY_PATH = "/services";
private ZooKeeper zk;
// 注册服务
public void register(String serviceName, String address) throws Exception {
String path = REGISTRY_PATH + "/" + serviceName + "/" + address;
// 创建临时顺序节点
zk.create(path, address.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
}
// 发现服务
public List<String> discover(String serviceName) throws Exception {
String path = REGISTRY_PATH + "/" + serviceName;
List<String> children = zk.getChildren(path, true);
return children.stream()
.map(child -> {
try {
return new String(zk.getData(path + "/" + child, false, null));
} catch (Exception e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}

场景:多台服务器需要选出一个 Master 执行任务
问题:避免多个 Master 同时执行造成数据混乱
解决:ZooKeeper 选主机制
ZooKeeper 结构:
/master
├── lock # 锁节点(临时顺序节点)
└── master-00000001 # 当前 Master
选举流程:
1. 所有服务器尝试创建 /master/lock 节点
2. 创建成功者成为 Master
3. 其他服务器 Watch 监控 /master 节点
4. Master 故障时,节点删除,触发重新选举
public class MasterElection {
private static final String MASTER_PATH = "/master";
private ZooKeeper zk;
// 尝试成为 Master
public boolean tryBecomeMaster() {
try {
// 创建临时节点
zk.create(MASTER_PATH,
"master".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL);
return true; // 成为 Master
} catch (KeeperException.NodeExistsException e) {
return false; // 已存在 Master
}
}
// 监控 Master 变化
public void watchMaster(Watcher watcher) {
try {
Stat stat = zk.exists(MASTER_PATH, watcher);
if (stat == null) {
// Master 不存在,尝试选举
tryBecomeMaster();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

场景:多消费者按顺序消费任务
问题:如何保证 FIFO 顺序?
解决:ZooKeeper 顺序节点 + Watch
ZooKeeper 结构:
/queue
├── task-0000000001 # 任务 1
├── task-0000000002 # 任务 2
└── task-0000000003 # 任务 3
消费流程:
1. 获取队列最小序号的任务
2. 处理完成后删除节点
3. 继续获取下一个任务
public class DistributedQueue {
private static final String QUEUE_PATH = "/queue";
private ZooKeeper zk;
// 生产者:添加任务
public void offer(String task) throws Exception {
zk.create(QUEUE_PATH + "/task-",
task.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT_SEQUENTIAL);
}
// 消费者:获取任务
public String poll() throws Exception {
List<String> tasks = zk.getChildren(QUEUE_PATH, false);
if (tasks.isEmpty()) return null;
// 获取序号最小的任务
String minTask = tasks.stream()
.min(Comparator.naturalOrder())
.orElse(null);
if (minTask != null) {
String path = QUEUE_PATH + "/" + minTask;
byte[] data = zk.getData(path, false, null);
zk.delete(path, -1);
return new String(data);
}
return null;
}
}

场景:多个进程需要访问共享资源
问题:如何保证一次只有一个进程访问?
解决:ZooKeeper 临时顺序节点实现锁
ZooKeeper 结构:
/locks
├── lock-0000000001 # 获得锁的进程
├── lock-0000000002 # 等待中
└── lock-0000000003 # 等待中
锁原理:
1. 创建临时顺序节点
2. 判断是否为最小序号
3. 是 → 获得锁
4. 否 → Watch 前一个节点
5. 前一个节点删除 → 重新判断

场景节点类型Watch 用途典型框架
配置管理持久节点监听变更Apollo
服务发现临时节点监听上下线Dubbo
Master 选举临时节点监听 MasterHBase
分布式队列顺序节点监听新任务-
分布式锁临时顺序节点监听释放Curator

Q1: ZooKeeper 如何实现服务注册与发现?

Section titled “Q1: ZooKeeper 如何实现服务注册与发现?”

参考答案

  1. 服务提供者启动时创建临时节点
  2. 服务消费者获取服务节点列表
  3. 监听节点变化(上下线)
  4. 消费者根据负载均衡选择服务

Q2: 为什么 ZooKeeper适合做配置中心?

Section titled “Q2: 为什么 ZooKeeper适合做配置中心?”

参考答案

  1. 树形结构适合配置管理
  2. Watch 机制支持配置变更推送
  3. 临时节点支持服务动态上下线
  4. 高可用保证配置服务稳定性

参考答案

  1. 所有 Follower 尝试创建临时节点
  2. 创建成功者成为 Leader
  3. 其他节点 Watch Leader 节点
  4. Leader 故障后,节点删除触发重新选举