文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

【ZooKeeper】Apache ZooKeeper 深入详解

一、ZooKeeper 核心概念与架构

1.1 什么是 ZooKeeper?

Apache ZooKeeper 是一个分布式的、开放源码的分布式应用程序协调服务。它提供了一个简单的原语集合,分布式应用程序可以基于这些原语实现更高级的服务,如:

  • 配置管理
  • 命名服务
  • 分布式同步
  • 组服务

1.2 ZooKeeper 设计目标

分布式应用
协调需求
配置管理
领导者选举
分布式锁
服务发现
ZooKeeper

核心设计原则

  1. 简单性:类似文件系统的数据模型
  2. 可靠性:集群中大部分节点存活即可服务
  3. 顺序性:所有更新操作全局有序
  4. 快速性:读操作特别快速
  5. 原子性:更新操作要么成功要么失败

1.3 系统架构

// ZooKeeper 服务架构核心组件
public class ZooKeeperServer {
    // 数据存储
    private DataTree dataTree;
    private ZKDatabase zkDatabase;
    
    // 请求处理器链
    private RequestProcessor firstProcessor;
    
    // 会话管理
    private SessionTracker sessionTracker;
    
    // 网络层
    private ServerCnxnFactory cnxnFactory;
}

集群角色

  • Leader:处理所有写请求,协调共识
  • Follower:处理读请求,转发写请求到 Leader
  • Observer:处理读请求,不参与投票

二、ZooKeeper 数据模型

2.1 ZNode 详解

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

/
├── /zookeeper
│   └── /quota
├── /app1
│   ├── /config (存储配置数据)
│   ├── /lock (分布式锁)
│   └── /leader (领导者选举)
└── /app2
    ├── /services
    └── /metadata
2.1.1 ZNode 类型
public class CreateRequest extends Record {
    private String path;           // 节点路径
    private byte[] data;           // 节点数据
    private List<ACL> acl;         // 访问控制列表
    private int flags;             // 节点类型标志
    
    // 节点类型常量
    public static final int PERSISTENT = 0;           // 持久节点
    public static final int EPHEMERAL = 1;           // 临时节点  
    public static final int SEQUENTIAL = 2;          // 顺序节点
    public static final int CONTAINER = 4;           // 容器节点
    public static final int PERSISTENT_SEQUENTIAL = PERSISTENT | SEQUENTIAL;
    public static final int EPHEMERAL_SEQUENTIAL = EPHEMERAL | SEQUENTIAL;
}

节点类型详解

  1. 持久节点(PERSISTENT)

    • 创建后一直存在,除非显式删除
    • 适用于存储配置信息等持久化数据
  2. 临时节点(EPHEMERAL)

    • 与客户端会话绑定,会话结束自动删除
    • 适用于服务注册、存活检测
  3. 顺序节点(SEQUENTIAL)

    • 名称自动追加单调递增序号
    • 适用于分布式锁、队列等场景
  4. 容器节点(CONTAINER)

    • 当最后一个子节点被删除时自动删除
    • 适用于实现领导者选举等模式
2.1.2 ZNode 数据结构
public class DataNode {
    byte data[];                    // 节点数据(最大1MB)
    Long acl;                      // ACL版本号
    StatPersisted stat;            // 节点状态信息
    Set<String> children;          // 子节点集合
}

public class StatPersisted {
    private long czxid;            // 创建该节点的事务ID
    private long mzxid;            // 最后修改的事务ID  
    private long ctime;            // 创建时间
    private long mtime;            // 最后修改时间
    private int version;           // 数据版本号
    private int cversion;          // 子节点版本号
    private int aversion;          // ACL版本号
    private long ephemeralOwner;   // 临时节点所有者会话ID
    private int dataLength;        // 数据长度
    private int numChildren;       // 子节点数量
    private long pzxid;            // 最后修改的子节点事务ID
}

2.2 版本控制与条件更新

ZooKeeper 通过版本号实现乐观锁:

// 条件更新示例
public boolean conditionalUpdate(String path, String newData, int expectedVersion) {
    try {
        // 检查当前版本
        Stat currentStat = zk.exists(path, false);
        if (currentStat.getVersion() != expectedVersion) {
            return false; // 版本不匹配,更新失败
        }
        
        // 执行条件更新
        zk.setData(path, newData.getBytes(), expectedVersion);
        return true;
    } catch (KeeperException.BadVersionException e) {
        return false; // 版本冲突
    }
}

三、ZooKeeper 客户端会话

3.1 会话生命周期

public class ZooKeeper {
    private final long sessionId;           // 会话ID
    private final byte[] sessionPasswd;    // 会话密码
    private final int sessionTimeout;      // 会话超时时间
    
