在實際生產中,特別是分佈式系統中,我們經常遇到這樣的場景:一個複雜的任務,近需要從分佈式機器中選出一臺機器來執行。諸如此類的問題,我們統稱爲“Master選舉”。比如,在分佈式系統中很常見的一個問題就是定時任務的執行。如果多臺機器同時執行相同的定時任務,業務複雜則可能出現災難性的後果。本篇博客就以定時任務爲例來示例說明Curator的Master選舉用法。
原理
利用zookeeper來實現Master選舉的基本思路如下:
選擇一個根節點(與其他業務隔離),比如/jobMaster,多臺機器同時在此節點下面創建一個子節點/jobMaster/lock,zookeeper保證了最終只有一臺機器能夠創建成功,那麼這臺機器將成爲Master。由它來執行業務操作。
Curator所做的事情就是將上面的思路進行了封裝,把原生API的節點創建、事件監聽和自動選舉進行整合封裝,提供了一套簡單易用的解決方案。
在實際生產中,特別是分佈式系統中,我們經常遇到這樣的場景:一個複雜的任務,近需要從分佈式機器中選出一臺機器來執行。諸如此類的問題,我們統稱爲“Master選舉”。比如,在分佈式系統中很常見的一個問題就是定時任務的執行。如果多臺機器同時執行相同的定時任務,業務複雜則可能出現災難性的後果。本篇博客就以定時任務爲例來示例說明Curator的Master選舉用法。
原理
利用zookeeper來實現Master選舉的基本思路如下:
選擇一個根節點(與其他業務隔離),比如/jobMaster,多臺機器同時在此節點下面創建一個子節點/jobMaster/lock,zookeeper保證了最終只有一臺機器能夠創建成功,那麼這臺機器將成爲Master。由它來執行業務操作。
Curator所做的事情就是將上面的思路進行了封裝,把原生API的節點創建、事件監聽和自動選舉進行整合封裝,提供了一套簡單易用的解決方案。
構造方法
public LeaderLatch(CuratorFramework client, String latchPath)
public LeaderLatch(CuratorFramework client, String latchPath, String id)
示例代碼
package com.secbro.learn.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.CloseableUtils;
import java.util.ArrayList;
import java.util.List;
public class LeaderLatchTest {
private static final String PATH = "/demo/leader";
public static void main(String[] args) {
List<LeaderLatch> latchList = new ArrayList<>();
List<CuratorFramework> clients = new ArrayList<>();
try {
for (int i = 0; i < 10; i++) {
CuratorFramework client = getClient();
clients.add(client);
final LeaderLatch leaderLatch = new LeaderLatch(client, PATH, "client#" + i);
leaderLatch.addListener(new LeaderLatchListener() {
@Override
public void isLeader() {
System.out.println(leaderLatch.getId() + ":I am leader. I am doing jobs!");
}
@Override
public void notLeader() {
System.out.println(leaderLatch.getId() + ":I am not leader. I will do nothing!");
}
});
latchList.add(leaderLatch);
leaderLatch.start();
}
Thread.sleep(Integer.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
} finally {
for(CuratorFramework client : clients){
CloseableUtils.closeQuietly(client);
}
for(LeaderLatch leaderLatch : latchList){
CloseableUtils.closeQuietly(leaderLatch);
}
}
}
private static CuratorFramework getClient() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181")
.retryPolicy(retryPolicy)
.sessionTimeoutMs(6000)
.connectionTimeoutMs(3000)
.namespace("demo")
.build();
client.start();
return client;
}
}
打印結果:
client#6:I am leader. I am doing jobs!
重複執行幾次會發現是不同的client獲得leader。
本示例啓動了10個client,程序會隨機選中其中一個作爲leader。通過註冊監聽的方式來判斷自己是否成爲leader。調用close()方法釋放當前領導權。
LeaderLatch通過增加了一個ConnectionStateListener監聽連接問題。如果出現SUSPENDED或者LOST,leader會報告自己不再是leader(直到重新建立連接,否則不會有leader)。如果LOST的連接被重新建立即RECONNECTED,leaderLatch會刪除先前的zNode並重新建立zNode。
Leader Election
通過LeaderSelectorListener可以對領導權進行控制, 在適當的時候釋放領導權,這樣每個節點都有可能獲得領導權。 而LeaderLatch則一直持有leadership, 除非調用close方法,否則它不會釋放領導權。
構造方法
public LeaderSelector(CuratorFramework client, String leaderPath, LeaderSelectorListener listener)
public LeaderSelector(CuratorFramework client, String leaderPath, ThreadFactory threadFactory, Executor executor, LeaderSelectorListener listener)
示例代碼
package com.secbro.learn.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
import org.apache.curator.framework.recipes.leader.LeaderSelector;
import org.apache.curator.framework.recipes.leader.LeaderSelectorListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.utils.CloseableUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
public class LeaderSelectorTest {
private static final String PATH = "/demo/leader";
public static void main(String[] args) {
List<LeaderSelector> selectors = new ArrayList<>();
List<CuratorFramework> clients = new ArrayList<>();
try {
for (int i = 0; i < 10; i++) {
CuratorFramework client = getClient();
clients.add(client);
final String name = "client#" + i;
LeaderSelector leaderSelector = new LeaderSelector(client, PATH, new LeaderSelectorListener() {
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
System.out.println(name + ":I am leader.");
Thread.sleep(2000);
}
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
}
});
leaderSelector.autoRequeue();
leaderSelector.start();
selectors.add(leaderSelector);
}
Thread.sleep(Integer.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
} finally {
for(CuratorFramework client : clients){
CloseableUtils.closeQuietly(client);
}
for(LeaderSelector selector : selectors){
CloseableUtils.closeQuietly(selector);
}
}
}
private static CuratorFramework getClient() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181")
.retryPolicy(retryPolicy)
.sessionTimeoutMs(6000)
.connectionTimeoutMs(3000)
.namespace("demo")
.build();
client.start();
return client;
}
}
執行結果,控制檯不停打印:
client#5:I am leader.
client#8:I am leader.
client#3:I am leader.
client#1:I am leader.
client#4:I am leader.
client#0:I am leader.
本示例創建了10個LeaderSelector並對起添加監聽,當被選爲leader之後,調用takeLeadership方法進行業務邏輯處理,處理完成即釋放領導權。其中autoRequeue()方法的調用確保此實例在釋放領導權後還可能獲得領導權。
LeaderSelectorListener類繼承了ConnectionStateListener。一旦LeaderSelector啓動,它會向curator客戶端添加監聽器。 使用LeaderSelector必須時刻注意連接的變化。一旦出現連接問題如SUSPENDED,curator實例必須確保它可能不再是leader,直至它重新收到RECONNECTED。如果LOST出現,curator實例不再是leader並且其takeLeadership()應該直接退出。
推薦的做法是,如果發生SUSPENDED或者LOST連接問題,最好直接拋CancelLeadershipException,此時,leaderSelector實例會嘗試中斷並且取消正在執行takeLeadership()方法的線程。 建議擴展LeaderSelectorListenerAdapter, LeaderSelectorListenerAdapter中已經提供了推薦的處理方式 。
總結
Curator提供了兩種方法來實現Leader選舉,根據不同的業務場景可選擇不同的方式。總之,Curator幫住開發人員封裝了很多繁雜的操作,使得實現一個Leader選舉變得輕而易舉。