
介绍
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断开连接)。
工作流程:
- 多个客户端创建并启动同一个路径下的
LeaderLatch。 - 其中一个客户端会成功获得领导权,其
hasLeadership()方法返回true。 - 该客户端会一直保持Leader状态,除非它被关闭 (
close()) 或与ZooKeeper服务器的会话过期。 - 当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去执行任务,执行完毕后自动离开队列末尾,等待下一次机会。
工作流程:
- 多个客户端创建同一个路径下的
LeaderSelector,并设置监听器。 - 当某个客户端被选为Leader时,它的
takeLeadership(CuratorFramework client)方法会被调用。 - 关键点: 只要
takeLeadership方法不返回,该客户端就一直是Leader。 - 当任务执行完毕,从
takeLeadership方法返回时,该客户端会立即自动放弃领导权。 - 如果你调用了
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 用于选举一个“值班员”,值完这一班就换下一个人。
评论区请在客户端页面查看