ZooKeeper 五种经典应用场景详解

ZooKeeper 五种经典应用场景详解

目录

  1. 分布式锁(Distributed Lock)
  2. 配置管理(Configuration Management)
  3. 服务注册与发现(Service Registration and Discovery)
  4. Leader选举(Leader Election)
  5. 分布式队列(Distributed Queue)

1. 分布式锁

简介

分布式锁是 ZooKeeper 最经典的应用场景之一,用于在分布式系统中实现互斥访问共享资源。通过 ZooKeeper 的临时顺序节点(Ephemeral Sequential Node)特性,可以实现公平的分布式锁机制。

架构图

锁节点结构
应用集群
ZooKeeper集群
创建临时顺序节点
创建临时顺序节点
创建临时顺序节点
监听前一个节点
监听前一个节点
lock根节点
lock-0000000001
lock-0000000002
lock-0000000003
应用实例1
应用实例2
应用实例3
ZooKeeper Node 1
ZooKeeper Node 2
ZooKeeper Node 3

示例代码

Java 实现
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.util.concurrent.TimeUnit;

public class DistributedLockExample {
    private static final String ZK_ADDRESS = "localhost:2181";
    private static final String LOCK_PATH = "/distributed-lock";
  
    private CuratorFramework client;
    private InterProcessMutex lock;
  
    public DistributedLockExample() {
        // 创建 Curator 客户端
        client = CuratorFrameworkFactory.builder()
                .connectString(ZK_ADDRESS)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        client.start();
      
        // 创建分布式锁
        lock = new InterProcessMutex(client, LOCK_PATH);
    }
  
