基于curator实现leader选举

本文介绍了如何利用Netflix Curator在Zookeeper上实现领导者选举。Curator作为高级API抽象,简化了基于Zookeeper的应用开发,提供包括Leader选举在内的多种功能。文章详细比较了通过LeaderSelector和LeaderLatch两种方式实现选举的过程和特点。
摘要由CSDN通过智能技术生成
1.前言
curator由Netflix的工程师开发,主要目的为了基于zookeeper的应用变得简单可靠,在2013年成为apache的顶级项目。curator基于zookeeper,但提供了更高级别的
API抽象以及工具集,并对zookeeper提供的常用功能进行了封装和扩充,例如leader选举、分布式锁、服务发现、缓存等功能,从而使开发者在实现这些功能时不用在实现
哪些无聊的程式化代码段,也减少出错的可能性。

2.本文主要讲解如何利用curator实现leader选举,并对curator提供的两种实现形式进行对比
2.1 通过LeaderSelector进行leader选举
建议通过LeaderSelectorListenerAdapter实现,当某个实例成为leader后,会调用对应实例的takeLeadership方法,此方法执行期间,此实例一直占着leader权,
当takeLeadership方法执行结束,实例自动释放leader权,所有实例重新进行leader选举

package chengf.falcon.curator.leader;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
* @author: 作者: chengaofeng
* @date: 创建时间:2018-06-07 15:26:54
* @Description: TODO
* @version V1.0
*/
public class SelectorClient extends LeaderSelectorListenerAdapter implements Closeable {
private final String name;
private final LeaderSelector leaderSelector;
private final AtomicInteger leaderCount = new AtomicInteger();

public SelectorClient(CuratorFramework client, String path, String name) {
this.name = name;

// 利用一个给定的路径创建一个leader selector
// 执行leader选举的所有参与者对应的路径必须一样
// 本例中SelectorClient也是一个LeaderSelectorListener,但这不是必须的。
leaderSelector = new LeaderSelector(client, path, this);

// 在大多数情况下,我们会希望一个selector放弃leader后还要重新参与leader选举
leaderSelector.autoRequeue();
}

/**
* 启动当前实例参与leader选举
*
* @throws IOException
*/
public void start() throws IOException {
// leader选举是在后台处理的,所以这个方法会立即返回
leaderSelector.start();
}

@Override
public void close() throws IOException {
leaderSelector.close();
}

/**
* 当前实例成为leader时,会执行下面的方法,这个方法执行结束后,当前实例就自动释放leader了,所以在想放弃leader前此方法不能结束
*/
@Override
public void takeLeadership(CuratorFramework client) throws Exception {

final int waitSeconds = (int) (5 * Math.random()) + 1;

System.out.println(name + " 现在是leader了,持续成为leader " + waitSeconds + " 秒.");
System.out.println(name + " 之前已经成为了 " + leaderCount.getAndIncrement() + " 次leader.");
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(waitSeconds));
} catch (InterruptedException e) {
System.err.println(name + " was interrupted.");
Thread.currentThread().interrupt();
} finally {
System.out.println(name + " 释放leader权.\n");
}
}
}


执行类

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package chengf.falcon.curator.leader;

import com.google.common.collect.Lists;
import org.apache.curator.utils.CloseableUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.test.TestingServer;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.List;

public class LeaderSelectorExample {
private static final int CLIENT_QTY = 10;

private static final String PATH = "/examples/leader";

public static void main(String[] args) throws Exception {
// all of the useful sample code is in ExampleClient.java

System.out.println("创建 " + CLIENT_QTY
+ " 个客户端, 公平的参与leader选举,成为leader后,会等待一个随机的时间(几秒中),之后释放leader权,所有实例重新进行leader选举。");

List<CuratorFramework> clients = Lists.newArrayList();
List<SelectorClient> examples = Lists.newArrayList();
TestingServer server = new TestingServer();
try {
for (int i = 0; i < CLIENT_QTY; ++i) {
CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(),
new ExponentialBackoffRetry(1000, 3));
clients.add(client);

SelectorClient example = new SelectorClient(client, PATH, "Client #" + i);
examples.add(example);

client.start();
example.start();
}

System.out.println("按 enter/return 来退出\n");
new BufferedReader(new InputStreamReader(System.in)).readLine();
} finally {
System.out.println("关闭程序...");

for (SelectorClient exampleClient : examples) {
CloseableUtils.closeQuietly(exampleClient);
}
for (CuratorFramework client : clients) {
CloseableUtils.closeQuietly(client);
}

CloseableUtils.closeQuietly(server);
}
}
}

