第一章:ZooKeeper 概述与核心概念

1.1 什么是 ZooKeeper?

Apache ZooKeeper 是一个分布式协调服务,为分布式应用提供高性能的配置维护、命名服务、分布式同步和组服务。它最初是 Hadoop 的子项目,现在已成为许多分布式系统的核心依赖。

1.2 ZooKeeper 的设计目标

核心设计原则

  • 简单性:提供简单的接口和原语
  • 可靠性:集群中大部分节点存活即可正常工作
  • 有序性:所有更新操作都有全局顺序
  • 速度:读操作特别快,适合读多写少的场景

一致性保证

ZooKeeper 提供以下一致性保证:

  • 顺序一致性:客户端更新按顺序应用
  • 原子性:更新操作要么成功要么失败
  • 单一系统映像:客户端看到相同的服务视图
  • 可靠性:一旦更新完成,结果将持久化
  • 及时性:客户端视图在一定时间内保证最新

1.3 ZooKeeper 的核心架构

数据模型:ZNode

ZooKeeper 的数据模型类似于文件系统,采用层次化的命名空间:

/ (根节点)
├── /zookeeper        # 系统保留节点
├── /kafka           # Kafka 使用的节点
│   ├── /brokers     # Broker 注册信息
│   ├── /config      # 配置信息
│   └── /consumers   # 消费者组信息
├── /hbase           # HBase 使用的节点
└── /myapp           # 自定义应用节点
    ├── /locks       # 分布式锁
    ├── /leaders     # 领导者选举
    └── /config      # 配置数据

ZNode 类型

  • 持久节点:客户端断开连接后仍然存在
  • 临时节点:客户端会话结束自动删除
  • 顺序节点:名称自动附加单调递增序号
  • 容器节点:当没有子节点时自动删除(3.5+)
  • TTL 节点:设置生存时间(3.5+)

第二章:ZooKeeper 详细功能解析

2.1 配置管理

动态配置共享

// 应用启动时读取配置
String configPath = "/myapp/config";
byte[] configData = zk.getData(configPath, true, null);
String config = new String(configData);

// 监听配置变化
Watcher configWatcher = new Watcher() {
    public void process(WatchedEvent event) {
        if (event.getType() == EventType.NodeDataChanged) {
            // 重新加载配置
            byte[] newData = zk.getData(configPath, true, null);
            updateConfig(new String(newData));
        }
    }
};

配置管理优势

  • 集中管理:所有配置集中存储
  • 实时更新:配置变更立即生效
  • 版本控制:支持配置版本回溯
  • 权限控制:精细的访问权限管理

2.2 命名服务

服务注册发现

// 服务注册
String servicePath = "/services/my-service";
String instancePath = zk.create(servicePath + "/instance-", 
    "192.168.1.100:8080".getBytes(),
    ZooDefs.Ids.OPEN_ACL_UNSAFE,
    CreateMode.EPHEMERAL_SEQUENTIAL);

// 服务发现
List<String> instances = zk.getChildren(servicePath, true);
for (String instance : instances) {
    byte[] data = zk.getData(servicePath + "/" + instance, false, null);
    String endpoint = new String(data);
    // 添加到可用服务列表
}

2.3 分布式锁

排他锁实现

public class DistributedLock {
    private ZooKeeper zk;
    private String lockPath;
    private String currentLock;

    public boolean tryLock() throws Exception {
        // 创建临时顺序节点
        currentLock = zk.create(lockPath + "/lock-", 
            null, ZooDefs.Ids.OPEN_ACL_UNSAFE, 
            CreateMode.EPHEMERAL_SEQUENTIAL);

        // 获取所有锁节点并排序
        List<String> locks = zk.getChildren(lockPath, false);
        Collections.sort(locks);

        // 检查是否获得锁(是否是最小序号)
        String smallestLock = lockPath + "/" + locks.get(0);
        return currentLock.equals(smallestLock);
    }

    public void unlock() throws Exception {
        zk.delete(currentLock, -1);
    }
}