    /**
     * 获取锁并执行业务逻辑
     */
    public void doWorkWithLock() {
        try {
            // 尝试获取锁,最多等待10秒
            if (lock.acquire(10, TimeUnit.SECONDS)) {
                try {
                    System.out.println(Thread.currentThread().getName() + " 获取到锁");
                    // 执行业务逻辑
                    doBusinessLogic();
                } finally {
                    // 释放锁
                    lock.release();
                    System.out.println(Thread.currentThread().getName() + " 释放锁");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  
    private void doBusinessLogic() throws InterruptedException {
        // 模拟业务处理
        Thread.sleep(2000);
        System.out.println("执行业务逻辑...");
    }
  
    public void close() {
        if (client != null) {
            client.close();
        }
    }
  
    public static void main(String[] args) {
        DistributedLockExample example = new DistributedLockExample();
      
        // 模拟多个线程竞争锁
        for (int i = 0; i < 5; i++) {
            final int threadId = i;
            new Thread(() -> {
                example.doWorkWithLock();
            }, "Thread-" + threadId).start();
        }
      
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
      
        example.close();
    }
}
原生 ZooKeeper API 实现
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class NativeDistributedLock {
    private static final String ZK_ADDRESS = "localhost:2181";
    private static final String LOCK_ROOT = "/locks";
    private static final String LOCK_PREFIX = "lock-";
  
    private ZooKeeper zk;
    private String lockPath;
    private String currentLockPath;
  
    public NativeDistributedLock() throws IOException, InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        zk = new ZooKeeper(ZK_ADDRESS, 3000, event -> {
            if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
                latch.countDown();
            }
        });
        latch.await();
      
        // 确保根节点存在
        try {
            if (zk.exists(LOCK_ROOT, false) == null) {
                zk.create(LOCK_ROOT, new byte[0], 
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (KeeperException e) {
            // 节点已存在,忽略
        }
    }
  
    /**
     * 获取锁
     */
    public boolean tryLock() throws KeeperException, InterruptedException {
        // 创建临时顺序节点
        currentLockPath = zk.create(LOCK_ROOT + "/" + LOCK_PREFIX, 
            new byte[0], 
            ZooDefs.Ids.OPEN_ACL_UNSAFE, 
            CreateMode.EPHEMERAL_SEQUENTIAL);
      
        // 获取所有锁节点并排序
        List<String> children = zk.getChildren(LOCK_ROOT, false);
        Collections.sort(children);
      
        // 判断当前节点是否是最小的节点
        String currentNode = currentLockPath.substring(LOCK_ROOT.length() + 1);
        int index = children.indexOf(currentNode);
      
        if (index == 0) {
            // 当前节点是最小的,获取锁成功
            return true;
        } else {
            // 监听前一个节点
            String previousNode = LOCK_ROOT + "/" + children.get(index - 1);
            final CountDownLatch latch = new CountDownLatch(1);
          
            Stat stat = zk.exists(previousNode, event -> {
                if (event.getType() == Watcher.Event.EventType.NodeDeleted) {
                    latch.countDown();
                }
            });
          
            if (stat == null) {
                // 前一个节点已不存在,重新尝试获取锁
                return tryLock();
            }
          
            // 等待前一个节点被删除
            latch.await();
            return tryLock();
        }
    }
  
    /**
     * 释放锁
     */
    public void unlock() throws KeeperException, InterruptedException {
        if (currentLockPath != null) {
            zk.delete(currentLockPath, -1);
            currentLockPath = null;
        }
    }
  
    public void close() throws InterruptedException {
        if (zk != null) {
            zk.close();
        }
    }
}

重难点分析

重点:

  1. 临时顺序节点:使用 EPHEMERAL_SEQUENTIAL 模式创建节点,确保节点按顺序创建且会话断开时自动删除
  2. 公平锁机制:通过节点序号实现公平锁,序号最小的节点获得锁
  3. 监听机制:通过监听前一个节点的删除事件,实现锁的自动获取

难点:

  1. 惊群效应:多个客户端同时监听同一个节点,当节点删除时所有客户端都会被唤醒。解决方案是只监听前一个节点
  2. 会话超时:客户端会话超时会导致临时节点被删除,可能影响锁的正确性。需要实现会话重连机制
  3. 死锁检测:需要处理客户端异常退出导致的锁无法释放问题(临时节点会自动删除,但需要确保会话正常关闭)

2. 配置管理

简介

配置管理是 ZooKeeper 的另一个重要应用场景,用于集中管理分布式系统的配置信息。当配置发生变化时,所有订阅该配置的客户端都能实时收到通知并更新本地配置。

架构图

应用集群
配置管理服务
ZooKeeper集群
写入配置
监听配置变化
监听配置变化
监听配置变化
配置变更通知
配置变更通知
配置变更通知
应用实例1
应用实例2
应用实例3
配置管理服务
ZooKeeper集群
config/app-config节点

示例代码

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.nio.charset.StandardCharsets;

public class ConfigManager {
    private static final String ZK_ADDRESS = "localhost:2181";
    private static final String CONFIG_PATH = "/config/app-config";
  
    private CuratorFramework client;
    private NodeCache nodeCache;
    private String currentConfig;
  
    public ConfigManager() {
        client = CuratorFrameworkFactory.builder()
                .connectString(ZK_ADDRESS)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        client.start();
      
        // 创建节点缓存监听器
        nodeCache = new NodeCache(client, CONFIG_PATH);
        nodeCache.getListenable().addListener(new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {
                updateConfig();
            }
        });
      
        try {
            nodeCache.start(true);
            updateConfig();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  
    /**
     * 更新配置
     */
    private void updateConfig() {
        try {
            byte[] data = nodeCache.getCurrentData().getData();
            currentConfig = new String(data, StandardCharsets.UTF_8);
            System.out.println("配置已更新: " + currentConfig);
            // 应用新配置
            applyConfig(currentConfig);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  
    /**
     * 应用配置到业务逻辑
     */
    private void applyConfig(String config) {
        // 解析配置并应用到业务逻辑
        System.out.println("应用配置: " + config);
        // 例如:更新数据库连接池大小、线程池参数等
    }
  
    /**
     * 获取当前配置
     */
    public String getCurrentConfig() {
        return currentConfig;
    }
  
    /**
     * 更新配置(管理员操作)
     */
    public void updateConfig(String newConfig) throws Exception {
        client.setData().forPath(CONFIG_PATH, newConfig.getBytes(StandardCharsets.UTF_8));
    }
  
    /**
     * 初始化配置
     */
    public void initConfig(String initialConfig) throws Exception {
        try {
            client.create().creatingParentsIfNeeded()
                    .forPath(CONFIG_PATH, initialConfig.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            // 节点已存在,直接更新
            updateConfig(initialConfig);
        }
    }
  
    public void close() {
        if (nodeCache != null) {
            try {
                nodeCache.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (client != null) {
            client.close();
        }
    }
  
    public static void main(String[] args) throws Exception {
        ConfigManager configManager = new ConfigManager();
      
        // 初始化配置
        configManager.initConfig("{\"threadPoolSize\": 10, \"dbPoolSize\": 20}");
      
        // 模拟配置更新
        Thread.sleep(5000);
        configManager.updateConfig("{\"threadPoolSize\": 20, \"dbPoolSize\": 30}");
      
        Thread.sleep(10000);
        configManager.close();
    }
}

重难点分析

重点:

  1. 实时通知:使用 NodeCachePathChildrenCache 监听配置节点变化
  2. 配置版本管理:通过节点版本号实现配置的版本控制
  3. 配置回滚:保存历史配置,支持配置回滚

难点:

  1. 配置一致性:确保所有节点在同一时刻看到相同的配置。需要处理网络分区和节点故障的情况
  2. 配置冲突:多个管理员同时修改配置时的冲突处理。可以使用乐观锁(版本号)机制
  3. 配置格式:需要定义统一的配置格式(JSON、XML、Properties等),并处理格式错误的情况

3. 服务注册与发现

简介

服务注册与发现是微服务架构中的核心组件。服务提供者将自己的服务信息注册到 ZooKeeper,服务消费者从 ZooKeeper 获取可用的服务列表,实现服务的动态发现和负载均衡。

架构图

服务消费者
服务提供者
ZooKeeper集群
注册服务
注册服务
注册服务
发现服务
发现服务
调用
调用
调用
消费者1
消费者2
用户服务实例1
用户服务实例2
订单服务实例1
ZooKeeper集群
services根节点
user-service
order-service
user-service-provider-1
user-service-provider-2
order-service-provider-1

示例代码

服务注册
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class ServiceRegistry {
    private static final String ZK_ADDRESS = "localhost:2181";
    private static final String SERVICES_ROOT = "/services";
  
    private CuratorFramework client;
    private String serviceName;
    private String servicePath;
    private String currentProviderPath;
  
    public ServiceRegistry(String serviceName) {
        this.serviceName = serviceName;
        this.servicePath = SERVICES_ROOT + "/" + serviceName;
      
        client = CuratorFrameworkFactory.builder()
                .connectString(ZK_ADDRESS)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        client.start();
    }
  
    /**
     * 注册服务
     */
    public void register(String host, int port) throws Exception {
        // 确保服务根节点存在
        try {
            if (client.checkExists().forPath(SERVICES_ROOT) == null) {
                client.create().creatingParentsIfNeeded()
                        .forPath(SERVICES_ROOT);
            }
        } catch (Exception e) {
            // 节点已存在
        }
      
        // 创建服务提供者节点(临时节点)
        String providerInfo = host + ":" + port;
        currentProviderPath = client.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
                .forPath(servicePath + "/provider-", 
                        providerInfo.getBytes(StandardCharsets.UTF_8));
      
        System.out.println("服务注册成功: " + currentProviderPath + " -> " + providerInfo);
    }
  
    /**
     * 注销服务
     */
    public void unregister() {
        if (currentProviderPath != null) {
            try {
                client.delete().forPath(currentProviderPath);
                System.out.println("服务注销成功: " + currentProviderPath);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
  
    public void close() {
        unregister();
        if (client != null) {
            client.close();
        }
    }
}
服务发现
public class ServiceDiscovery {
    private static final String ZK_ADDRESS = "localhost:2181";
    private static final String SERVICES_ROOT = "/services";
  
    private CuratorFramework client;
    private String serviceName;
    private String servicePath;
    private PathChildrenCache pathChildrenCache;
    private List<String> serviceProviders;
  
    public ServiceDiscovery(String serviceName) {
        this.serviceName = serviceName;
        this.servicePath = SERVICES_ROOT + "/" + serviceName;
        this.serviceProviders = new ArrayList<>();
      
        client = CuratorFrameworkFactory.builder()
                .connectString(ZK_ADDRESS)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        client.start();
      
        // 监听服务提供者列表变化
        pathChildrenCache = new PathChildrenCache(client, servicePath, true);
        pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) 
                    throws Exception {
                updateServiceProviders();
            }
        });
      
        try {
            pathChildrenCache.start();
            updateServiceProviders();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  
    /**
     * 更新服务提供者列表
     */
    private void updateServiceProviders() {
        try {
            List<String> providers = new ArrayList<>();
            List<String> children = client.getChildren().forPath(servicePath);
          
            for (String child : children) {
                String childPath = servicePath + "/" + child;
                byte[] data = client.getData().forPath(childPath);
                String providerInfo = new String(data, StandardCharsets.UTF_8);
                providers.add(providerInfo);
            }
          
            synchronized (this) {
                serviceProviders = providers;
            }
          
            System.out.println("服务列表已更新: " + serviceProviders);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  
    /**
     * 获取服务提供者列表
     */
    public synchronized List<String> getServiceProviders() {
        return new ArrayList<>(serviceProviders);
    }
  
    /**
     * 负载均衡:随机选择一个服务提供者
     */
    public String getRandomProvider() {
        List<String> providers = getServiceProviders();
        if (providers.isEmpty()) {
            return null;
        }
        int index = (int) (Math.random() * providers.size());
        return providers.get(index);
    }
  
    /**
     * 负载均衡:轮询选择服务提供者
     */
    private int roundRobinIndex = 0;
    public String getRoundRobinProvider() {
        List<String> providers = getServiceProviders();
        if (providers.isEmpty()) {
            return null;
        }
        String provider = providers.get(roundRobinIndex % providers.size());
        roundRobinIndex++;
        return provider;
    }
  
    public void close() {
        if (pathChildrenCache != null) {
            try {
                pathChildrenCache.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (client != null) {
            client.close();
        }
    }
}

重难点分析

重点:

  1. 临时节点:使用临时节点存储服务提供者信息,服务下线时自动删除
  2. 动态监听:使用 PathChildrenCache 监听服务列表变化,实现服务的动态发现
  3. 负载均衡:实现多种负载均衡策略(随机、轮询、一致性哈希等)

难点:

  1. 服务健康检查:ZooKeeper 只能检测到会话断开,无法检测服务是否真正可用。需要结合心跳机制或健康检查接口
  2. 服务雪崩:当大量服务同时注册或注销时,可能触发大量事件。需要实现事件合并和防抖机制
  3. 网络分区:网络分区可能导致服务注册信息不一致。需要实现分区恢复后的数据同步机制

4. Leader选举

简介

Leader 选举是分布式系统中的经典问题,用于在多个节点中选出一个主节点来协调工作。ZooKeeper 通过临时顺序节点和最小序号机制,可以优雅地实现 Leader 选举。

架构图

应用节点集群
ZooKeeper集群
创建临时顺序节点
创建临时顺序节点
创建临时顺序节点
Leader
Follower
Follower
监听前一个节点
监听前一个节点
应用节点1
应用节点2
应用节点3
ZooKeeper集群
election根节点
node-0000000001
node-0000000002
node-0000000003

示例代码

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.util.concurrent.TimeUnit;

public class LeaderElectionExample {
    private static final String ZK_ADDRESS = "localhost:2181";
    private static final String ELECTION_PATH = "/election";
  
    private CuratorFramework client;
    private LeaderLatch leaderLatch;
    private String nodeId;
  
    public LeaderElectionExample(String nodeId) {
        this.nodeId = nodeId;
      
        client = CuratorFrameworkFactory.builder()
                .connectString(ZK_ADDRESS)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        client.start();
      
        leaderLatch = new LeaderLatch(client, ELECTION_PATH, nodeId);
        leaderLatch.addListener(new LeaderLatchListener() {
            @Override
            public void isLeader() {
                System.out.println("节点 " + nodeId + " 成为 Leader");
                startLeaderWork();
            }
          
            @Override
            public void notLeader() {
                System.out.println("节点 " + nodeId + " 不再是 Leader");
                stopLeaderWork();
            }
        });
    }
  
    /**
     * 参与选举
     */
    public void start() throws Exception {
        leaderLatch.start();
    }
  
    /**
     * 检查是否是 Leader
     */
    public boolean isLeader() {
        return leaderLatch.hasLeadership();
    }
  
    /**
     * 等待成为 Leader(带超时)
     */
    public boolean awaitLeadership(long timeout, TimeUnit unit) throws InterruptedException {
        return leaderLatch.await(timeout, unit);
    }
  
    /**
     * Leader 的工作
     */
    private void startLeaderWork() {
        System.out.println("节点 " + nodeId + " 开始执行 Leader 工作");
        // 例如:定时任务调度、数据同步等
    }
  
    /**
     * 停止 Leader 工作
     */
    private void stopLeaderWork() {
        System.out.println("节点 " + nodeId + " 停止 Leader 工作");
    }
  
    /**
     * 退出选举
     */
    public void close() throws Exception {
        if (leaderLatch != null) {
            leaderLatch.close();
        }
        if (client != null) {
            client.close();
        }
    }
  
    public static void main(String[] args) throws Exception {
        // 创建多个节点参与选举
        LeaderElectionExample node1 = new LeaderElectionExample("node-1");
        LeaderElectionExample node2 = new LeaderElectionExample("node-2");
        LeaderElectionExample node3 = new LeaderElectionExample("node-3");
      
        node1.start();
        node2.start();
        node3.start();
      
        // 等待选举完成
        Thread.sleep(2000);
      
        // 检查 Leader
        if (node1.isLeader()) {
            System.out.println("Node-1 是 Leader");
        }
        if (node2.isLeader()) {
            System.out.println("Node-2 是 Leader");
        }
        if (node3.isLeader()) {
            System.out.println("Node-3 是 Leader");
        }
      
        // 模拟 Leader 节点退出
        Thread.sleep(5000);
        if (node1.isLeader()) {
            node1.close();
            System.out.println("Leader 节点退出,重新选举...");
        }
      
        Thread.sleep(2000);
      
        // 清理
        node1.close();
        node2.close();
        node3.close();
    }
}
使用原生 API 实现 Leader 选举
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class NativeLeaderElection implements Watcher {
    private static final String ZK_ADDRESS = "localhost:2181";
    private static final String ELECTION_ROOT = "/election";
    private static final String NODE_PREFIX = "node-";
  
    private ZooKeeper zk;
    private String currentNodePath;
    private String watchedNodePath;
    private boolean isLeader = false;
    private LeaderElectionListener listener;
  
    public interface LeaderElectionListener {
        void onElected();
        void onRevoked();
    }
  
    public NativeLeaderElection(LeaderElectionListener listener) 
            throws IOException, InterruptedException {
        this.listener = listener;
      
        CountDownLatch latch = new CountDownLatch(1);
        zk = new ZooKeeper(ZK_ADDRESS, 3000, event -> {
            if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
                latch.countDown();
            }
        });
        latch.await();
      
        // 确保根节点存在
        try {
            if (zk.exists(ELECTION_ROOT, false) == null) {
                zk.create(ELECTION_ROOT, new byte[0], 
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (KeeperException e) {
            // 节点已存在
        }
    }
  
    /**
     * 参与选举
     */
    public void participate() throws KeeperException, InterruptedException {
        // 创建临时顺序节点
        currentNodePath = zk.create(ELECTION_ROOT + "/" + NODE_PREFIX, 
            new byte[0], 
            ZooDefs.Ids.OPEN_ACL_UNSAFE, 
            CreateMode.EPHEMERAL_SEQUENTIAL);
      
        checkAndBecomeLeader();
    }
  
    /**
     * 检查并成为 Leader
     */
    private void checkAndBecomeLeader() throws KeeperException, InterruptedException {
        List<String> children = zk.getChildren(ELECTION_ROOT, false);
        Collections.sort(children);
      
        String currentNode = currentNodePath.substring(ELECTION_ROOT.length() + 1);
        int index = children.indexOf(currentNode);
      
        if (index == 0) {
            // 当前节点是最小的,成为 Leader
            if (!isLeader) {
                isLeader = true;
                System.out.println("成为 Leader: " + currentNodePath);
                if (listener != null) {
                    listener.onElected();
                }
            }
        } else {
            // 不是 Leader,监听前一个节点
            if (isLeader) {
                isLeader = false;
                System.out.println("不再是 Leader: " + currentNodePath);
                if (listener != null) {
                    listener.onRevoked();
                }
            }
          
            String previousNode = ELECTION_ROOT + "/" + children.get(index - 1);
            watchNode(previousNode);
        }
    }
  
    /**
     * 监听节点
     */
    private void watchNode(String nodePath) throws KeeperException, InterruptedException {
        watchedNodePath = nodePath;
        Stat stat = zk.exists(nodePath, this);
        if (stat == null) {
            // 前一个节点已不存在,重新检查
            checkAndBecomeLeader();
        }
    }
  
    @Override
    public void process(WatchedEvent event) {
        if (event.getType() == Event.EventType.NodeDeleted) {
            if (event.getPath().equals(watchedNodePath)) {
                try {
                    checkAndBecomeLeader();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
  
    /**
     * 检查是否是 Leader
     */
    public boolean isLeader() {
        return isLeader;
    }
  
    public void close() throws InterruptedException {
        if (zk != null) {
            zk.close();
        }
    }
}

重难点分析

重点:

  1. 最小序号机制:序号最小的节点成为 Leader,简单高效
  2. 自动故障转移:Leader 节点故障时,下一个节点自动成为新的 Leader
  3. 临时节点:使用临时节点确保节点故障时自动从选举中移除

难点:

  1. 脑裂问题:网络分区可能导致多个 Leader 同时存在。需要结合其他机制(如 fencing token)来防止脑裂
  2. 选举风暴:大量节点同时参与选举时可能产生大量事件。需要优化监听机制
  3. Leader 切换开销:Leader 切换时可能需要重新加载状态、重建连接等,需要最小化切换开销

5. 分布式队列

简介

分布式队列用于在分布式系统中实现任务队列,支持多生产者多消费者模式。ZooKeeper 可以通过顺序节点和监听机制实现分布式队列。

架构图

消费者
生产者
ZooKeeper集群
生产任务
生产任务
消费任务
消费任务
被消费
被消费
消费者1
消费者2
生产者1
生产者2
ZooKeeper集群
queue根节点
item-0000000001
item-0000000002
item-0000000003

示例代码

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.queue.DistributedQueue;
import org.apache.curator.framework.recipes.queue.QueueBuilder;
import org.apache.curator.framework.recipes.queue.QueueConsumer;
import org.apache.curator.framework.recipes.queue.QueueSerializer;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.nio.charset.StandardCharsets;

public class DistributedQueueExample {
    private static final String ZK_ADDRESS = "localhost:2181";
    private static final String QUEUE_PATH = "/distributed-queue";
  
    /**
     * 队列序列化器
     */
    public static class StringQueueSerializer implements QueueSerializer<String> {
        @Override
        public byte[] serialize(String item) {
            return item.getBytes(StandardCharsets.UTF_8);
        }
      
        @Override
        public String deserialize(byte[] bytes) {
            return new String(bytes, StandardCharsets.UTF_8);
        }
    }
  
    /**
     * 队列消费者
     */
    public static class StringQueueConsumer implements QueueConsumer<String> {
        private String consumerName;
      
        public StringQueueConsumer(String consumerName) {
            this.consumerName = consumerName;
        }
      
        @Override
        public void consumeMessage(String message) throws Exception {
            System.out.println(consumerName + " 消费消息: " + message);
            // 处理消息
            processMessage(message);
        }
      
        @Override
        public void stateChanged(CuratorFramework client, ConnectionState newState) {
            System.out.println(consumerName + " 连接状态变化: " + newState);
        }
      
        private void processMessage(String message) {
            // 模拟消息处理
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
  
    /**
     * 生产者
     */
    public static class QueueProducer {
        private CuratorFramework client;
        private DistributedQueue<String> queue;
      
        public QueueProducer() {
            client = CuratorFrameworkFactory.builder()
                    .connectString(ZK_ADDRESS)
                    .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                    .build();
            client.start();
          
            queue = QueueBuilder.builder(client, 
                    new StringQueueConsumer("Producer"), 
                    new StringQueueSerializer(), 
                    QUEUE_PATH)
                    .buildQueue();
          
            try {
                queue.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
      
        public void put(String message) throws Exception {
            queue.put(message);
            System.out.println("生产消息: " + message);
        }
      
        public void close() throws Exception {
            if (queue != null) {
                queue.close();
            }
            if (client != null) {
                client.close();
            }
        }
    }
  
    /**
     * 消费者
     */
    public static class QueueConsumer {
        private CuratorFramework client;
        private DistributedQueue<String> queue;
        private StringQueueConsumer consumer;
      
        public QueueConsumer(String consumerName) {
            consumer = new StringQueueConsumer(consumerName);
          
            client = CuratorFrameworkFactory.builder()
                    .connectString(ZK_ADDRESS)
                    .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                    .build();
            client.start();
          
            queue = QueueBuilder.builder(client, 
                    consumer, 
                    new StringQueueSerializer(), 
                    QUEUE_PATH)
                    .buildQueue();
          
            try {
                queue.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
      
        public void close() throws Exception {
            if (queue != null) {
                queue.close();
            }
            if (client != null) {
                client.close();
            }
        }
    }
  
    public static void main(String[] args) throws Exception {
        // 创建生产者
        QueueProducer producer = new QueueProducer();
      
        // 创建多个消费者
        QueueConsumer consumer1 = new QueueConsumer("Consumer-1");
        QueueConsumer consumer2 = new QueueConsumer("Consumer-2");
        QueueConsumer consumer3 = new QueueConsumer("Consumer-3");
      
        // 生产消息
        for (int i = 0; i < 10; i++) {
            producer.put("Message-" + i);
            Thread.sleep(500);
        }
      
        // 等待消费完成
        Thread.sleep(20000);
      
        // 清理
        producer.close();
        consumer1.close();
        consumer2.close();
        consumer3.close();
    }
}
使用原生 API 实现简单队列
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class SimpleDistributedQueue {
    private static final String ZK_ADDRESS = "localhost:2181";
    private static final String QUEUE_ROOT = "/queue";
    private static final String ITEM_PREFIX = "item-";
  
    private ZooKeeper zk;
  
    public SimpleDistributedQueue() throws IOException, InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        zk = new ZooKeeper(ZK_ADDRESS, 3000, event -> {
            if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
                latch.countDown();
            }
        });
        latch.await();
      
        // 确保根节点存在
        try {
            if (zk.exists(QUEUE_ROOT, false) == null) {
                zk.create(QUEUE_ROOT, new byte[0], 
                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (KeeperException e) {
            // 节点已存在
        }
    }
  
    /**
     * 入队
     */
    public void offer(byte[] data) throws KeeperException, InterruptedException {
        zk.create(QUEUE_ROOT + "/" + ITEM_PREFIX, 
            data, 
            ZooDefs.Ids.OPEN_ACL_UNSAFE, 
            CreateMode.PERSISTENT_SEQUENTIAL);
    }
  
    /**
     * 出队(阻塞)
     */
    public byte[] take() throws KeeperException, InterruptedException {
        while (true) {
            List<String> children = zk.getChildren(QUEUE_ROOT, false);
            if (children.isEmpty()) {
                // 队列为空,等待
                final CountDownLatch latch = new CountDownLatch(1);
                zk.getChildren(QUEUE_ROOT, event -> {
                    if (event.getType() == Watcher.Event.EventType.NodeChildrenChanged) {
                        latch.countDown();
                    }
                });
                latch.await();
                continue;
            }
          
            // 排序并获取最小的节点
            Collections.sort(children);
            String firstChild = children.get(0);
            String firstChildPath = QUEUE_ROOT + "/" + firstChild;
          
            try {
                byte[] data = zk.getData(firstChildPath, false, null);
                zk.delete(firstChildPath, -1);
                return data;
            } catch (KeeperException.NoNodeException e) {
                // 节点已被其他消费者删除,重试
                continue;
            }
        }
    }
  
    /**
     * 出队(非阻塞)
     */
    public byte[] poll() throws KeeperException, InterruptedException {
        List<String> children = zk.getChildren(QUEUE_ROOT, false);
        if (children.isEmpty()) {
            return null;
        }
      
        Collections.sort(children);
        String firstChild = children.get(0);
        String firstChildPath = QUEUE_ROOT + "/" + firstChild;
      
        try {
            byte[] data = zk.getData(firstChildPath, false, null);
            zk.delete(firstChildPath, -1);
            return data;
        } catch (KeeperException.NoNodeException e) {
            return null;
        }
    }
  
    /**
     * 获取队列大小
     */
    public int size() throws KeeperException, InterruptedException {
        return zk.getChildren(QUEUE_ROOT, false).size();
    }
  
    public void close() throws InterruptedException {
        if (zk != null) {
            zk.close();
        }
    }
}

重难点分析

重点:

  1. FIFO 保证:通过顺序节点和排序机制保证先进先出
  2. 并发安全:多个消费者同时消费时的并发控制
  3. 阻塞消费:队列为空时消费者阻塞等待

难点:

  1. 重复消费:多个消费者可能同时获取到同一个消息。需要使用原子操作(如 getData + delete 的原子性)或分布式锁
  2. 消息丢失:消费者获取消息后但在处理前崩溃,可能导致消息丢失。可以使用临时节点或事务机制
  3. 性能优化:大量消息时的性能问题。可以考虑批量操作、异步处理等优化手段

总结

ZooKeeper 作为分布式协调服务,在上述五种场景中都发挥了重要作用:

  1. 分布式锁:解决分布式环境下的互斥访问问题
  2. 配置管理:实现配置的集中管理和动态更新
  3. 服务注册与发现:支持微服务架构中的服务治理
  4. Leader 选举:实现分布式系统中的主从切换
  5. 分布式队列:提供分布式环境下的任务队列功能

通用最佳实践

  1. 使用 Curator 框架:Curator 提供了更高级的抽象,简化了 ZooKeeper 的使用
  2. 处理连接断开:实现重连机制和状态恢复
  3. 监控和日志:记录关键操作,便于问题排查
  4. 性能优化:合理使用缓存、批量操作等优化手段
  5. 错误处理:妥善处理各种异常情况,确保系统稳定性

注意事项

  • ZooKeeper 不适合存储大量数据,主要用于协调和元数据管理
  • 注意会话超时设置,避免因网络抖动导致节点被误删
  • 合理设置监听器,避免监听风暴
  • 考虑 ZooKeeper 的性能限制,对于高并发场景需要结合其他技术
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值