执行结果

创建 10 个客户端, 公平的参与leader选举,成为leader后,会等待一个随机的时间(几秒中),之后释放leader权,所有实例重新进行leader选举。
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
SLF4J: Failed to load class "org.slf4j.impl.StaticMDCBinder".
SLF4J: Defaulting to no-operation MDCAdapter implementation.
SLF4J: See http://www.slf4j.org/codes.html#no_static_mdc_binder for further details.
按 enter/return 来退出

Client #2 现在是leader了,持续成为leader 3 秒.
Client #2 之前已经成为了 0 次leader.
Client #2 释放leader权.

Client #4 现在是leader了,持续成为leader 3 秒.
Client #4 之前已经成为了 0 次leader.
Client #4 释放leader权.

Client #5 现在是leader了,持续成为leader 5 秒.
Client #5 之前已经成为了 0 次leader.
Client #5 释放leader权.

Client #9 现在是leader了,持续成为leader 3 秒.
Client #9 之前已经成为了 0 次leader.
Client #9 释放leader权.

Client #6 现在是leader了,持续成为leader 3 秒.
Client #6 之前已经成为了 0 次leader.
Client #6 释放leader权.

Client #1 现在是leader了,持续成为leader 4 秒.
Client #1 之前已经成为了 0 次leader.
Client #1 释放leader权.

Client #8 现在是leader了,持续成为leader 1 秒.
Client #8 之前已经成为了 0 次leader.
Client #8 释放leader权.

Client #7 现在是leader了,持续成为leader 3 秒.
Client #7 之前已经成为了 0 次leader.
Client #7 释放leader权.

Client #0 现在是leader了,持续成为leader 2 秒.
Client #0 之前已经成为了 0 次leader.
Client #0 释放leader权.

Client #3 现在是leader了,持续成为leader 5 秒.
Client #3 之前已经成为了 0 次leader.
Client #3 释放leader权.

Client #2 现在是leader了,持续成为leader 4 秒.
Client #2 之前已经成为了 1 次leader.
Client #2 释放leader权.

Client #4 现在是leader了,持续成为leader 3 秒.
Client #4 之前已经成为了 1 次leader.
Client #4 释放leader权.

Client #5 现在是leader了,持续成为leader 3 秒.
Client #5 之前已经成为了 1 次leader.
Client #5 释放leader权.

Client #9 现在是leader了,持续成为leader 3 秒.
Client #9 之前已经成为了 1 次leader.
Client #9 释放leader权.

Client #6 现在是leader了,持续成为leader 1 秒.
Client #6 之前已经成为了 1 次leader.
Client #6 释放leader权.

Client #1 现在是leader了,持续成为leader 4 秒.
Client #1 之前已经成为了 1 次leader.
Client #1 释放leader权.

Client #8 现在是leader了,持续成为leader 5 秒.
Client #8 之前已经成为了 1 次leader.
关闭程序...
Client #8 was interrupted.
Client #8 释放leader权.



2.2 通过LeaderLatch实现leader选举,具体实现LeaderLatchListener来实现,
当实例成为leader后,会调用isLeader()方法,之后除非此实例连接不到zookeeper,否侧将一直占着leader权,当失去leader权后会调用notLeader()方法,为了模拟
选举过程,我们追加了一个ScheduledExecutorService来周期性的自己释放leader权

/**
*
*/
package chengf.falcon.curator.leader;

import java.io.IOException;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;