共享锁实现

public class ReadWriteLock {
    private static final String READ_PREFIX = "read-";
    private static final String WRITE_PREFIX = "write-";

    public boolean tryReadLock() throws Exception {
        String readLock = zk.create(lockPath + "/" + READ_PREFIX, 
            null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
            CreateMode.EPHEMERAL_SEQUENTIAL);

        List<String> locks = getSortedLocks();

        // 检查前面是否有写锁
        for (String lock : locks) {
            if (lock.startsWith(WRITE_PREFIX)) {
                if (getLockPath(lock).equals(readLock)) {
                    break;
                }
                return false; // 前面有写锁,等待
            }
        }
        return true;
    }
}

2.4 领导者选举

选举算法实现

public class LeaderElection {
    private String electionPath = "/election";
    private String currentNode;
    private String leaderNode;

    public void participate() throws Exception {
        // 创建临时顺序节点
        currentNode = zk.create(electionPath + "/node-", 
            null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
            CreateMode.EPHEMERAL_SEQUENTIAL);

        electLeader();
    }

    private void electLeader() throws Exception {
        List<String> nodes = zk.getChildren(electionPath, false);
        Collections.sort(nodes);

        String smallestNode = nodes.get(0);
        leaderNode = electionPath + "/" + smallestNode;

        if (currentNode.equals(leaderNode)) {
            onElectedAsLeader();
        } else {
            // 监听前一个节点
            int currentIndex = nodes.indexOf(getNodeName(currentNode));
            String previousNode = nodes.get(currentIndex - 1);
            zk.exists(electionPath + "/" + previousNode, 
                new LeaderElectionWatcher());
        }
    }

    private class LeaderElectionWatcher implements Watcher {
        public void process(WatchedEvent event) {
            if (event.getType() == EventType.NodeDeleted) {
                try {
                    electLeader(); // 重新选举
                } catch (Exception e) {
                    // 处理异常
                }
            }
        }
    }
}

2.5 集群管理

节点状态监控

public class ClusterManager {
    private String membersPath = "/cluster/members";

    public void joinCluster(String nodeId, String endpoint) throws Exception {
        // 注册节点
        zk.create(membersPath + "/" + nodeId, 
            endpoint.getBytes(), 
            ZooDefs.Ids.OPEN_ACL_UNSAFE,
            CreateMode.EPHEMERAL);

        // 监听成员变化
        zk.getChildren(membersPath, new MembersWatcher());
    }

    private class MembersWatcher implements Watcher {
        public void process(WatchedEvent event) {
            if (event.getType() == EventType.NodeChildrenChanged) {
                try {
                    updateMemberList();
                } catch (Exception e) {
                    // 处理异常
                }
            }
        }
    }

    private void updateMemberList() throws Exception {
        List<String> members = zk.getChildren(membersPath, true);
        // 更新集群成员视图
    }
}

第三章:ZooKeeper 集群架构

3.1 集群角色与选举

服务器角色

  • Leader:处理所有写请求,协调故障恢复
  • Follower:处理读请求,参与领导者选举
  • Observer:处理读请求,不参与选举(提高扩展性)

ZAB 协议(ZooKeeper Atomic Broadcast)

ZAB 协议保证集群一致性:

  1. 发现阶段:选举领导者,收集 follower 状态
  2. 同步阶段:将领导者数据同步到 follower
  3. 广播阶段:处理客户端写请求并广播

选举算法细节

// 伪代码:选举过程
public class LeaderElectionAlgorithm {
    public void runElection() {
        while (true) {
            // 1. 收集选票
            Map<Long, Integer> votes = collectVotes();

            // 2. 统计选票
            Long proposedLeader = countVotes(votes);

            // 3. 检查是否达成多数派
            if (hasQuorum(proposedLeader)) {
                // 4. 宣布选举结果
                announceLeader(proposedLeader);
                break;
            }
        }
    }
}

3.2 数据复制与一致性

写请求处理流程

