Apache Curator LeaderSelector 深度解析:分布式领导者选举的优雅实现
-
- 一、领导者选举概述
-
- 1.1 什么是领导者选举?
- 1.2 领导者选举的应用场景
- 二、LeaderSelector 核心使用指南
-
- 2.1 环境准备
- 2.2 基础使用示例
- 2.3 核心接口详解
- 三、LeaderSelector 工作原理深度剖析
-
- 3.1 整体架构图
- 3.2 核心原理:基于临时顺序节点
- 3.3 公平性保证
- 3.4 状态管理与异常处理
- 四、高级特性与最佳实践
-
- 4.1 自动重新入队:autoRequeue()
- 4.2 设置参与者 ID
- 4.3 获取当前领导者信息
- 4.4 完整的生产级示例
- 五、LeaderSelector 与 LeaderLatch 的对比
- 六、总结
-
- 6.1 核心要点回顾
- 6.2 完整工作流程图
- 6.3 一句话总结
|
🌺The Begin🌺点点关注,收藏不迷路🌺 |
摘要:在分布式系统中,领导者选举是协调任务执行、避免资源竞争的核心机制。Apache Curator 提供的 LeaderSelector 组件,通过封装 ZooKeeper 的临时顺序节点,为开发者提供了一个优雅、可靠且功能丰富的领导者选举解决方案。本文将深入剖析 LeaderSelector 的使用方法、工作原理以及高级特性,通过流程图和源码级的分析,帮助读者掌握这一分布式协调利器。
一、领导者选举概述
1.1 什么是领导者选举?
在分布式计算中,领导者选举是指从多个节点(进程)中选出一个唯一的节点作为组织者(Leader),负责协调任务或执行特殊操作的过程。选举不仅在系统启动时需要,当领导者意外宕机时,也需要自动选举出新的领导者。
#mermaid-svg-YVaveFDmfJcq8VvO{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YVaveFDmfJcq8VvO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YVaveFDmfJcq8VvO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YVaveFDmfJcq8VvO .error-icon{fill:#552222;}#mermaid-svg-YVaveFDmfJcq8VvO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YVaveFDmfJcq8VvO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YVaveFDmfJcq8VvO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YVaveFDmfJcq8VvO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YVaveFDmfJcq8VvO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YVaveFDmfJcq8VvO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YVaveFDmfJcq8VvO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YVaveFDmfJcq8VvO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YVaveFDmfJcq8VvO .marker.cross{stroke:#333333;}#mermaid-svg-YVaveFDmfJcq8VvO svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YVaveFDmfJcq8VvO p{margin:0;}#mermaid-svg-YVaveFDmfJcq8VvO .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-YVaveFDmfJcq8VvO .cluster-label text{fill:#333;}#mermaid-svg-YVaveFDmfJcq8VvO .cluster-label span{color:#333;}#mermaid-svg-YVaveFDmfJcq8VvO .cluster-label span p{background-color:transparent;}#mermaid-svg-YVaveFDmfJcq8VvO .label text,#mermaid-svg-YVaveFDmfJcq8VvO span{fill:#333;color:#333;}#mermaid-svg-YVaveFDmfJcq8VvO .node rect,#mermaid-svg-YVaveFDmfJcq8VvO .node circle,#mermaid-svg-YVaveFDmfJcq8VvO .node ellipse,#mermaid-svg-YVaveFDmfJcq8VvO .node polygon,#mermaid-svg-YVaveFDmfJcq8VvO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YVaveFDmfJcq8VvO .rough-node .label text,#mermaid-svg-YVaveFDmfJcq8VvO .node .label text,#mermaid-svg-YVaveFDmfJcq8VvO .image-shape .label,#mermaid-svg-YVaveFDmfJcq8VvO .icon-shape .label{text-anchor:middle;}#mermaid-svg-YVaveFDmfJcq8VvO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YVaveFDmfJcq8VvO .rough-node .label,#mermaid-svg-YVaveFDmfJcq8VvO .node .label,#mermaid-svg-YVaveFDmfJcq8VvO .image-shape .label,#mermaid-svg-YVaveFDmfJcq8VvO .icon-shape .label{text-align:center;}#mermaid-svg-YVaveFDmfJcq8VvO .node.clickable{cursor:pointer;}#mermaid-svg-YVaveFDmfJcq8VvO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YVaveFDmfJcq8VvO .arrowheadPath{fill:#333333;}#mermaid-svg-YVaveFDmfJcq8VvO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YVaveFDmfJcq8VvO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YVaveFDmfJcq8VvO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YVaveFDmfJcq8VvO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YVaveFDmfJcq8VvO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YVaveFDmfJcq8VvO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YVaveFDmfJcq8VvO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YVaveFDmfJcq8VvO .cluster text{fill:#333;}#mermaid-svg-YVaveFDmfJcq8VvO .cluster span{color:#333;}#mermaid-svg-YVaveFDmfJcq8VvO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YVaveFDmfJcq8VvO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YVaveFDmfJcq8VvO rect.text{fill:none;stroke-width:0;}#mermaid-svg-YVaveFDmfJcq8VvO .icon-shape,#mermaid-svg-YVaveFDmfJcq8VvO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YVaveFDmfJcq8VvO .icon-shape p,#mermaid-svg-YVaveFDmfJcq8VvO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YVaveFDmfJcq8VvO .icon-shape rect,#mermaid-svg-YVaveFDmfJcq8VvO .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YVaveFDmfJcq8VvO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YVaveFDmfJcq8VvO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YVaveFDmfJcq8VvO :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
重新选举
Leader 故障
多节点集群
节点1Leader
节点2Follower
节点3Follower
节点4Follower
节点1宕机
节点2
节点3
节点4
新 Leader
1.2 领导者选举的应用场景
| 定时任务调度 | 多节点中只选出一个节点执行定时任务 | 分布式定时任务框架 |
| 主备切换 | 实现高可用的主备模式,主节点故障时自动切换 | 数据库主从切换、消息队列控制器 |
| 资源协调 | 协调分布式任务的执行,避免重复工作 | MapReduce 任务分配 |
| 配置管理 | 由领导者负责从配置中心拉取配置并分发 | 配置中心 |
二、LeaderSelector 核心使用指南
2.1 环境准备
首先需要在项目中引入 Curator Recipes 依赖:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.5.0</version> <!– 推荐使用最新稳定版 –>
</dependency>
2.2 基础使用示例
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class LeaderElectionExample {
private static final String ZK_CONNECT_STRING = "localhost:2181";
private static final String LEADER_PATH = "/myapp/leader";
public static void main(String[] args) throws Exception {
// 1. 创建 Curator 客户端
CuratorFramework client = CuratorFrameworkFactory.newClient(
ZK_CONNECT_STRING,
new ExponentialBackoffRetry(1000, 3)
);
client.start();
// 2. 创建 LeaderSelector 实例
LeaderSelector leaderSelector = new LeaderSelector(
client,
LEADER_PATH,
new LeaderSelectorListenerAdapter() {
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
// 当成为领导者时,此方法被调用
System.out.println(Thread.currentThread().getName() + " 成为领导者!");
// 执行领导者任务(此方法必须阻塞,直到想释放领导权)
performLeaderTasks();
System.out.println("领导者任务完成,释放领导权");
}
private void performLeaderTasks() throws InterruptedException {
// 模拟长时间运行的任务
while (true) {
System.out.println("领导者正在执行协调任务…");
Thread.sleep(5000);
// 可根据业务逻辑设置退出条件
}
}
}
);
// 3. 启动选举(非阻塞,自动参与竞选)
leaderSelector.start();
// 保持程序运行
Thread.sleep(Long.MAX_VALUE);
// 4. 关闭资源(实际应用中会在 ShutdownHook 中处理)
leaderSelector.close();
client.close();
}
}
2.3 核心接口详解
| LeaderSelector | 核心选举器 | 管理客户端的选举生命周期 |
| LeaderSelectorListener | 监听器接口 | 定义 takeLeadership 和 stateChanged 方法 |
| LeaderSelectorListenerAdapter | 适配器类 | 提供默认的 stateChanged 实现,推荐使用 |
| start() | 启动选举 | 将当前节点加入候选列表(非阻塞) |
| close() | 关闭选举 | 退出候选列表,释放领导权 |
| autoRequeue() | 自动重入 | 释放领导权后自动重新参与选举 |
| hasLeadership() | 检查领导权 | 返回当前实例是否持有领导权 |
三、LeaderSelector 工作原理深度剖析
3.1 整体架构图
#mermaid-svg-L4m1C5L66Xt0myZA{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-L4m1C5L66Xt0myZA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-L4m1C5L66Xt0myZA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-L4m1C5L66Xt0myZA .error-icon{fill:#552222;}#mermaid-svg-L4m1C5L66Xt0myZA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-L4m1C5L66Xt0myZA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-L4m1C5L66Xt0myZA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-L4m1C5L66Xt0myZA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-L4m1C5L66Xt0myZA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-L4m1C5L66Xt0myZA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-L4m1C5L66Xt0myZA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-L4m1C5L66Xt0myZA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-L4m1C5L66Xt0myZA .marker.cross{stroke:#333333;}#mermaid-svg-L4m1C5L66Xt0myZA svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-L4m1C5L66Xt0myZA p{margin:0;}#mermaid-svg-L4m1C5L66Xt0myZA .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-L4m1C5L66Xt0myZA .cluster-label text{fill:#333;}#mermaid-svg-L4m1C5L66Xt0myZA .cluster-label span{color:#333;}#mermaid-svg-L4m1C5L66Xt0myZA .cluster-label span p{background-color:transparent;}#mermaid-svg-L4m1C5L66Xt0myZA .label text,#mermaid-svg-L4m1C5L66Xt0myZA span{fill:#333;color:#333;}#mermaid-svg-L4m1C5L66Xt0myZA .node rect,#mermaid-svg-L4m1C5L66Xt0myZA .node circle,#mermaid-svg-L4m1C5L66Xt0myZA .node ellipse,#mermaid-svg-L4m1C5L66Xt0myZA .node polygon,#mermaid-svg-L4m1C5L66Xt0myZA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-L4m1C5L66Xt0myZA .rough-node .label text,#mermaid-svg-L4m1C5L66Xt0myZA .node .label text,#mermaid-svg-L4m1C5L66Xt0myZA .image-shape .label,#mermaid-svg-L4m1C5L66Xt0myZA .icon-shape .label{text-anchor:middle;}#mermaid-svg-L4m1C5L66Xt0myZA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-L4m1C5L66Xt0myZA .rough-node .label,#mermaid-svg-L4m1C5L66Xt0myZA .node .label,#mermaid-svg-L4m1C5L66Xt0myZA .image-shape .label,#mermaid-svg-L4m1C5L66Xt0myZA .icon-shape .label{text-align:center;}#mermaid-svg-L4m1C5L66Xt0myZA .node.clickable{cursor:pointer;}#mermaid-svg-L4m1C5L66Xt0myZA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-L4m1C5L66Xt0myZA .arrowheadPath{fill:#333333;}#mermaid-svg-L4m1C5L66Xt0myZA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-L4m1C5L66Xt0myZA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-L4m1C5L66Xt0myZA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-L4m1C5L66Xt0myZA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-L4m1C5L66Xt0myZA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-L4m1C5L66Xt0myZA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-L4m1C5L66Xt0myZA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-L4m1C5L66Xt0myZA .cluster text{fill:#333;}#mermaid-svg-L4m1C5L66Xt0myZA .cluster span{color:#333;}#mermaid-svg-L4m1C5L66Xt0myZA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-L4m1C5L66Xt0myZA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-L4m1C5L66Xt0myZA rect.text{fill:none;stroke-width:0;}#mermaid-svg-L4m1C5L66Xt0myZA .icon-shape,#mermaid-svg-L4m1C5L66Xt0myZA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-L4m1C5L66Xt0myZA .icon-shape p,#mermaid-svg-L4m1C5L66Xt0myZA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-L4m1C5L66Xt0myZA .icon-shape rect,#mermaid-svg-L4m1C5L66Xt0myZA .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-L4m1C5L66Xt0myZA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-L4m1C5L66Xt0myZA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-L4m1C5L66Xt0myZA :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
客户端集群
ZooKeeper
持有
监听
监听
选举流程
是
否
创建临时顺序节点
排序节点列表
是否是最小节点?
成为Leader回调takeLeadership
监听前一个节点
前节点删除
leader节点
lock-000000001临时顺序节点
lock-000000002
lock-000000003
客户端1
客户端2
客户端3
3.2 核心原理:基于临时顺序节点
LeaderSelector 的底层基于 InterProcessMutex(可重入分布式锁)实现,通过 ZooKeeper 的临时顺序节点来完成公平选举。
关键机制:
节点创建:每个参与选举的客户端在指定路径下创建一个临时顺序节点,如 /leader/lock-000000001、/leader/lock-000000002
- 临时节点:客户端会话结束或断开连接时自动删除
- 顺序节点:ZooKeeper 保证节点按创建顺序严格递增编号
领导者判定:所有节点按序号排序,序号最小的节点持有者成为领导者
监听机制:非领导者节点监听比自身序号小的前一个节点(形成链式监听),避免羊群效应
重新选举:当领导者节点被删除(客户端断开或主动释放)时,下一个最小序号的节点会收到通知并成为新领导者
3.3 公平性保证
LeaderSelector 提供的是公平选举——节点按照创建顺序依次获得领导权。这是因为:
- 顺序节点的编号严格反映了参与选举的时间顺序
- 链式监听保证了唤醒的顺序性
- 与 InterProcessMutex 的队列机制一致
3.4 状态管理与异常处理
LeaderSelector 的正确使用离不开对连接状态的处理。LeaderSelectorListenerAdapter 提供了推荐的处理方式:
public class ResilientLeaderListener extends LeaderSelectorListenerAdapter {
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
// 领导者业务逻辑
}
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
// 当连接状态变化时,适配器会处理:
// – 如果状态变为 SUSPENDED 或 LOST,自动抛出 CancelLeadershipException
// – 这会导致当前线程中断,takeLeadership 方法退出
// – 领导权被释放,触发重新选举
super.stateChanged(client, newState);
}
}
关键状态:
| SUSPENDED | 连接挂起,会话可能仍有效 | 建议放弃领导权 |
| LOST | 会话已过期,领导权必然丧失 | 必须放弃领导权 |
| RECONNECTED | 重连成功 | 领导权可能已变更 |
四、高级特性与最佳实践
4.1 自动重新入队:autoRequeue()
默认情况下,当 takeLeadership 方法返回后,该实例会退出选举队列。autoRequeue() 方法可以让实例在释放领导权后自动重新参与选举:
LeaderSelector leaderSelector = new LeaderSelector(client, path, listener);
leaderSelector.autoRequeue(); // 关键配置
leaderSelector.start();
这种方式适用于需要轮流担任领导者的场景,如任务调度轮换。
4.2 设置参与者 ID
通过 setId() 方法可以为参与者设置自定义标识,方便监控和管理:
leaderSelector.setId("application-server-192.168.1.100");
设置后,可以通过 getParticipants() 获取所有参与者的信息。
4.3 获取当前领导者信息
// 获取当前领导者(可能会远程查询,注意性能)
Participant leader = leaderSelector.getLeader();
System.out.println("当前领导者 ID: " + leader.getId());
// 获取所有参与者
Collection<Participant> participants = leaderSelector.getParticipants();
for (Participant p : participants) {
System.out.println(p.getId() + (p.isLeader() ? " (Leader)" : ""));
}
4.4 完整的生产级示例
@Component
public class DistributedTaskCoordinator {
private static final Logger log = LoggerFactory.getLogger(getClass());
private final CuratorFramework client;
private final LeaderSelector leaderSelector;
private final String nodeId;
public DistributedTaskCoordinator(@Value("${zookeeper.connect}") String connectString) {
this.nodeId = generateNodeId();
this.client = createCuratorClient(connectString);
this.leaderSelector = createLeaderSelector();
}
private CuratorFramework createCuratorClient(String connectString) {
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(connectString)
.sessionTimeoutMs(30000)
.connectionTimeoutMs(15000)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
client.start();
return client;
}
private LeaderSelector createLeaderSelector() {
LeaderSelector selector = new LeaderSelector(
client,
"/distributed/task/leader",
new LeaderSelectorListenerAdapter() {
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
log.info("节点 {} 成为领导者,开始执行协调任务", nodeId);
// 启动领导者任务(在独立线程中执行)
startLeaderTasks();
// 阻塞直到主动退出或连接中断
awaitTermination();
log.info("节点 {} 释放领导权", nodeId);
}
}
);
selector.setId(nodeId);
selector.autoRequeue(); // 允许自动重入选举
return selector;
}
private void startLeaderTasks() {
// 启动心跳检测、任务分发等领导者专用任务
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
// 执行领导者任务
performLeadershipDuties();
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
}
@PostConstruct
public void init() {
leaderSelector.start();
log.info("节点 {} 已加入领导者选举", nodeId);
}
@PreDestroy
public void destroy() {
CloseableUtils.closeQuietly(leaderSelector);
CloseableUtils.closeQuietly(client);
log.info("节点 {} 已退出领导者选举", nodeId);
}
private String generateNodeId() {
return "node-" + UUID.randomUUID().toString().substring(0, 8);
}
}
五、LeaderSelector 与 LeaderLatch 的对比
Curator 提供了两种领导者选举实现,它们的适用场景不同:
| 领导权持有方式 | 主动控制(通过 takeLeadership 方法) | 被动持有(调用 await() 阻塞) |
| 自动重入选举 | 支持(通过 autoRequeue()) | 不支持,需重新 start() |
| 领导权释放条件 | takeLeadership 方法返回 | 调用 close() 方法 |
| 适用场景 | 需轮流担任领导者的任务调度 | 需稳定持有领导权的场景 |
| 典型应用 | 分布式定时任务、工作队列 | 主备切换、单次初始化 |
六、总结
6.1 核心要点回顾
| 底层原理 | 基于 ZooKeeper 临时顺序节点,序号最小者当选 |
| 公平性 | 严格按照请求顺序获得领导权 |
| 错误处理 | 通过 stateChanged 响应连接状态,推荐抛出 CancelLeadershipException |
| 关键配置 | autoRequeue() 实现自动重入选举 |
| 与 LeaderLatch 区别 | LeaderSelector 支持领导权轮换,更灵活 |
6.2 完整工作流程图
#mermaid-svg-pVZzOIfnwYicejxO{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pVZzOIfnwYicejxO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pVZzOIfnwYicejxO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pVZzOIfnwYicejxO .error-icon{fill:#552222;}#mermaid-svg-pVZzOIfnwYicejxO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pVZzOIfnwYicejxO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pVZzOIfnwYicejxO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pVZzOIfnwYicejxO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pVZzOIfnwYicejxO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pVZzOIfnwYicejxO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pVZzOIfnwYicejxO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pVZzOIfnwYicejxO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pVZzOIfnwYicejxO .marker.cross{stroke:#333333;}#mermaid-svg-pVZzOIfnwYicejxO svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pVZzOIfnwYicejxO p{margin:0;}#mermaid-svg-pVZzOIfnwYicejxO .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-pVZzOIfnwYicejxO .cluster-label text{fill:#333;}#mermaid-svg-pVZzOIfnwYicejxO .cluster-label span{color:#333;}#mermaid-svg-pVZzOIfnwYicejxO .cluster-label span p{background-color:transparent;}#mermaid-svg-pVZzOIfnwYicejxO .label text,#mermaid-svg-pVZzOIfnwYicejxO span{fill:#333;color:#333;}#mermaid-svg-pVZzOIfnwYicejxO .node rect,#mermaid-svg-pVZzOIfnwYicejxO .node circle,#mermaid-svg-pVZzOIfnwYicejxO .node ellipse,#mermaid-svg-pVZzOIfnwYicejxO .node polygon,#mermaid-svg-pVZzOIfnwYicejxO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pVZzOIfnwYicejxO .rough-node .label text,#mermaid-svg-pVZzOIfnwYicejxO .node .label text,#mermaid-svg-pVZzOIfnwYicejxO .image-shape .label,#mermaid-svg-pVZzOIfnwYicejxO .icon-shape .label{text-anchor:middle;}#mermaid-svg-pVZzOIfnwYicejxO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pVZzOIfnwYicejxO .rough-node .label,#mermaid-svg-pVZzOIfnwYicejxO .node .label,#mermaid-svg-pVZzOIfnwYicejxO .image-shape .label,#mermaid-svg-pVZzOIfnwYicejxO .icon-shape .label{text-align:center;}#mermaid-svg-pVZzOIfnwYicejxO .node.clickable{cursor:pointer;}#mermaid-svg-pVZzOIfnwYicejxO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pVZzOIfnwYicejxO .arrowheadPath{fill:#333333;}#mermaid-svg-pVZzOIfnwYicejxO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pVZzOIfnwYicejxO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pVZzOIfnwYicejxO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pVZzOIfnwYicejxO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pVZzOIfnwYicejxO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pVZzOIfnwYicejxO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pVZzOIfnwYicejxO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pVZzOIfnwYicejxO .cluster text{fill:#333;}#mermaid-svg-pVZzOIfnwYicejxO .cluster span{color:#333;}#mermaid-svg-pVZzOIfnwYicejxO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pVZzOIfnwYicejxO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pVZzOIfnwYicejxO rect.text{fill:none;stroke-width:0;}#mermaid-svg-pVZzOIfnwYicejxO .icon-shape,#mermaid-svg-pVZzOIfnwYicejxO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pVZzOIfnwYicejxO .icon-shape p,#mermaid-svg-pVZzOIfnwYicejxO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pVZzOIfnwYicejxO .icon-shape rect,#mermaid-svg-pVZzOIfnwYicejxO .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pVZzOIfnwYicejxO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pVZzOIfnwYicejxO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pVZzOIfnwYicejxO :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
是
否
是
是
否
否
启动 LeaderSelector
创建临时顺序节点
获取所有子节点
排序节点序号
是否是最小节点?
成为领导者
回调 takeLeadership
方法是否返回?
释放领导权
是否 autoRequeue?
退出选举
监听前一个节点
前节点删除
6.3 一句话总结
Apache Curator 的 LeaderSelector 通过 ZooKeeper 临时顺序节点 + 链式监听实现了公平、可靠的领导者选举,配合 autoRequeue 和 状态监听机制,为分布式任务调度、主备切换等场景提供了开箱即用的解决方案,是构建高可用分布式系统的基石组件。

|
🌺The End🌺点点关注,收藏不迷路🌺 |



