Curator源码解析:LeaderLatch实现

文章目录

前言

博主介绍:✌目前全网粉丝4W+,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质作者、专注于Java后端技术领域。

涵盖技术内容:Java后端、大数据、算法、分布式微服务、中间件、前端、运维等。

博主所有博客文件目录索引:博客目录索引(持续更新)

CSDN搜索:长路

视频平台:b站-Coder长路

Curator框架源码地址

github地址:https://github.com/apache/curator

我们切到apache-curator-3.3.0这个tag。

核心本质原理

本质 :利用 ZooKeeper 的临时顺序节点 + 节点序号最小者获胜 机制。

优点设计:

临时节点:客户端断开时自动删除,避免僵尸节点

顺序节点:保证每个客户端获得唯一递增序号

最小序号获胜:公平且确定性的选举算法

监听机制:每个客户端只监听紧挨着自己的前一个节点,避免羊群效应。

plain 复制代码
[初始状态]
/election/lock-0000000001 (客户端A) ← Leader
/election/lock-0000000002 (客户端B) ← 监听 lock-0000000001
/election/lock-0000000003 (客户端C) ← 监听 lock-0000000002

[客户端A下线后]
/election/lock-0000000002 (客户端B) ← 新Leader
/election/lock-0000000003 (客户端C) ← 监听 lock-0000000002

Curator 主从选举最核心的原理:利用 ZooKeeper 临时顺序节点的序号特性,序号最小者获胜,其他节点监听前一个节点的删除事件

第1步:客户端启动,创建临时顺序节点

java 复制代码
// 每个客户端执行:
client.create()
    .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
    .forPath("/leader/latch-", id.getBytes());

实际创建的节点

java 复制代码
/leader/latch-0000000001  (客户端A)
/leader/latch-0000000002  (客户端B)
/leader/latch-0000000003  (客户端C)

第2步:获取所有子节点并排序

java 复制代码
List<String> children = client.getChildren().forPath("/leader");
// 排序后:
["latch-0000000001", "latch-0000000002", "latch-0000000003"]

第3步:检查自己是否是第一个节点

客户端A (latch-0000000001):

  • 自己的索引 = 0 (第一个)
  • 成为 Leader

客户端B (latch-0000000002):

  • 自己的索引 = 1 (第二个)
  • ❌ 不是 Leader,需要监听前一个节点 latch-0000000001

客户端C (latch-0000000003):

  • 自己的索引 = 2 (第三个)
  • ❌ 不是 Leader,需要监听前一个节点 latch-0000000002

核心代码如下:

第4步:领导权变更

客户端A下线时:

  • ZooKeeper 自动删除临时节点 latch-0000000001
  • 客户端B 收到 NodeDeleted 通知

客户端B 重新检查排序:

java 复制代码
// 新的子节点列表:
["latch-0000000002", "latch-0000000003"]
  • 客户端B 索引变为 0 → ✅ 成为新 Leader

源码解析

LeaderLatch核心实现(选主逻辑与监听器)

LeaderLatch是 Curator 实现分布式选主的核心类,主要逻辑在LeaderLatch.java中

1、初始化与启动(LeaderLatch构造器 & start)

java 复制代码
// LeaderLatch构造方法
public LeaderLatch(CuratorFramework client, String latchPath, String id, CloseMode closeMode) {
    this.client = client.newWatcherRemoveCuratorFramework(); // 包装客户端,用于移除监听器
    this.latchPath = PathUtils.validatePath(latchPath); // 校验选主节点路径
    this.id = id; // 节点标识(如你的localAddress)
    this.closeMode = closeMode;
}

// 启动选主逻辑
public void start() throws Exception {
    // 状态校验:只能从LATENT转为STARTED
    Preconditions.checkState(state.compareAndSet(State.LATENT, State.STARTED), "Cannot be started more than once");

    // 连接建立后执行内部启动逻辑
    startTask.set(AfterConnectionEstablished.execute(client, new Runnable() {
        @Override
        public void run() {
            try {
                internalStart(); // 核心选主逻辑(创建临时节点、监控节点变化等)
            } finally {
                startTask.set(null);
            }
        }
    }));
}
  • 启动后会在latchPath下创建临时顺序节点(如/latchPath/latch-000000001),通过节点顺序判断leader。
  • 临时节点特性:当客户端与ZK断开连接(会话失效)时,节点会自动删除,触发重新选主。

继续看下internalStart方法:

java 复制代码
private synchronized void internalStart() {
    if (state.get() == State.STARTED) {
        // 3. 添加连接状态监听器
        client.getConnectionStateListenable().addListener(listener);
        try {
            // 4. 开始参与选举
            reset();
        } catch (Exception e) {
            ThreadUtils.checkInterrupted(e);
            log.error("An error occurred checking resetting leadership.", e);
        }
    }
}