客户端 → Leader → 提案广播 → Follower ACK → 提交 → 响应客户端

读请求处理

  • 可能返回旧数据:读请求可能由 follower 处理
  • sync() 操作:强制同步最新数据
  • 线性化读:从 leader 读取保证最新数据

3.3 会话管理

会话生命周期

public class SessionLifecycle {
    // 会话创建
    long sessionId = zk.getSessionId();

    // 心跳维护
    void sendPing() {
        // 定期发送心跳保持会话
    }

    // 会话超时处理
    void onSessionExpired() {
        // 清理临时节点,重新连接
    }
}

会话状态转移

CONNECTING → CONNECTED → (EXPIRED/CLOSED)

第四章:ZooKeeper 安装与配置

4.1 系统要求与准备

环境要求

  • Java:JDK 8 或更高版本
  • 内存:至少 2GB 可用内存
  • 磁盘:可靠的持久化存储
  • 网络:稳定的网络环境

集群规划

  • 节点数量:推荐 3、5、7 个节点(奇数个)
  • 磁盘配置:使用 SSD 提高性能
  • 网络配置:低延迟、高带宽网络

4.2 单机模式安装

下载与安装

# 1. 下载 ZooKeeper
cd /opt
wget https://downloads.apache.org/zookeeper/zookeeper-3.8.1/apache-zookeeper-3.8.1-bin.tar.gz

# 2. 解压
tar -xzf apache-zookeeper-3.8.1-bin.tar.gz
ln -s apache-zookeeper-3.8.1-bin zookeeper

# 3. 创建数据目录
mkdir -p /var/lib/zookeeper/data
mkdir -p /var/lib/zookeeper/logs

# 4. 设置环境变量
echo 'export ZOOKEEPER_HOME=/opt/zookeeper' >> ~/.bashrc
echo 'export PATH=$PATH:$ZOOKEEPER_HOME/bin' >> ~/.bashrc
source ~/.bashrc

配置文件

创建 $ZOOKEEPER_HOME/conf/zoo.cfg

# zoo.cfg - 单机配置
tickTime=2000
dataDir=/var/lib/zookeeper/data
dataLogDir=/var/lib/zookeeper/logs
clientPort=2181

# 单机模式基础配置
maxClientCnxns=60
minSessionTimeout=4000
maxSessionTimeout=40000

# 高级配置
autopurge.snapRetainCount=3
autopurge.purgeInterval=24
syncLimit=5
initLimit=10

# 四字命令白名单
4lw.commands.whitelist=*

启动与验证

# 启动 ZooKeeper
$ZOOKEEPER_HOME/bin/zkServer.sh start

# 检查状态
$ZOOKEEPER_HOME/bin/zkServer.sh status

# 连接客户端
$ZOOKEEPER_HOME/bin/zkCli.sh -server localhost:2181

# 测试基本操作
[zk: localhost:2181] create /test "hello"
[zk: localhost:2181] get /test
[zk: localhost:2181] ls /

4.3 集群模式安装

集群配置准备

假设有 3 个节点:

  • node1: 192.168.1.101
  • node2: 192.168.1.102
  • node3: 192.168.1.103

集群配置文件

每个节点的 zoo.cfg

# zoo.cfg - 集群配置
tickTime=2000
dataDir=/var/lib/zookeeper/data
dataLogDir=/var/lib/zookeeper/logs
clientPort=2181

# 集群配置
initLimit=10
syncLimit=5

# 服务器列表
server.1=192.168.1.101:2888:3888
server.2=192.168.1.102:2888:3888
server.3=192.168.1.103:2888:3888

# 高级配置
autopurge.snapRetainCount=3
autopurge.purgeInterval=24
4lw.commands.whitelist=*

端口说明:

  • 2888:领导者与 follower 通信端口
  • 3888:领导者选举通信端口

设置 myid 文件

在每个节点的 dataDir 目录创建 myid 文件:

节点1

echo "1" > /var/lib/zookeeper/data/myid

节点2

echo "2" > /var/lib/zookeeper/data/myid