    // 会话状态
    public enum States {
        CONNECTING, ASSOCIATING, CONNECTED, 
        CLOSED, AUTH_FAILED, NOT_CONNECTED
    }
}

会话状态转移

NOT_CONNECTED
CONNECTING
ASSOCIATING
CONNECTED
CLOSED
AUTH_FAILED

3.2 心跳与会话超时

// 会话跟踪器实现
public class SessionTrackerImpl implements SessionTracker {
    private final ConcurrentHashMap<Long, SessionImpl> sessionsById;
    private final ExpiryQueue<SessionImpl> sessionExpiryQueue;
    
    public void run() {
        while (running) {
            long currentTime = System.currentTimeMillis();
            
            // 检查过期会话
            for (SessionImpl s : sessionExpiryQueue.getExpiredSessions()) {
                expireSession(s.getSessionId());
            }
            
            // 更新下一个过期时间
            updateNextExpirationTime();
            
            Thread.sleep(1000); // 每秒检查一次
        }
    }
}

四、Watch 机制深度解析

4.1 Watch 注册与触发

public class ZKWatchManager implements ClientWatchManager {
    // 不同类型的 Watcher 集合
    private final Map<String, Set<Watcher>> dataWatches;
    private final Map<String, Set<Watcher>> existWatches;
    private final Map<String, Set<Watcher>> childWatches;
    
    // 注册 Watch
    public void addWatch(String path, Watcher watcher, WatcherMode mode) {
        switch (mode) {
            case DATA: dataWatches.computeIfAbsent(path, k -> new HashSet<>()).add(watcher); break;
            case CHILDREN: childWatches.computeIfAbsent(path, k -> new HashSet<>()).add(watcher); break;
        }
    }
}

4.2 Watch 事件类型

public class WatchedEvent {
    private final KeeperState keeperState;  // 连接状态事件
    private final EventType eventType;      // 节点事件类型
    private final String path;              // 事件路径
    
    public enum EventType {
        None, NodeCreated, NodeDeleted, 
        NodeDataChanged, NodeChildrenChanged
    }
}

4.3 Watch 语义保证

ZooKeeper 对 Watch 提供以下保证:

  1. 一次性触发:Watch 被触发后需要重新注册
  2. 有序性:客户端按顺序收到 Watch 事件
  3. 可靠性:不会丢失任何变更事件
  4. 时效性:客户端看到的状态不会比 Watch 事件更旧

五、ZAB 协议深度解析

5.1 ZAB 协议概述

ZooKeeper Atomic Broadcast(ZAB)是 ZooKeeper 的核心共识算法,专为 ZooKeeper 设计,保证所有更新的全局顺序。

5.1.1 协议阶段
选举阶段
发现阶段
同步阶段
广播阶段
恢复阶段

5.2 选举算法实现

public class FastLeaderElection implements Election {
    // 选票数据结构
    static public class ToVote {
        long proposedLeader;    // 推荐的Leader
        long proposedZxid;      // 推荐Leader的ZXID
        long logicalClock;      // 逻辑时钟
        long peerEpoch;         // 选举周期
    }
    
    // 选举逻辑
    private Vote lookForLeader() throws InterruptedException {
        while (!stop) {
            // 1. 自荐为Leader
            vote = new Vote(myid, getLastLoggedZxid());
            
            // 2. 发送投票通知
            sendNotifications();
            
            // 3. 接收其他服务器投票
            Notification n = recvQueue.poll(timeout, TimeUnit.MILLISECONDS);
            
            // 4. 判断投票有效性
            if (n.electionEpoch > logicalclock) {
                logicalclock = n.electionEpoch;
                vote = null; // 重置投票
            }
            
            // 5. 统计投票结果
            if (getVoteTracker().hasAllQuorum()) {
                return vote; // 选举成功
            }
        }
    }
}

5.3 事务处理流程

public class LeaderZooKeeperServer extends ZooKeeperServer {
    // 提议队列
    private final ConcurrentLinkedQueue<Proposal> outstandingProposals;
    
    // 事务提交流程
    public void propose(Request request) throws IOException {
        // 1. 创建事务提议
        Proposal p = new Proposal();
        p.request = request;
        p.zxid = request.zxid;
        
        // 2. 发送给所有Follower
        for (LearnerHandler f : getForwardingFollowers()) {
            f.queuePacket(p);
        }
        
        // 3. 等待ACK
        outstandingProposals.add(p);
        
        // 4. 收到多数ACK后提交
        if (p.hasAllQuorum()) {
            commit(p);
        }
    }
}

5.4 数据一致性保证

public class ZKDatabase {
    private final DataTree dataTree;
    private final FileTxnSnapLog snapLog;
    
