zookeeper廣泛用於分佈式服務中,比如選舉。這裏簡單介紹下,算是入門。
基本概念
我們知道zookeeper的結構是樹形結構
1.集羣host啓動後的監聽/master節點的刪除事件
2.各服務器host嘗試創建master,成功則把自己的信息存在master上,失敗則讀取master節點信息
3.服務註冊:各服務器host在/serverList節點下創建子節點,並把自己的信息存在節點上
4.服務發現:服務消費者方查詢/serverList子節點列表,獲取服務提供方各個host信息
這個就是利用zookeeper選舉的基本思路。
不過上邊的方案還是有問題的:即驚羣效應。當服務的機器數量比較多的時候,觸發了選舉,此時大量的請求進入zookeeper會產生比較大的壓力。
改進思路還是用zookeeper的臨時順序節點:多個機器同時向該根路徑下創建臨時順序節點,沒搶到Leader的節點都監聽前一個節點的刪除事件,這樣在前一個節點刪除後進行重新搶主。這樣一般只會有一個機器去選舉成爲leader,如果多個機器同時掛掉,參與選主的機器也不會太多。
recipes實現
基於這個思路,recipes提供了兩種實現選舉的方案:LeaderLatch與LeaderSelector。成爲leader後都有對應的Listener回調。
LeaderLatch
LeaderLatch配合LeaderLatchListener使用
示例:
public class LeadLatchMain {
public static void main(String[] args) throws Exception {
List<LeaderLatch> leaders = new ArrayList<LeaderLatch>();
for (int i = 0; i < 10; i++) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("172.16.59.154:2181").sessionTimeoutMs(5000).connectionTimeoutMs(5000).retryPolicy(retryPolicy)
.namespace("base")
.build();
String name = "client" + i;
// 指定客戶端和選舉路徑
LeaderLatch leader = new LeaderLatch(client, "/master");
leader.addListener(new LeaderLatchListener() {
@Override
public void isLeader() {
// 如果成爲leader了,則回調該方法
System.out.println("isLeader " + name + ", Id :" + leader.getOurPath());
try {
List<String> children = client.getChildren().forPath("/master");
children.forEach(c -> System.out.println(c));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void notLeader() {
System.out.println("notLeader " + name);
}
});
leaders.add(leader);
client.start();// 連接zookeeper服務器
leader.start();// 開始參與選舉
}
// checkLeader(leaders);
Thread.sleep(Integer.MAX_VALUE);
}
private static void checkLeader(List<LeaderLatch> leaderLatchList) throws Exception {
//Leader選舉需要時間 等待10秒
Thread.sleep(10000);
for (int i = 0; i < leaderLatchList.size(); i++) {
LeaderLatch leaderLatch = leaderLatchList.get(i);
//通過hasLeadership()方法判斷當前節點是否是leader
if (leaderLatch.hasLeadership()) {
System.out.println("當前leader:" + leaderLatch.getId());
// 釋放leader權限 重新進行搶主
leaderLatch.close();
checkLeader(leaderLatchList);
}
}
}
}
接下來一起看下源碼實現:點開LeaderLatch 的start 方法,進去。
最主要的實現是這個函數
private void checkLeadership(List<String> children) throws Exception {
if ( debugCheckLeaderShipLatch != null ) {
debugCheckLeaderShipLatch.await();
}
final String localOurPath = ourPath.get();
// 節點按編號排序
List<String> sortedChildren = LockInternals.getSortedChildren(LOCK_NAME, sorter, children);
// 獲取當前子節點對應的序號
int ourIndex = (localOurPath != null) ? sortedChildren.indexOf(ZKPaths.getNodeFromPath(localOurPath)) : -1;
if ( ourIndex < 0 ) {
log.error("Can't find our node. Resetting. Index: " + ourIndex);
reset();// 當前節點不在子節點列表內,選舉失敗
}
else if ( ourIndex == 0 ) {
// 當前節點編號最小, 設當前節點爲leader
setLeadership(true);
}
else {
// 沒有成爲leader,因此獲取前一個節點的路徑,並註冊監聽
String watchPath = sortedChildren.get(ourIndex - 1);
Watcher watcher = new Watcher() {
@Override
public void process(WatchedEvent event)
{
// 當且僅當當前節點狀態爲STARTED,且被監聽的前一個節點發生刪除,重新進入getChildren 進行選舉
if ( (state.get() == State.STARTED) && (event.getType() == Event.EventType.NodeDeleted) && (localOurPath != null) ) {
try {
getChildren();
} catch ( Exception ex ) {
ThreadUtils.checkInterrupted(ex);
log.error("An error occurred checking the leadership.", ex);
}
}
}
};
BackgroundCallback callback = new BackgroundCallback() {
@Override
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
// 註冊失敗,則重新進入選舉
if ( event.getResultCode() == KeeperException.Code.NONODE.intValue() ) {
// previous node is gone - reset
reset();
}
}
};
// use getData() instead of exists() to avoid leaving unneeded watchers which is a type of resource leak
client.getData().usingWatcher(watcher).inBackground(callback).forPath(ZKPaths.makePath(latchPath, watchPath));
}
}
LeaderSelector
LeaderSelector與 LeaderSelectorListener,LeaderSelectorListenerAdapter一期使用
public class LeadSelectorMain {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("172.16.59.154:2181").sessionTimeoutMs(5000).connectionTimeoutMs(5000).retryPolicy(retryPolicy)
.namespace("base")
.build();
client.start();// 連接
String name = "client" + i;
// 0.LeaderSelector 的構造參數 LeaderSelectorListener 都會被包裝成 WrappedListener
// 1.此處使用LeaderSelectorListenerAdapter,它實現了stateChanged 函數,當客戶端與zk失連後,拋出 CancelLeadershipException 異常
// 2.WrappedListener 捕獲該異常後,會自動取消領導權
LeaderSelector leaderSelector = new LeaderSelector(client, "/master", new LeaderSelectorListenerAdapter() {
@Override
public void takeLeadership(CuratorFramework client) throws Exception {
// 官方文檔:http://curator.apache.org/getting-started.html
// this callback will get called when you are the leader
// do whatever leader work you need to and only exit
// this method when you want to relinquish leadership
System.out.println(name + " 成爲leader了");// 也就是說,該客戶端成爲Leader後,該方法會被回調
// sleep 10秒
Thread.sleep(10000);
System.out.println(name + " 放棄成爲leader");// 退出 takeLeadership 方法後,就放棄成爲leader
}
});
//放棄領導權之後,自動再次競選
leaderSelector.autoRequeue();// not required, but this is behavior that you will probably expect
leaderSelector.start();
}
System.out.println("log end----------");
Thread.sleep(Integer.MAX_VALUE);
}
}
開發人員只需要在takeLeadership 函數中實現自己的代碼記錄。