利用zookeeper模擬實現HA高可用
1、需求
在分佈式場景中,對於主從架構來說,最大的問題就是單點故障。當學過zookeeper之後,我們都知道,可以利用zookeeper集羣來幫助實現Hadoop的HA,那到底Hadoop的HA是如何實現的呢?
2、實現思路
zookeeper給我們提供了兩個非常重要的組件:
1、znode系統:提供了存儲關鍵數據的能力
2、監聽機制:提供了監聽感興趣數據變化的能力
利用zookeeper的這兩點能力,我們實現HA
3、具體實現功能
1、當開始啓動namenode的時候,所有剛啓動的namenode都需要去爭搶成爲active的namenode,沒有爭搶成功的則成爲standby的狀態
2、當active的namenode死掉之後,需要剩下的所有的stanby都需要去爭搶成爲active的狀態
4、具體代碼實現
package com.ghgj.zookeeper.zkapp1903;
import org.apache.zookeeper.*;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.ZooDefs.Ids;
import java.util.List;
/**
* 需求:
* 模擬實現HA
* <p>
* 具體的功能:
* 1、當開始啓動namenode的時候,所有剛啓動的namenode都需要去爭搶成爲active的namenode
* 沒有爭搶成功的則成爲standby的狀態
* 2、當active的namenode死掉之後,需要剩下的所有的stanby都需要去爭搶成爲active的狀態
* <p>
* 在這個模擬實現中,假定所有的namenode之間的數據狀態都是同步的。沒有數據狀態差別
* <p>
* 分析實現思路:
* 見代碼註釋
*/
public class NameNodeHA {
// 連接信息
private static final String CONNECT_STR = "hadoop02:2181,hadoop03:2181";
// 會話超時時長 會話建立成功最長的等待時間
private static final int TIME_OUT = 5000;
// 存儲active namenode的父級znode節點
private static final String ACTIVE_PARENT = "/namenode_active";
// 存儲standby namenode的父級znode節點
private static final String STANBY_PARENT = "/namenode_standbys";
// 鎖節點
private static final String LOCK_ZNODE = "/namenode_lock";
// 當前上線的節點名稱
private static final String NAMENODE_HOST = "hadoop02";
static ZooKeeper zookeeper = null;
public static void main(String[] args) throws Exception {
/**
* 獲取連接
*
* 關於監聽器的知識:
* 有兩種添加監聽的方式:
* 1、通過會話對象添加,這個會話對象中的所有的相應都能接收到,都在這個監聽器對象中的process
* 方法中執行業務邏輯的回調
* 在獲取會話的時候添加的監聽是屬於全局監聽,當前這個會話中的任何事件響應,都會回調這個監聽器對象中的
* process方法
*
* 2、在對應的三種添加監聽的方式中,注入自定義的監聽對象,那麼注入的監聽器對象是誰,
* 當事件響應的時候,就回調這個監聽器對象中的process方法
*/
zookeeper = new ZooKeeper(CONNECT_STR, TIME_OUT, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 哪個znode節點
String path = event.getPath();
// 事件的類型
EventType type = event.getType();
// 如果是 ACTIVE_PARENT 的 NodeChildrenChanged 事件
// 當active namenode死掉或者增加都會觸發process回調
if (path.equals(ACTIVE_PARENT) && type == EventType.NodeChildrenChanged) {
try {
// 爭搶成爲active namenode
List<String> onlyAtiveNM = zookeeper.getChildren(ACTIVE_PARENT, null);
if (onlyAtiveNM.size() == 0) {
// 原來的active namenode死掉了
// 正式實現:爭搶成爲active namenode
// 搶鎖: 使用創建一個znode來模式實現搶鎖,誰創建成功就是誰獲取到了這把鎖
// 註冊監聽
// 關注 LOCK_ZNODE 的 NodeCreated 事件
zookeeper.exists(LOCK_ZNODE, true);
// 創建鎖節點
// 觸發了 LOCK_ZNODE 的 NodeCreated 事件
if (zookeeper.exists(LOCK_ZNODE, false) == null) {
zookeeper.create(LOCK_ZNODE, NAMENODE_HOST.getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
}
} else if (onlyAtiveNM.size() == 1) {
// 相當於已經有一個anmenode把自己切換成爲active了
// 所以不需要做什麼操作
}
} catch (Exception e) {
e.printStackTrace();
}
} else if (path.equals(LOCK_ZNODE) && type == EventType.NodeCreated) {
String namenode_lock_znode = null;
try {
// 真正的來判斷,誰創建成功的鎖節點,如果是自己創建成功的,則切換自己的狀態成爲 active
byte[] data = zookeeper.getData(LOCK_ZNODE, false, null);
// namenode_lock_znode
// 這個對象 namenode_lock_znode 就是誰創建成功的那個namenode的節點名稱
namenode_lock_znode = new String(data, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
// 需要判斷,是否是自己創建成功的鎖節點
if (NAMENODE_HOST.equals(namenode_lock_znode)) {
// 是自己創建成功的鎖節點
// 切換自己的狀態
try {
// 首先刪除自己在 STANDBY_PERENT節點下的該表自己的znode
String deletePath = STANBY_PARENT + "/" + NAMENODE_HOST;
if (zookeeper.exists(deletePath, false) != null) {
zookeeper.delete(deletePath, -1);
}
// 再創建一個znode節點在ACTIVE_PARNET下面
String createPath = ACTIVE_PARENT + "/" + NAMENODE_HOST;
zookeeper.create(createPath, NAMENODE_HOST.getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println(NAMENODE_HOST + " 註冊成爲active角色");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
} else {
// 判斷得出結果 鎖節點不是自己創建成功,不要成爲active
// 什麼都不做
}
}
}
});
/**
* 執行各種操作
*/
// 確保兩個父節點存在
if (zookeeper.exists(ACTIVE_PARENT, null) == null) {
zookeeper.create(ACTIVE_PARENT, "storage active namenode data".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
if (zookeeper.exists(STANBY_PARENT, null) == null) {
zookeeper.create(STANBY_PARENT, "storage standby namenode data".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
// 先來判斷是否有active的namenode
List<String> activeNM = zookeeper.getChildren(ACTIVE_PARENT, null);
if (activeNM.size() == 1) {
System.out.println(activeNM.get(0) + " 節點是active角色, 自己 " + NAMENODE_HOST + "成爲standby角色");
// 如果有active的namenode, 則自動成爲 standby的namenode
// 到 STANBY_PARENT 這個znode節點下,創建一個子節點代表當前這個standby namenode
String standByPath = STANBY_PARENT + "/" + NAMENODE_HOST;
zookeeper.create(standByPath, NAMENODE_HOST.getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
// 註冊監聽:關注現在的active namenode 是否死掉
// ACTIVE_PARENT 的 NodeChildrenChanged 事件
// 關心是否 active 的namenode 死掉
zookeeper.getChildren(ACTIVE_PARENT, true);
} else {
// 當發現沒有active的namenode的時候:
// 先爭搶鎖
// 爭搶鎖爭搶到了的話,就切換自己的狀態
// 註冊監聽
// 關注 LOCK_ZNODE 的 NodeCreated 事件
zookeeper.exists(LOCK_ZNODE, true);
// 創建鎖節點
// 觸發了 LOCK_ZNODE 的 NodeCreated 事件
zookeeper.create(LOCK_ZNODE, NAMENODE_HOST.getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println("發現沒有active,去爭搶成爲" + NAMENODE_HOST + " 節點成爲standby節點");
}
/**
* 關閉連接
*/
while (true) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5、執行效果
當hadoop02節點啓動:
當hadoop03啓動的時候:
當hadoop04啓動的時候:
當現在爲active的hadoop02宕機的時候:
當hadoop02被宕機的時候,發現最終,hadoop03和hadoop04爭搶成爲active角色,最後發現hadoop04競爭成功成爲active角色