    // 数据恢复
    public void loadDataBase() throws IOException {
        // 1. 从快照恢复
        long zxid = snapLog.restore(dataTree, sessions);
        
        // 2. 从事务日志重放
        TxnIterator itr = snapLog.readTxnLog(zxid);
        while (itr.next()) {
            processTxn(itr.getTxn());
        }
    }
    
    // 处理事务
    public ProcessTxnResult processTxn(TxnHeader hdr, Record txn) {
        switch (hdr.getType()) {
            case OpCode.create:
                return processCreateTxn(hdr, (CreateTxn) txn);
            case OpCode.setData:
                return processSetDataTxn(hdr, (SetDataTxn) txn);
            // ... 其他操作类型
        }
    }
}

六、请求处理流程

6.1 请求处理器链

ZooKeeper 使用责任链模式处理请求:

// 处理器链配置
protected void setupRequestProcessors() {
    // 单机模式
    if (standalone) {
        firstProcessor = new PrepRequestProcessor(this, 
            new FinalRequestProcessor(this));
    } 
    // 集群模式 - Leader
    else if (isLeader()) {
        firstProcessor = new PrepRequestProcessor(this,
            new ProposalRequestProcessor(this,
                new CommitProcessor(this,
                    new Leader.ToBeAppliedRequestProcessor(this,
                        new FinalRequestProcessor(this)))));
    }
    // 集群模式 - Follower  
    else {
        firstProcessor = new FollowerRequestProcessor(this,
            new CommitProcessor(this,
                new FinalRequestProcessor(this)));
    }
}

6.2 读请求处理

public class FinalRequestProcessor implements RequestProcessor {
    public void processRequest(Request request) {
        switch (request.type) {
            case OpCode.getData:
                GetDataRequest getDataRequest = new GetDataRequest();
                ByteBufferInputStream.byteBufferToRecord(request.request, getDataRequest);
                
                // 读取数据
                DataNode n = zks.getZKDatabase().getNode(getDataRequest.getPath());
                if (n == null) {
                    throw new KeeperException.NoNodeException();
                }
                
                // 检查ACL权限
                zks.getZKDatabase().checkACL(request.cnxn, 
                    Collections.singletonList(n.acl), ZooDefs.Perms.READ, 
                    request.authInfo, getDataRequest.getPath(), null);
                
                // 设置Watch
                if (getDataRequest.getWatch()) {
                    zks.getZKDatabase().addWatch(getDataRequest.getPath(), 
                        request.cnxn);
                }
                
                // 返回响应
                GetDataResponse response = new GetDataResponse(n.data, n.stat);
                request.cnxn.sendResponse(response);
                break;
        }
    }
}

6.3 写请求处理

public class PrepRequestProcessor extends Thread implements RequestProcessor {
    public void processRequest(Request request) {
        // 1. 请求验证和预处理
        pRequest2Txn(request.type, request.request, 
            request.authInfo, request.getTxn());
        
        // 2. 分配ZXID
        request.zxid = getZxid();
        
        // 3. 传递给下一个处理器
        nextProcessor.processRequest(request);
    }
}

七、典型应用场景实现

7.1 分布式锁实现

public class DistributedLock {
    private final ZooKeeper zk;
    private final String lockPath;
    private String currentLockPath;
    
    public boolean tryLock() throws Exception {
        // 创建临时顺序节点
        currentLockPath = zk.create(lockPath + "/lock-", 
            null, ZooDefs.Ids.OPEN_ACL_UNSAFE, 
            CreateMode.EPHEMERAL_SEQUENTIAL);
        
        // 获取所有锁节点
        List<String> children = zk.getChildren(lockPath, false);
        Collections.sort(children);
        
        // 检查是否获得锁
        String smallestNode = children.get(0);
        if (currentLockPath.endsWith(smallestNode)) {
            return true; // 获得锁
        }
        
        // 监听前一个节点
        String previousNode = getPreviousNode(children, currentLockPath);
        CountDownLatch latch = new CountDownLatch(1);
        Stat stat = zk.exists(lockPath + "/" + previousNode, 
            event -> {
                if (event.getType() == EventType.NodeDeleted) {
                    latch.countDown();
                }
            });
            
        if (stat != null) {
            latch.await(); // 等待锁释放
        }
        
        return true;
    }
}

7.2 配置管理实现

public class ConfigManager {
    private final ZooKeeper zk;
    private final String configPath;
    private volatile Map<String, String> config = new HashMap<>();
    
    public void init() throws Exception {
        // 读取初始配置
        loadConfig();
        
        // 监听配置变更
        zk.getData(configPath, event -> {
            if (event.getType() == EventType.NodeDataChanged) {
                loadConfig(); // 重新加载配置
            }
        }, null);
    }
    
