Zookeeper 实战应用案例解析:从高频到低频,一网打尽!

378 阅读4分钟

前言

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。
  • 数据量过大(超过内存限制)需拆分使用。