// 对于添加连接状态监听器实现,其自己本身就实现了一个listener
private final ConnectionStateListener listener = new ConnectionStateListener(){
    @Override
    public void stateChanged(CuratorFramework client, ConnectionState newState)
    {
        handleStateChange(newState);
    }
};

// 该部分变更状态来源为:ConnectionStateManager#processEvents 会进行状态的检测变更
// 处理状态的变更情况
private void handleStateChange(ConnectionState newState)
{
    switch ( newState )
    {
        default:
        {
            // NOP
            break;
        }
        case RECONNECTED:
        {
            try
            {
                if ( client.getConnectionStateErrorPolicy().isErrorState(ConnectionState.SUSPENDED) || !hasLeadership.get() )
                {
                    reset();
                }
            }
            catch ( Exception e )
            {
                ThreadUtils.checkInterrupted(e);
                log.error("Could not reset leader latch", e);
                setLeadership(false);
            }
            break;
        }
        case SUSPENDED:
        {
            if ( client.getConnectionStateErrorPolicy().isErrorState(ConnectionState.SUSPENDED) )
            {
                setLeadership(false);
            }
            break;
        }
        case LOST:
        {
            setLeadership(false);
            break;
        }
    }
}

**核心选举逻辑:**reset()

java 复制代码
void reset() throws Exception {
    // 5. 放弃领导权
    setLeadership(false);
    // 6. 删除之前创建的节点
    setNode(null);

    BackgroundCallback callback = new BackgroundCallback() {
        @Override
        public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
            if (event.getResultCode() == KeeperException.Code.OK.intValue()) {
                // 7. 创建临时顺序节点
                setNode(event.getName());
                if (state.get() == State.CLOSED) {
                    setNode(null);
                } else {
                    // 8. 获取所有子节点,检查是否成为 Leader
                    getChildren();
                }
            } else {
                log.error("getChildren() failed. rc = " + event.getResultCode());
            }
        }
    };
    // 9. 创建临时顺序节点
    client.create()
        .creatingParentContainersIfNeeded()
        .withProtection()
        .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
        .inBackground(callback)
        .forPath(ZKPaths.makePath(latchPath, LOCK_NAME),
                 LeaderSelector.getIdBytes(id));
}

检查是否是leadership:

java 复制代码
private void checkLeadership(List<String> children) throws Exception
    {
        final String localOurPath = ourPath.get();
        // 10. 对所有子节点排序
        List<String> sortedChildren = LockInternals.getSortedChildren(LOCK_NAME, sorter, children);
        // 11. 找到自己节点的序号
        int ourIndex = (localOurPath != null) ? sortedChildren.indexOf(ZKPaths.getNodeFromPath(localOurPath)) : -1;
        if ( ourIndex < 0 )
        {
            log.error("Can't find our node. Resetting. Index: " + ourIndex);
            // 12. 如果找不到自己的节点,重新参与选举
            reset();
        }
        else if ( ourIndex == 0 )
        {
            // 13. 如果自己是第一个节点,获得领导权
            setLeadership(true);
        }
        else
        {
            // 14. 否则监听前一个节点
            String watchPath = sortedChildren.get(ourIndex - 1);
            Watcher watcher = new Watcher()
            {
                @Override
                public void process(WatchedEvent event)
                {
                    if ( (state.get() == State.STARTED) && (event.getType() == Event.EventType.NodeDeleted) && (localOurPath != null) )
                    {
                        try
                        {
                            // 15. 前一个节点被删除,重新检查
                            getChildren();
                        }
                        catch ( Exception ex )
                        {
                            ThreadUtils.checkInterrupted(ex);
                            log.error("An error occurred checking the leadership.", ex);
                        }
                    }
                }
            };

            BackgroundCallback callback = new BackgroundCallback()
            {
                @Override
                public void processResult(CuratorFramework client, CuratorEvent event) throws Exception
                {
                    if ( event.getResultCode() == KeeperException.Code.NONODE.intValue() )
                    {
                        // previous node is gone - reset
                        reset();
                    }
                }
            };
            // 16. 监听前一个节点
            // use getData() instead of exists() to avoid leaving unneeded watchers which is a type of resource leak
            client.getData().usingWatcher(watcher).inBackground(callback).forPath(ZKPaths.makePath(latchPath, watchPath));
        }
    }

2、监听器(LeaderLatchListener)机制

java 复制代码
// LeaderLatch中管理监听器的成员
private final StandardListenerManager<LeaderLatchListener> listeners = StandardListenerManager.standard();

// 添加监听器
public void addListener(LeaderLatchListener listener) {
    listeners.addListener(listener);
}

