5.3 判例三:业务集群的主节点选举


文档摘要

5.3 判例三:业务集群的主节点选举 本节摘要:让业务集群自己选出唯一主节点:各实例在选举目录下创建临时节点占位,主节点意外退出后由监听者自动补位。本判例对比手写占位版与 Curator 的 LeaderLatch、LeaderSelector 两种风格,并给出"主节点该干什么"的清单。 背景:定时任务谁来做 十台实例组成的推送服务,每晚的对账任务只该跑一份——这与 5.2 的锁判例需求相仿,但语义不同:锁是"一次性资格",选主是长期身份。主节点除了跑任务,通常还负责全局节流、消费分发、状态汇报这类"同一时刻只该有一个角色"的工作。业务选主要求三件事:常态下全集群有且仅有一个主;主挂了秒级补位;补位过程不产生双主。

5.3 判例三:业务集群的主节点选举

本节摘要:让业务集群自己选出唯一主节点:各实例在选举目录下创建临时节点占位,主节点意外退出后由监听者自动补位。本判例对比手写占位版与 Curator 的 LeaderLatch、LeaderSelector 两种风格,并给出"主节点该干什么"的清单。

背景:定时任务谁来做

十台实例组成的推送服务,每晚的对账任务只该跑一份——这与 5.2 的锁判例需求相仿,但语义不同:锁是"一次性资格",选主是长期身份。主节点除了跑任务,通常还负责全局节流、消费分发、状态汇报这类"同一时刻只该有一个角色"的工作。业务选主要求三件事:常态下全集群有且仅有一个主;主挂了秒级补位;补位过程不产生双主。

手写占位版:看清骨架

最小实现只需要一个临时节点加一次 exists 监听:

public class MiniLeader { private final CuratorFramework client; private final String path = "/election/push-master"; public void campaign() throws Exception { try { // 抢座:原子创建,成者为王 client.create().withMode(CreateMode.EPHEMERAL) .forPath(path, InetAddress.getLocalHost().getHostName().getBytes()); onBecomeLeader(); } catch (NodeExistsException e) { // 败者为臣:监听王座,等它空出来 client.checkExists().usingWatcher(evt -> { try { campaign(); } // 王座消失,重新参选 catch (Exception ex) { /* 退避重试 */ } }).forPath(path); } } private void onBecomeLeader() { System.out.println("我是主节点:启动对账调度与全局节流"); // 主节点职责在此挂载;同时要挂自己的连接状态监听, // LOST 时必须停止主节点行为——资格已随会话吊销 } }

骨架成立,但有两个粗糙处:败者重试没有退避(主节点频繁切换时全员疯狂参选);主节点 LOST 后如果旧实例还活着(GC 停顿假死),恢复后的它并不知道"王座已经易主",需要重新走一遍 campaign 并先校验自己的临时节点是否还在。这两个粗糙处,正是 Curator 组件存在的理由。

LeaderLatch 与 LeaderSelector:两种官方配方

Curator 提供两种风格,区别在"任期"的语义。LeaderLatch 是"把门人":所有实例 latch 到同一个门上,门内同时只有一人,start 后通过 hasLeadership() 或监听器得知自己是否当选,close 或挂掉即让出,其余排队者自动补位:

LeaderLatch latch = new LeaderLatch(client, "/election/push-master", "inst-" + myId); latch.addListener(new LeaderLatchListener() { @Override public void isLeader() { startScheduling(); // 当选:启动主节点职责 } @Override public void notLeader() { stopScheduling(); // 让位:必须干净地停掉全部主节点行为 } }); latch.start(); // start 后自动参选,无需手写重试 // 状态查询:运维接口里暴露当前是否主节点 boolean isMaster = latch.hasLeadership();

LeaderSelector 是"轮班制":你申请的任务不绑定身份,而是抢到后执行 takeLeadership 方法,方法返回即让位,且 autoRequeue 可让下岗者自动重新排队。适合"主节点任务做完就该换人"的公平轮转场景:

LeaderSelector selector = new LeaderSelector(client, "/election/push-master", new LeaderSelectorListenerAdapter() { @Override public void takeLeadership(CuratorFramework c) throws Exception { runOneRoundOfSettlement(); // 做一轮主节点工作 Thread.sleep(60_000); // 本轮任期一分钟,返回即让位 } }); selector.autoRequeue(); // 让位后自动重新排队 selector.start();

选型口诀:身份型用 Latch,任务型用 Selector。长期主节点(调度器、节流器)用前者;排队干活(一批批处理轮着来)用后者。两者的底层都是 5.2 的临时顺序节点排号,Curator 在上面补了退避、状态机与让位语义。

图:主节点补位的完整时序

图:主节点补位的完整时序

验证与坑位

验证脚本:起三个实例参选,确认日志只有一个"当选";kill 主实例,观察补位时间(会话超时加参选延迟,通常几秒);最狠的一招——对主实例触发一次 30 秒的 GC 停顿(用压测工具造内存压力),观察它醒来后是否正确让位。三道题都过,选举才算投产合格。

最后一个坑属于"过度设计":给主节点挂太多职责,一次补位要重建连接池、重放积压队列、预热缓存,补位时间从秒级拖到分钟级。主节点职责清单的第一条永远是:能拆就拆,主节点只留真正全局唯一的活

要点回顾

  • 身份型选主用 LeaderLatch:临时节点占位、监听补位、LOST 时干净退位,Curator 补齐了退避与状态机。
  • 任务型轮班用 LeaderSelector:takeLeadership 返回即让位,autoRequeue 自动再排队。
  • 不双主的根基:临时节点删除与监听触发原子衔接,假死复活者必须重验资格。
  • 主节点是协调者:只保留全局唯一职责,补位时间与职责数量成正比。

常见疑问

问:业务选主和 4.2 节 ZooKeeper 自己的选举什么关系? 同源不同层:ZooKeeper 内部选举用完整的 ZAB 规则选日志最新的节点当 Leader;业务选主只是"借它的强一致存储做占位竞争",谁的请求先到谁占位。理解这层区别,就不会把两者混为一谈。

第四个判例回到"名单":实例们如何登记报到,消费者如何拿到一份始终新鲜的名单。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U