ZooKeeper 应用场景
ZooKeeper 应用场景
Section titled “ZooKeeper 应用场景”1. 配置管理
Section titled “1. 配置管理”场景:多台服务器需要读取同一份配置问题:修改配置需要同步到所有服务器解决: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); } }}2. 服务注册与发现
Section titled “2. 服务注册与发现”场景:服务消费者需要找到服务提供者问题:提供者可能随时变化(上线/下线/扩容)解决: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()); }}3. Master 选举
Section titled “3. Master 选举”场景:多台服务器需要选出一个 Master 执行任务问题:避免多个 Master 同时执行造成数据混乱解决:ZooKeeper 选主机制ZooKeeper 结构: /master ├── lock # 锁节点(临时顺序节点) └── master-00000001 # 当前 Master
选举流程:1. 所有服务器尝试创建 /master/lock 节点2. 创建成功者成为 Master3. 其他服务器 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(); } }}4. 分布式队列
Section titled “4. 分布式队列”场景:多消费者按顺序消费任务问题:如何保证 FIFO 顺序?解决:ZooKeeper 顺序节点 + WatchZooKeeper 结构: /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; }}5. 分布式锁
Section titled “5. 分布式锁”场景:多个进程需要访问共享资源问题:如何保证一次只有一个进程访问?解决:ZooKeeper 临时顺序节点实现锁实现原理(详见下篇)
Section titled “实现原理(详见下篇)”ZooKeeper 结构: /locks ├── lock-0000000001 # 获得锁的进程 ├── lock-0000000002 # 等待中 └── lock-0000000003 # 等待中
锁原理:1. 创建临时顺序节点2. 判断是否为最小序号3. 是 → 获得锁4. 否 → Watch 前一个节点5. 前一个节点删除 → 重新判断应用场景对比
Section titled “应用场景对比”| 场景 | 节点类型 | Watch 用途 | 典型框架 |
|---|---|---|---|
| 配置管理 | 持久节点 | 监听变更 | Apollo |
| 服务发现 | 临时节点 | 监听上下线 | Dubbo |
| Master 选举 | 临时节点 | 监听 Master | HBase |
| 分布式队列 | 顺序节点 | 监听新任务 | - |
| 分布式锁 | 临时顺序节点 | 监听释放 | Curator |
面试高频问题
Section titled “面试高频问题”Q1: ZooKeeper 如何实现服务注册与发现?
Section titled “Q1: ZooKeeper 如何实现服务注册与发现?”参考答案:
- 服务提供者启动时创建临时节点
- 服务消费者获取服务节点列表
- 监听节点变化(上下线)
- 消费者根据负载均衡选择服务
Q2: 为什么 ZooKeeper适合做配置中心?
Section titled “Q2: 为什么 ZooKeeper适合做配置中心?”参考答案:
- 树形结构适合配置管理
- Watch 机制支持配置变更推送
- 临时节点支持服务动态上下线
- 高可用保证配置服务稳定性
Q3: ZooKeeper 选主的流程是什么?
Section titled “Q3: ZooKeeper 选主的流程是什么?”参考答案:
- 所有 Follower 尝试创建临时节点
- 创建成功者成为 Leader
- 其他节点 Watch Leader 节点
- Leader 故障后,节点删除触发重新选举