// 触发监听器回调(内部逻辑,当选主状态变化时调用)
private void setLeadership(boolean newValue) {
    boolean oldValue = hasLeadership.getAndSet(newValue);
    if (oldValue != newValue) {
        if (newValue) {
            // 成为leader时回调isLeader()
            listeners.forEach(listener -> listener.isLeader());
        } else {
            // 失去leader时回调notLeader()
            listeners.forEach(listener -> listener.notLeader());
        }
    }
}

其中对于setLeaderShip操作涉及到了如下几个方法调用来源:

  • 当节点顺序变化(如leader节点删除),LeaderLatch会重新判断自身是否为leader,通过setLeadership触发监听器的isLeader()notLeader()

3、关闭逻辑

java 复制代码
@Override
public void close() throws IOException {
    close(closeMode); // 默认使用构造时的CloseMode(如SILENT)
}

private synchronized void internalClose(CloseMode closeMode, boolean failOnClosed) throws IOException {
    if (!state.compareAndSet(State.STARTED, State.CLOSED)) {
        if (failOnClosed) {
            throw new IllegalStateException("Already closed or has not been started");
        }
        return;
    }

    cancelStartTask(); // 取消启动任务
    try {
        setNode(null); // 删除自身创建的临时节点
        client.removeWatchers(); // 移除ZK监听器
    } catch (Exception e) {
        throw new IOException(e);
    } finally {
        // 移除连接状态监听器
        client.getConnectionStateListenable().removeListener(listener);
        // 根据CloseMode处理监听器(如通知失去 leadership)
        switch (closeMode) {
            case NOTIFY_LEADER:
                setLeadership(false);
                listeners.clear();
                break;
            case SILENT:
                listeners.clear();
                setLeadership(false);
                break;
        }
    }
}

连接状态监听(ConnectionStateListener

Curator的CuratorFramework通过getConnectionStateListenable()提供连接状态监听能力,底层实现涉及如下。

1、连接状态监听器的注册与触发

java 复制代码
// CuratorFrameworkImpl中获取连接状态监听器容器
@Override
public Listenable<ConnectionStateListener> getConnectionStateListenable() {
    return connectionStateManager.getListenable();
}

// DelegatingCuratorFramework(代理类)同样支持
@Override
public Listenable<ConnectionStateListener> getConnectionStateListenable() {
    return client.getConnectionStateListenable();
}
  • Listenable是Curator的监听器容器接口,支持添加/移除监听器。
  • 当ZK连接状态变化(如SUSPENDED/LOST/RECONNECTED)时,会遍历所有注册的ConnectionStateListener并触发stateChanged方法。

2、LeaderLatch内部的连接状态处理

LeaderLatch自身也注册了连接状态监听器,用于处理连接异常:

java 复制代码
private final ConnectionStateListener listener = new ConnectionStateListener() {
    @Override
    public void stateChanged(CuratorFramework client, ConnectionState newState) {
        handleStateChange(newState); // 处理连接状态变化
    }
};

// 连接状态变化时的处理逻辑(内部方法)
private void handleStateChange(ConnectionState newState) {
    switch (newState) {
        case SUSPENDED:
        case LOST:
            // 连接中断时,标记为非leader
            setLeadership(false);
            break;
        case RECONNECTED:
            // 重连后重新参与选主
            try {
                reset(); // 重置状态并重新创建节点
            } catch (Exception e) {
                log.error("Error resetting leader latch after reconnection", e);
            }
            break;
        // 其他状态忽略
        default:
            break;
    }
}

资料获取

大家点赞、收藏、关注、评论啦~

精彩专栏推荐订阅:在下方专栏👇🏻

更多博客与资料可查看👇🏻获取联系方式👇🏻,🍅文末获取开发资源及更多资源博客获取🍅

相关推荐
富士康质检员张全蛋18 小时前
Zookeeper原理和Dubbo中的作用
zookeeper·dubbo
云技纵横1 天前
订票系统高并发实战:基于 ZooKeeper 的分布式锁、选座与幂等回滚(Java/Curator)
分布式·zookeeper·java-zookeeper
2301_807288631 天前
MPRPC项目(第11天,zookeeper)
分布式·zookeeper·debian
CrazyClaz3 天前
Zookeeper
分布式·zookeeper
yumgpkpm3 天前
网易数帆EasyData使用Cloudera CDP、CMP(华为鲲鹏版)作为底座的ChatBI方案
大数据·hive·hadoop·华为·zookeeper·kafka·cloudera
J_bean3 天前
Spring Boot + Dubbo + Zookeeper 集成
spring boot·zookeeper·dubbo
天码-行空6 天前
【大数据环境安装指南】ZooKeeper搭建Storm高可用集群教程
大数据·zookeeper·storm
天码-行空6 天前
【大数据环境安装指南】ZooKeeper搭建spark高可用集群教程
大数据·linux·运维·zookeeper·spark
java1234_小锋7 天前
Zookeeper集群数据是如何同步的?
分布式·zookeeper·云原生