    private void loadConfig() throws Exception {
        byte[] data = zk.getData(configPath, false, null);
        String configStr = new String(data, StandardCharsets.UTF_8);
        
        // 解析配置
        Map<String, String> newConfig = parseConfig(configStr);
        synchronized (this) {
            this.config = newConfig;
        }
        
        // 通知配置变更
        notifyConfigChange();
    }
}

7.3 服务发现实现

public class ServiceRegistry {
    private final ZooKeeper zk;
    private final String servicePath;
    
    public void registerService(String serviceName, String endpoint) throws Exception {
        String serviceNode = servicePath + "/" + serviceName;
        
        // 创建服务节点(如果不存在)
        if (zk.exists(serviceNode, false) == null) {
            zk.create(serviceNode, null, 
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        
        // 注册服务实例
        String instancePath = serviceNode + "/instance-";
        zk.create(instancePath, endpoint.getBytes(),
            ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    }
    
    public List<String> discoverServices(String serviceName) throws Exception {
        String serviceNode = servicePath + "/" + serviceName;
        return zk.getChildren(serviceNode, false);
    }
}

八、性能优化与最佳实践

8.1 配置优化

# zoo.cfg 关键配置
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/var/lib/zookeeper
clientPort=2181

# 内存配置
maxClientCnxns=60
minSessionTimeout=4000
maxSessionTimeout=40000

# 快照和日志配置
autopurge.snapRetainCount=3
autopurge.purgeInterval=1
preAllocSize=65536
snapCount=100000

8.2 JVM 优化

# JVM 参数
export JAVA_OPTS="-server \
-Xms4g -Xmx4g \
-XX:NewSize=1g -XX:MaxNewSize=1g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=20 \
-XX:InitiatingHeapOccupancyPercent=35 \
-XX:+DisableExplicitGC \
-Dzookeeper.admin.enableServer=false"

8.3 监控指标

关键监控指标:

  1. znode 数量:监控数据节点增长
  2. Watch 数量:避免 Watch 过多影响性能
  3. 会话数量:监控连接数
  4. 请求延迟:P95、P99 延迟指标
  5. 事务吞吐量:每秒处理的事务数

九、故障排查与恢复

9.1 常见问题排查

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

# 检查监控指标
echo mntr | nc localhost 2181

# 检查连接信息  
echo cons | nc localhost 2181

# 检查Watch信息
echo wchs | nc localhost 2181

9.2 数据恢复流程

public class DataRecovery {
    public void recoverFromFailure() throws Exception {
        // 1. 检查事务日志完整性
        FileTxnLog txnLog = new FileTxnLog(dataDir);
        long lastZxid = txnLog.getLastLoggedZxid();
        
        // 2. 从最新快照恢复
        FileSnap snap = new FileSnap(snapDir);
        DataTree tree = new DataTree();
        snap.deserialize(tree, sessions);
        
        // 3. 重放事务日志
        TxnIterator itr = txnLog.read(lastZxid);
        while (itr.next()) {
            ProcessTxnResult rc = tree.processTxn(itr.getHeader(), itr.getTxn());
        }
        
        // 4. 验证数据一致性
        if (!tree.isValid()) {
            throw new IOException("Data recovery failed: inconsistent state");
        }
    }
}

十、ZooKeeper 3.8.0 新特性

10.1 可插拔的 ZKDatabase

// 支持自定义存储后端
public interface ZKDatabase {
    void setDataTree(DataTree dt);
    DataTree getDataTree();
    long getDataTreeLastProcessedZxid();
}

10.2 增强的管理 API

// 新的管理命令接口
public interface AdminCommands {
    String getCommandOutput(String cmd);
    
    // 新增命令
    String sync(String path);
    String reconfig(String joining, String leaving, String newMembers);
}

十一、总结

ZooKeeper 作为分布式协调服务的基石,其设计体现了分布式系统的核心原则:

  1. 强一致性:通过 ZAB 协议保证数据一致性
  2. 高可用性:多数派存活即可提供服务
  3. 顺序保证:所有操作全局有序
  4. 高性能:读操作特别快速,写操作经过优化

在实际应用中,需要注意:

  • 合理设置会话超时时间
  • 避免在 ZNode 中存储大文件
  • 合理使用 Watch 机制,避免过多监听
  • 定期清理事务日志和快照

ZooKeeper 的简洁设计和强大功能使其成为构建可靠分布式系统的首选协调服务。

posted @ 2025-10-06 20:30  NeoLshu  阅读(0)  评论(0)    收藏  举报  来源