/**
* @author: 作者: chengaofeng
* @date: 创建时间:2018-06-07 15:26:54
* @Description: TODO
* @version V1.0
*/
public class LatchClient implements LeaderLatchListener {

private final String name;

private LeaderLatch leaderLatch;

private CuratorFramework client;
public LatchClient(CuratorFramework client, String path, String name) throws Exception {
this.name = name;
this.client = client;
leaderLatch = new LeaderLatch(client, path);
leaderLatch.addListener(this);
leaderLatch.start();
}

/*
* (non-Javadoc)
*
* @see
* org.apache.curator.framework.recipes.leader.LeaderLatchListener#isLeader(
* )
*/
/**
* 成为leader后会执行下面的方法,方法执行完后不会释放leader权
*/
@Override
public void isLeader() {
System.out.println(name + " is now the leader. ");
}

/**
* 失去leader后执行下面的方法
*/
@Override
public void notLeader() {
System.out.println(name + " is not the leader. ");

}

public void stop() {
try {
leaderLatch.close();
} catch (IOException e) {
}
}

public boolean hasLeadership() {
return leaderLatch.hasLeadership();
}
public CuratorFramework getClient() {
return client;
}

public String getName() {
return name;
}

}


启动类

/**
*
*/
package chengf.falcon.curator.leader;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.test.TestingServer;
import org.apache.curator.utils.CloseableUtils;

import com.google.common.collect.Lists;

/**
* @author: 作者: chengaofeng
* @date: 创建时间:2018-06-07 15:25:47
* @Description: TODO
* @version V1.0
*/
public class LeaderLatchExample {
private static final int CLIENT_QTY = 10;

private static final String PATH = "/examples/leader";

public static void main(String[] args) throws Exception {
// all of the useful sample code is in ExampleClient.java

System.out.println("创建 " + CLIENT_QTY
+ " 个客户端, 公平的参与leader选举,成为leader后,将一直占用此leader权。直到四秒中后主动放弃leader权");


List<CuratorFramework> clients = Lists.newArrayList();
TestingServer server = new TestingServer();
List<LatchClient> examples = Lists.newArrayList();
int i = 0;
try {
for (; i < CLIENT_QTY; ++i) {
CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(),
new ExponentialBackoffRetry(1000, 3));
clients.add(client);

LatchClient example = new LatchClient(client, PATH, "Client #" + i);
examples.add(example);
client.start();

}

// 不调用该方法则某实例成为leader后将一直持续占有leader权
ScheduledExecutorService se = giveUpLeader(examples);

System.out.println("按 enter/return 来退出\n");
new BufferedReader(new InputStreamReader(System.in)).readLine();
se.shutdown();
} finally {
System.out.println("关闭程序...");

for (LatchClient exampleClient : examples) {
exampleClient.stop();
}
for (CuratorFramework client : clients) {
CloseableUtils.closeQuietly(client);
}

CloseableUtils.closeQuietly(server);
}
}

/**
* 主动放弃leader权,并新创建一个实例参与leader选举
* @param examples
*/
private static ScheduledExecutorService giveUpLeader(List<LatchClient> examples) {
ScheduledExecutorService se = Executors.newScheduledThreadPool(1);
se.scheduleAtFixedRate(()->{
examples.stream().filter(t-> t.hasLeadership()).findFirst().ifPresent(t-> {
examples.remove(t);
LatchClient example;
try {
example = new LatchClient(t.getClient(), PATH, t.getName());
examples.add(example);
} catch (Exception e) {

}
//当前实例主动放弃leader权
System.out.println(t.getName() + " 现在主动放弃leader权");
t.stop();});
}, 4, 4, TimeUnit.SECONDS);
return se;
}


}

执行结果

创建 10 个客户端, 公平的参与leader选举,成为leader后,将一直占用此leader权。直到四秒中后主动放弃leader权
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
SLF4J: Failed to load class "org.slf4j.impl.StaticMDCBinder".
SLF4J: Defaulting to no-operation MDCAdapter implementation.
SLF4J: See http://www.slf4j.org/codes.html#no_static_mdc_binder for further details.
按 enter/return 来退出

Client #3 is now the leader.
Client #3 现在主动放弃leader权
Client #2 is now the leader.
Client #2 现在主动放弃leader权
Client #9 is now the leader.
Client #9 现在主动放弃leader权
Client #4 is now the leader.
Client #4 现在主动放弃leader权
Client #1 is now the leader.
Client #1 现在主动放弃leader权
Client #5 is now the leader.
Client #5 现在主动放弃leader权
Client #7 is now the leader.
Client #7 现在主动放弃leader权
Client #8 is now the leader.
Client #8 现在主动放弃leader权
Client #6 is now the leader.
Client #6 现在主动放弃leader权
Client #0 is now the leader.

关闭程序...
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值