Curator知识点:LeaderLatch与LeaderSelector的区别是什么?

coverImg

介绍

LeaderLatch和LeaderSelector都是为了在分布式环境中实现“领导者选举”而设计的,但它们在设计哲学和使用模式上有显著的区别。

简单来说,核心区别是:

  • LeaderLatch: 一旦成为Leader,就会一直持有领导权,直到进程关闭或出现严重错误。它是一个“一劳永逸”的模型。
  • LeaderSelector: 允许一个客户端在完成任务后主动放弃领导权,从而触发新一轮选举。它是一个“按需使用”的模型。

下面我们通过一个表格和详细解释来深入对比。

核心区别对比

特性 LeaderLatch LeaderSelector
领导权释放机制 被动释放。只有当客户端与ZooKeeper的连接中断或进程关闭时,才会释放领导权。 主动释放。通过LeaderSelectorListener的takeLeadership方法返回时,客户端会自动放弃领导权。
使用模式 一次性、长期持有。获得领导权后,通常会一直保持,直到发生故障。 可重复、短期任务。获得领导权,执行一个特定任务,然后主动放弃,之后可以再次参与选举。
监听器接口 LeaderLatchListener LeaderSelectorListener
关键方法 start(), close(), hasLeadership() start(), close(), autoRequeue()
选举重入 如果领导权被释放(如连接断开后重连),它会自动重新参与选举。 默认情况下,takeLeadership方法只执行一次。需要调用autoRequeue()方法才能使其在执行完毕后自动重新加入选举队列。
适用场景 需要一个常驻的Master节点,例如主数据库、主网关、任务调度器主节点。 需要轮流执行的任务,例如分布式锁、批量数据处理任务的分配、负载均衡。

详细解释与代码示例

1. LeaderLatch

设计思想: 模拟一个“抢椅子”游戏,当音乐停止(选举开始)时,抢到椅子的节点就是Leader,并且它会一直坐在那把椅子上,直到它自己离开(进程退出)或被强制带走(与ZK断开连接)。

工作流程:

  1. 多个客户端创建并启动同一个路径下的LeaderLatch。
  2. 其中一个客户端会成功获得领导权,其 hasLeadership() 方法返回 true。
  3. 该客户端会一直保持Leader状态,除非它被关闭 (close()) 或与ZooKeeper服务器的会话过期。
  4. 当Leader失效后,其他存活的客户端会开始新一轮选举,产生新的Leader。

代码示例:

public class LeaderLatchExample {
    public static void main(String[] args) throws Exception {
        CuratorFramework client = ... // 创建Curator客户端
        LeaderLatch latch = new LeaderLatch(client, "/leader/latch/path", "Client-"+args[0]);
        
        latch.addListener(new LeaderLatchListener() {
            @Override
            public void isLeader() {
                System.out.println("我是Leader了!开始执行主节点任务...");
                // 这里可以启动一些只有Leader才能做的服务,比如定时任务调度
            }
            
            @Override
            public void notLeader() {
                System.out.println("我不是Leader了。");
                // 这里可以停止那些主节点服务
            }
        });
        
        latch.start();
        // 主线程阻塞,模拟应用程序持续运行
        Thread.sleep(Integer.MAX_VALUE);
    }
}

2. LeaderSelector

设计思想: 模拟一个“任务队列”,所有客户端排队领取任务。领到任务的客户端成为Leader去执行任务,执行完毕后自动离开队列末尾,等待下一次机会。

工作流程:

  1. 多个客户端创建同一个路径下的LeaderSelector,并设置监听器。
  2. 当某个客户端被选为Leader时,它的 takeLeadership(CuratorFramework client) 方法会被调用。
  3. 关键点: 只要 takeLeadership 方法不返回,该客户端就一直是Leader。
  4. 当任务执行完毕,从 takeLeadership 方法返回时,该客户端会立即自动放弃领导权。
  5. 如果你调用了 selector.autoRequeue(),那么这个客户端在放弃领导权后会自动重新加入到选举队列中,等待下一次成为Leader的机会。

代码示例:

public class LeaderSelectorExample {
    public static void main(String[] args) {
        CuratorFramework client = ... // 创建Curator客户端
        LeaderSelector selector = new LeaderSelector(client, "/leader/selector/path", new LeaderSelectorListenerAdapter() {
            @Override
            public void takeLeadership(CuratorFramework client) throws Exception {
                // 这个方法被调用,说明我们成为了Leader
                System.out.println("成为Leader,开始执行关键任务...");
                
                // 模拟执行一个耗时任务
                for (int i = 0; i < 5; i++) {
                    System.out.println("正在处理任务..." + i);
                    Thread.sleep(1000);
                    // 如果此时连接断开,或者这个方法返回,领导权就会被释放
                }
                System.out.println("关键任务执行完毕,自动放弃领导权。");
                // 方法返回,领导权被释放
            }
        });
        
        // 重要:这保证了在执行完一次takeLeadership后,该实例会重新排队等待成为Leader
        selector.autoRequeue();
        selector.start();
        
        // 主线程不需要阻塞,因为LeaderSelector在后台线程中运行takeLeadership
    }
}

如何选择?

选择 LeaderLatch 当:

  • 你需要一个稳定、长期运行的主节点。例如,一个高可用的主数据库,我们希望主节点只要活着就一直是主节点,避免不必要的切换。

选择 LeaderSelector 当:

  • 你有一个需要被单个节点执行,但可以轮流执行的任务。例如,一个每小时运行一次的批处理作业,哪个节点抢到领导权就由哪个节点执行,执行完后大家公平竞争下一次的机会。这提供了天然的负载均衡和容错能力。
  • 它的行为模式非常类似于一个可重入的分布式锁。

总结一下,LeaderLatch 用于选举一个“国王”,国王不死,王位不换。而 LeaderSelector 用于选举一个“值班员”,值完这一班就换下一个人。

评论区请在客户端页面查看