节点3

echo "3" > /var/lib/zookeeper/data/myid

启动集群

# 在每个节点上启动
$ZOOKEEPER_HOME/bin/zkServer.sh start

# 检查集群状态
$ZOOKEEPER_HOME/bin/zkServer.sh status

# 应该显示模式:follower 或 leader

4.4 系统服务配置

Systemd 服务文件

创建 /etc/systemd/system/zookeeper.service

[Unit]
Description=Apache ZooKeeper
After=network.target

[Service]
Type=forking
User=zookeeper
Group=zookeeper
Environment=JAVA_HOME=/usr/lib/jvm/java-11-openjdk
ExecStart=/opt/zookeeper/bin/zkServer.sh start
ExecStop=/opt/zookeeper/bin/zkServer.sh stop
ExecReload=/opt/zookeeper/bin/zkServer.sh restart
Restart=on-failure
RestartSec=10s

[Install]
WantedBy=multi-user.target

创建专用用户

# 创建用户和组
sudo groupadd zookeeper
sudo useradd -g zookeeper zookeeper

# 设置目录权限
sudo chown -R zookeeper:zookeeper /opt/zookeeper
sudo chown -R zookeeper:zookeeper /var/lib/zookeeper

# 启用服务
sudo systemctl daemon-reload
sudo systemctl enable zookeeper
sudo systemctl start zookeeper

第五章:ZooKeeper 运维监控

5.1 监控指标与工具

关键监控指标

  • 节点数量:znode 总数和临时节点数
  • Watch 数量:活跃的 watch 数量
  • 延迟指标:请求处理延迟
  • 连接数:活跃客户端连接数
  • 队列大小:待处理请求队列

四字命令监控

# 查看服务器状态
echo stat | nc localhost 2181

# 查看连接详情
echo cons | nc localhost 2181

# 查看环境信息
echo envi | nc localhost 2181

# 查看监控摘要
echo mntr | nc localhost 2181

JMX 监控

# 启用 JMX 监控
export JMXLOCALONLY=false
export JMXPORT=9999
export JMXAUTH=false
export JMXSSL=false

# 或者通过启动参数
export SERVER_JVMFLAGS="-Dcom.sun.management.jmxremote \
  -Dcom.sun.management.jmxremote.port=9999 \
  -Dcom.sun.management.jmxremote.authenticate=false \
  -Dcom.sun.management.jmxremote.ssl=false"

5.2 备份与恢复

数据备份

# 备份快照和日志
tar -czf zk-backup-$(date +%Y%m%d).tar.gz \
  /var/lib/zookeeper/data/version-2 \
  /var/lib/zookeeper/logs/version-2

# 使用 zkCli 导出重要数据
$ZOOKEEPER_HOME/bin/zkCli.sh -server localhost:2181 <<EOF
get /important/config
get /critical/path
quit
EOF

数据恢复

# 停止 ZooKeeper
$ZOOKEEPER_HOME/bin/zkServer.sh stop

# 恢复备份数据
tar -xzf zk-backup-20231201.tar.gz -C /

# 启动 ZooKeeper
$ZOOKEEPER_HOME/bin/zkServer.sh start

5.3 性能调优

JVM 调优

# 修改 zkServer.sh 中的 JVM 参数
export SERVER_JVMFLAGS="-Xmx4g -Xms4g \
  -XX:+UseG1GC -XX:MaxGCPauseMillis=20 \
  -XX:+HeapDumpOnOutOfMemoryError \
  -Xloggc:/var/log/zookeeper/gc.log"

操作系统调优

# 增加文件描述符限制
echo "zookeeper - nofile 65536" >> /etc/security/limits.conf

# 网络调优
echo 'net.core.somaxconn=65536' >> /etc/sysctl.conf
echo 'net.ipv4.tcp_keepalive_time=600' >> /etc/sysctl.conf
sysctl -p

第六章:常见问题排查

6.1 启动问题

端口冲突

