轉:http://throwable.coding.me/2018/12/16/zookeeper-curator-usage
Leader選舉
在分布式計算中, leader elections是很重要的一個功能, 這個選舉過程是這樣子的: 指派一個進程作為組織者,將任務分發給各節點。 在任務開始前, 哪個節點都不知道誰是leader(領導者)或者coordinator(協調者). 當選舉算法開始執行后, 每個節點最終會得到一個唯一的節點作為任務leader. 除此之外, 選舉還經常會發生在leader意外宕機的情況下,新的leader要被選舉出來。
在zookeeper集群中,leader負責寫操作,然后通過Zab協議實現follower的同步,leader或者follower都可以處理讀操作。
Curator 有兩種leader選舉的recipe,分別是LeaderSelector和LeaderLatch。
前者是所有存活的客戶端不間斷的輪流做Leader,大同社會。后者是一旦選舉出Leader,除非有客戶端掛掉重新觸發選舉,否則不會交出領導權。某黨?
LeaderLatch
LeaderLatch有兩個構造函數:
public LeaderLatch(CuratorFramework client, String latchPath)
public LeaderLatch(CuratorFramework client, String latchPath, String id)
LeaderLatch的啟動:
leaderLatch.start( );
一旦啟動,LeaderLatch會和其它使用相同latch path的其它LeaderLatch交涉,然后其中一個最終會被選舉為leader,可以通過hasLeadership
方法查看LeaderLatch實例是否leader:
leaderLatch.hasLeadership( ); //返回true說明當前實例是leader
類似JDK的CountDownLatch, LeaderLatch在請求成為leadership會block(阻塞),一旦不使用LeaderLatch了,必須調用close
方法。 如果它是leader,會釋放leadership, 其它的參與者將會選舉一個leader。
public void await() throws InterruptedException,EOFException
/*Causes the current thread to wait until this instance acquires leadership
unless the thread is interrupted or closed.*/
public boolean await(long timeout,TimeUnit unit)throws InterruptedException
異常處理: LeaderLatch實例可以增加ConnectionStateListener來監聽網絡連接問題。 當 SUSPENDED 或 LOST 時, leader不再認為自己還是leader。當LOST后連接重連后RECONNECTED,LeaderLatch會刪除先前的ZNode然后重新創建一個。LeaderLatch用戶必須考慮導致leadership丟失的連接問題。 強烈推薦你使用ConnectionStateListener。
一個LeaderLatch的使用例子:
public class LeaderLatchDemo extends BaseConnectionInfo {
protected static String PATH = "/francis/leader";
private static final int CLIENT_QTY = 10;
public static void main(String[] args) throws Exception {
List<CuratorFramework> clients = Lists.newArrayList();
List<LeaderLatch> examples = Lists.newArrayList();
TestingServer server=new TestingServer();
try {
for (int i = 0; i < CLIENT_QTY; i++) {
CuratorFramework client
= CuratorFrameworkFactory.newClient(server.getConnectString(), new ExponentialBackoffRetry(20000, 3));
clients.add(client);
LeaderLatch latch = new LeaderLatch(client, PATH, "Client #" + i);
latch.addListener(new LeaderLatchListener() {
@Override
public void isLeader() {
// TODO Auto-generated method stub
System.out.println("I am Leader");
}
@Override
public void notLeader() {
// TODO Auto-generated method stub
System.out.println("I am not Leader");
}
});
examples.add(latch);
client.start();
latch.start();
}
Thread.sleep(10000);
LeaderLatch currentLeader = null;
for (LeaderLatch latch : examples) {
if (latch.hasLeadership()) {
currentLeader = latch;
}
}
System.out.println("current leader is " + currentLeader.getId());
System.out.println("release the leader " + currentLeader.getId());
currentLeader.close();
Thread.sleep(5000);
for (LeaderLatch latch : examples) {
if (latch.hasLeadership()) {
currentLeader = latch;
}
}
System.out.println("current leader is " + currentLeader.getId());
System.out.println("release the leader " + currentLeader.getId());
} finally {
for (LeaderLatch latch : examples) {
if (null != latch.getState())
CloseableUtils.closeQuietly(latch);
}
for (CuratorFramework client : clients) {
CloseableUtils.closeQuietly(client);
}
}
}
}
可以添加test module的依賴方便進行測試,不需要啟動真實的zookeeper服務端:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-test</artifactId>
<version>2.12.0</version>
</dependency>
首先我們創建了10個LeaderLatch,啟動后它們中的一個會被選舉為leader。 因為選舉會花費一些時間,start后并不能馬上就得到leader。
通過hasLeadership
查看自己是否是leader, 如果是的話返回true。
可以通過.getLeader().getId()
可以得到當前的leader的ID。
只能通過close
釋放當前的領導權。
await
是一個阻塞方法, 嘗試獲取leader地位,但是未必能上位。
LeaderSelector
LeaderSelector使用的時候主要涉及下面幾個類:
- LeaderSelector
- LeaderSelectorListener
- LeaderSelectorListenerAdapter
- CancelLeadershipException
核心類是LeaderSelector,它的構造函數如下:
public LeaderSelector(CuratorFramework client, String mutexPath,LeaderSelectorListener listener)
public LeaderSelector(CuratorFramework client, String mutexPath, ThreadFactory threadFactory, Executor executor, LeaderSelectorListener listener)
類似LeaderLatch,LeaderSelector必須start
: leaderSelector.start();
一旦啟動,當實例取得領導權時你的listener的takeLeadership()
方法被調用。而takeLeadership()方法只有領導權被釋放時才返回。 當你不再使用LeaderSelector實例時,應該調用它的close方法。
異常處理 LeaderSelectorListener類繼承ConnectionStateListener。LeaderSelector必須小心連接狀態的改變。如果實例成為leader, 它應該響應SUSPENDED 或 LOST。 當 SUSPENDED 狀態出現時, 實例必須假定在重新連接成功之前它可能不再是leader了。 如果LOST狀態出現, 實例不再是leader, takeLeadership方法返回。
重要: 推薦處理方式是當收到SUSPENDED 或 LOST時拋出CancelLeadershipException異常.。這會導致LeaderSelector實例中斷并取消執行takeLeadership方法的異常.。這非常重要, 你必須考慮擴展LeaderSelectorListenerAdapter. LeaderSelectorListenerAdapter提供了推薦的處理邏輯。
下面的一個例子摘抄自官方:
public class LeaderSelectorAdapter extends LeaderSelectorListenerAdapter implements Closeable {
private final String name;
private final LeaderSelector leaderSelector;
private final AtomicInteger leaderCount = new AtomicInteger();
public LeaderSelectorAdapter(CuratorFramework client, String path, String name) {
this.name = name;
leaderSelector = new LeaderSelector(client, path, this);
leaderSelector.autoRequeue();
}
public void start() throws IOException {
leaderSelector.start();
}
@Override
public void close() throws IOException {
leaderSelector.close();
}
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
final int waitSeconds = (int) (5 * Math.random()) + 1;
System.out.println(name + " is now the leader. Waiting " + waitSeconds + " seconds...");
System.out.println(name + " has been leader " + leaderCount.getAndIncrement() + " time(s) before.");
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(waitSeconds));
} catch (InterruptedException e) {
System.err.println(name + " was interrupted.");
Thread.currentThread().interrupt();
} finally {
System.out.println(name + " relinquishing leadership.\n");
}
}
}
你可以在takeLeadership進行任務的分配等等,并且不要返回,如果你想要要此實例一直是leader的話可以加一個死循環。調用 leaderSelector.autoRequeue();
保證在此實例釋放領導權之后還可能獲得領導權。 在這里我們使用AtomicInteger來記錄此client獲得領導權的次數, 它是”fair”, 每個client有平等的機會獲得領導權。
public class LeaderSelectorDemo {
protected static String PATH = "/francis/leader";
private static final int CLIENT_QTY = 10;
public static void main(String[] args) throws Exception {
List<CuratorFramework> clients = Lists.newArrayList();
List<LeaderSelectorAdapter> examples = Lists.newArrayList();
TestingServer server = new TestingServer();
try {
for (int i = 0; i < CLIENT_QTY; i++) {
CuratorFramework client
= CuratorFrameworkFactory.newClient(server.getConnectString(), new ExponentialBackoffRetry(20000, 3));
clients.add(client);
LeaderSelectorAdapter selectorAdapter = new LeaderSelectorAdapter(client, PATH, "Client #" + i);
examples.add(selectorAdapter);
client.start();
selectorAdapter.start();
}
System.out.println("Press enter/return to quit\n");
new BufferedReader(new InputStreamReader(System.in)).readLine();
} finally {
System.out.println("Shutting down...");
for (LeaderSelectorAdapter exampleClient : examples) {
CloseableUtils.closeQuietly(exampleClient);
}
for (CuratorFramework client : clients) {
CloseableUtils.closeQuietly(client);
}
CloseableUtils.closeQuietly(server);
}
}
}
對比可知,LeaderLatch必須調用close()
方法才會釋放領導權,而對于LeaderSelector,通過LeaderSelectorListener
可以對領導權進行控制, 在適當的時候釋放領導權,這樣每個節點都有可能獲得領導權。從而,LeaderSelector具有更好的靈活性和可控性,建議有LeaderElection應用場景下優先使用LeaderSelector。