# 检查端口占用
netstat -tlnp | grep 2181
lsof -i :2181

# 解决方:修改配置或停止冲突进程

权限问题

# 检查目录权限
ls -la /var/lib/zookeeper/

# 解决:修正权限
chown -R zookeeper:zookeeper /var/lib/zookeeper

6.2 运行问题

内存不足

# 检查内存使用
jstat -gc <zookeeper_pid>

# 调整 JVM 参数
export JAVA_OPTS="-Xmx2g -Xms2g"

网络分区

# 检查集群连接
echo mntr | nc localhost 2181 | grep followers

# 检查日志
tail -f /var/log/zookeeper/zookeeper.log

6.3 数据一致性问题

数据损坏恢复

# 1. 停止所有节点
$ZOOKEEPER_HOME/bin/zkServer.sh stop

# 2. 备份当前数据
cp -r /var/lib/zookeeper/data /backup/

# 3. 从健康节点复制数据
scp healthy-node:/var/lib/zookeeper/data/* /var/lib/zookeeper/data/

# 4. 重启集群
$ZOOKEEPER_HOME/bin/zkServer.sh start

第七章:ZooKeeper 使用场景

7.1 配置中心

动态配置管理

public class DynamicConfig {
    private Properties config = new Properties();

    public void init() throws Exception {
        // 加载初始配置
        loadConfig();

        // 监听配置变化
        zk.exists("/app/config", new Watcher() {
            public void process(WatchedEvent event) {
                if (event.getType() == EventType.NodeDataChanged) {
                    loadConfig();
                }
            }
        });
    }

    private void loadConfig() throws Exception {
        byte[] data = zk.getData("/app/config", false, null);
        // 更新配置
        config.load(new ByteArrayInputStream(data));
    }
}

7.2 分布式锁服务

公平锁实现

public class FairLock {
    private String lockPath;
    private String currentSeq;

    public void lock() throws Exception {
        // 创建顺序节点
        currentSeq = zk.create(lockPath + "/lock-", 
            null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
            CreateMode.EPHEMERAL_SEQUENTIAL);

        while (true) {
            List<String> locks = zk.getChildren(lockPath, false);
            Collections.sort(locks);

            if (currentSeq.endsWith(locks.get(0))) {
                return; // 获得锁
            }

            // 监听前一个节点
            int currentIndex = locks.indexOf(getSeqNumber(currentSeq));
            String prevLock = locks.get(currentIndex - 1);

            CountDownLatch latch = new CountDownLatch(1);
            zk.exists(lockPath + "/" + prevLock, 
                new LockWatcher(latch));
            latch.await();
        }
    }
}

7.3 服务发现与注册

服务注册中心

public class ServiceRegistry {
    private String basePath = "/services";

    public void registerService(String serviceName, 
                              String endpoint) throws Exception {
        String servicePath = basePath + "/" + serviceName;

        // 创建服务节点(如果不存在)
        if (zk.exists(servicePath, false) == null) {
            zk.create(servicePath, null, 
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }

        // 注册服务实例
        String instancePath = servicePath + "/instance-";
        zk.create(instancePath, endpoint.getBytes(),
            ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    }

    public List<String> discoverServices(String serviceName) throws Exception {
        String servicePath = basePath + "/" + serviceName;
        return zk.getChildren(servicePath, true);
    }
}

总结

ZooKeeper 作为分布式系统的基石,提供了可靠的协调服务。通过深入理解其核心概念、掌握集群部署和运维技能,可以构建出稳定可靠的分布式应用。随着云原生技术的发展,虽然出现了 etcd、Consul 等替代方案,但 ZooKeeper 在成熟度、稳定性和生态系统方面仍然具有重要地位。

在实际应用中,需要根据具体业务需求合理设计数据模型、配置参数和监控策略。对于关键业务系统,建议采用集群部署并建立完善的监控告警机制,确保服务的可靠性和可用性。

作者:严锋  创建时间:2025-10-14 08:46
最后编辑:严锋  更新时间:2025-11-04 14:01