Zookeeper實現哨兵機制

master選舉使用場景及結構
 現在很多時候我們的服務需要7*24小時工作,假如一臺機器掛了,我們希望能有其它機器頂替它繼續工作。此類問題現在多采用master-salve模式,也就是常說的主從模式,正常情況下主機提供服務,備機負責監聽主機狀態,當主機異常時,可以自動切換到備機繼續提供服務(這裏有點兒類似於數據庫主庫跟備庫,備機正常情況下只監聽,不工作),這個切換過程中選出下一個主機的過程就是master選舉。
對於以上提到的場景,傳統的解決方式是採用一個備用節點,這個備用節點定期給當前主節點發送ping包,主節點收到ping包後會向備用節點發送應答ack,當備用節點收到應答,就認爲主節點還活着,讓它繼續提供服務,否則就認爲主節點掛掉了,自己將開始行使主節點職責。如圖1所示:

Maven依賴信息

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
	<dependency>
		<groupId>com.101tec</groupId>
		<artifactId>zkclient</artifactId>
		<version>0.10</version>
		<exclusions>
			<exclusion>
				<artifactId>slf4j-api</artifactId>
				<groupId>org.slf4j</groupId>
			</exclusion>
			<exclusion>
				<artifactId>log4j</artifactId>
				<groupId>log4j</groupId>
			</exclusion>
			<exclusion>
				<artifactId>slf4j-log4j12</artifactId>
				<groupId>org.slf4j</groupId>
			</exclusion>
		</exclusions>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
</dependencies>

IndexController

@RestController
public class IndexController {
	// 獲取服務信息
	@RequestMapping("/getServerInfo")
	public String getServerInfo() {
		return ElectionMaster.isSurvival ? "當前服務器爲主節點" : "當前服務器爲從節點";
	}
}

MyApplicationRunner

@Component
public class MyApplicationRunner implements ApplicationRunner {

	// 創建zk連接
	ZkClient zkClient = new ZkClient("127.0.0.1:2181");
	private String path = "/election";
	@Value("${server.port}")
	private String serverPort;

	public void run(ApplicationArguments args) throws Exception {
		System.out.println("項目啓動完成...");
		createEphemeral();
		// 創建事件監聽
		zkClient.subscribeDataChanges(path, new IZkDataListener() {

		// 節點被刪除
		public void handleDataDeleted(String dataPath) throws Exception {
			// 主節點已經掛了,重新選舉
			System.out.println("主節點已經掛了,重新開始選舉");
			createEphemeral();
		}

		public void handleDataChange(String dataPath, Object data) throws Exception {

		}
	});

}

	private void createEphemeral() {
		try {
			zkClient.createEphemeral(path, serverPort);
			ElectionMaster.isSurvival = true;
			System.out.println("serverPort:" + serverPort + ",選舉成功....");
		} catch (Exception e) {
			ElectionMaster.isSurvival = false;
		}
	}

}

ElectionMaster

@Component
public class ElectionMaster {

	// 服務器info信息 是否存活
	public static boolean isSurvival;

}

Zookeeper實現分佈式配置中心

什麼是分佈式配置中心
項目中配置文件比較繁雜,而且不同環境的不同配置修改相對頻繁,每次發佈都需要對應修改配置,如果配置出現錯誤,需要重新打包發佈,時間成本較高,因此需要做統一的分佈式註冊中心,能做到自動更新配置文件信息,解決以上問題。

常用分佈式配置中心框架
首選爲disconf,可支持KV存儲以及配置文件形式存儲,使用和開發更爲簡便。並且本身也是基於zookpeer的分佈式配置中心開發,方便部署使用,並且支持實時更新通知操作,但是部署相對複雜。
Diamond(daɪəmənd)基本可以放棄,一般做KV的存儲配置項,做配置文件不是很好的選擇。
Spring Cloud Config因爲依賴git,使用侷限性較大,需要在各個環境中安裝git,並且不支持KV存儲,功能方面略差於disconf
分佈式配置中心實現原理

註冊中心 配置存儲 時效性 數據模型 維護性 優點 缺點
disconf zookpeer 實時推送 支持傳統的配置文件模式,亦支持KV結構數據 提供界面操作 基於分佈式的Zookeeper來實時推送穩定性、實效性、易用性上均優於其他 源碼較多,閱讀和使用起來相對較複雜
zookpeer zookpeer 實時推送 支持傳統的配置文件模式,亦支持KV結構數 命令操作 實時推送穩定性、實效性 開發量大
diamond mysql 每隔15s拉一次全量數據 只支持KV結構的數據 提供界面操 簡單、可靠、易用 數據模型不支持文件,使用不方便
Spring Cloud Config git 人工批量刷新 文件模式 git操作 簡單、可靠、易用 需要依賴GIT,並且更新GIT

基於Zookeeper實現分佈式配置中心
Maven依賴信息

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
	<dependency>
		<groupId>com.101tec</groupId>
		<artifactId>zkclient</artifactId>
		<version>0.10</version>
		<exclusions>
			<exclusion>
				<artifactId>slf4j-api</artifactId>
				<groupId>org.slf4j</groupId>
			</exclusion>
			<exclusion>
				<artifactId>log4j</artifactId>
				<groupId>log4j</groupId>
			</exclusion>
			<exclusion>
				<artifactId>slf4j-log4j12</artifactId>
				<groupId>org.slf4j</groupId>
			</exclusion>
		</exclusions>
	</dependency>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-lang3</artifactId>
	</dependency>
</dependencies>

application.yml

test:
key: demo

ConfigUtils
讀取配置文件信息

@Data
@Component
public class ConfigUtils {
	@Value("${test.key}")
	private String testKey;
}

MyApplicationRunner
項目啓動創建zk節點

@Component
public class MyApplicationRunner extends BaseZookeeper implements ApplicationRunner {
	@Autowired
	private ConfigUtils configUtils;
	// 啓動後執行方法
	public void run(ApplicationArguments args) throws Exception {
		System.out.println("項目啓動成功...");
		String testValue = configUtils.getTestKey();
		String testKey = "/testKey";
		try {
			// 創建節點信息
			zkClient.createEphemeral(testKey, testValue);
		} catch (Exception e) {
			e.printStackTrace();
		}
		zkClient.subscribeDataChanges(testKey, new IZkDataListener() {
			public void handleDataDeleted(String dataPath) throws Exception {
			}
			// 當值發生變化的時候
			public void handleDataChange(String dataPath, Object data) throws Exception {
				System.out.println("dataPath:" + dataPath + ",data:" + data);
				final String strData = (String) data;
				configUtils.setTestKey(strData);
			}
		});

	}

}

UpdateInfoService
修改Zookeeper節點信息

@Service
public class UpdateInfoService extends BaseZookeeper {
	public String updateInfo(String key, String value) {
		try {
			zkClient.writeData("/" + key, value);
			return "success";
		} catch (Exception e) {
			return "fail";
		}
	}
}

IndexController
獲取配置文件信息

@RestController
public class IndexController {
	@Autowired
	private ConfigUtils configUtils;
	@Autowired
	private UpdateInfoService updateInfoService;

	@RequestMapping("/getInfo")
	public String getInfo() {
		return configUtils.getTestKey();
	}

	@RequestMapping("/updateInfo")
	public String updateInfo(String key, String value) {
		String updateInfo = updateInfoService.updateInfo(key, value);
		return updateInfo;
	}
}

Zookeeper集羣選舉策略

Zookeeper集羣選舉原理
Zookeeper的角色

1.領導者(leader),負責進行投票的發起和決議,更新系統狀態
2.學習者(learner),包括跟隨者(follower)和觀察者(observer),follower用於接受客戶端請求並想客戶端返回結果,在選主過程中參與投票
3.Observer可以接受客戶端連接,將寫請求轉發給leader,但observer不參加投票過程,只同步leader的狀態,observer的目的是爲了擴展系統,提高讀取速度
4.客戶端(client),請求發起方Zookeeper的核心是原子廣播,這個機制保證了各個Server之間的同步。實現這個機制的協議叫做Zab協議。Zab協議有兩種模式,它們分別是恢復模式(選主)和廣播模式(同步)。當服務啓動或者在領導者崩潰後,Zab就進入了恢復模式,當領導者被選舉出來,且大多數Server完成了和leader的狀態同步以後,恢復模式就結束了。狀態同步保證了leader和Server具有相同的系統狀態。

爲了保證事務的順序一致性,zookeeper採用了遞增的事務id號(zxid)來標識事務。所有的提議(proposal)都在被提出的時候加上了zxid。實現中zxid是一個64位的數字,它高32位是epoch用來標識leader關係是否改變,每次一個leader被選出來,它都會有一個新的epoch,標識當前屬於那個leader的統治時期。低32位用於遞增計數。

Zookeeper的讀寫機制
 » Zookeeper是一個主多個server組成的集羣
 » 一個leader,多個follower
 » 每個server保存一份數據副本
 » 全局數據一致
 » 分佈式讀寫
 » 更新請求轉發,由leader實施
Zookeeper的保證
» 更新請求順序進行,來自同一個client的更新請求按其發送順序依次執行
» 數據更新原子性,一次數據更新要麼成功,要麼失敗
» 全局唯一數據視圖,client無論連接到哪個server,數據視圖都是一致的
» 實時性,在一定事件範圍內,client能讀到最新數據

Zookeeper節點數據操作流程
Zookeeper leader 選舉

Zookeeper leader 選舉

半數通過
    – 3臺機器 掛一臺 2>3/2
    – 4臺機器 掛2臺 2!>4/2
 • A提案說,我要選自己,B你同意嗎?C你同意嗎?B說,我同意選A;C說,我同意選A。(注意,這裏超過半數了,其實在現實世界選舉已經成功了。

但是計算機世界是很嚴格,另外要理解算法,要繼續模擬下去。)
  • 接着B提案說,我要選自己,A你同意嗎;A說,我已經超半數同意當選,你的提案無效;C說,A已經超半數同意當選,B提案無效。
  • 接着C提案說,我要選自己,A你同意嗎;A說,我已經超半數同意當選,你的提案無效;B說,A已經超半數同意當選,C的提案無效。
• 選舉已經產生了Leader,後面的都是follower,只能服從Leader的命令。而且這裏還有個小細節,就是其實誰先啓動誰當頭。

Zookeeper 集羣環境搭建  
1.安裝jdk運行jdk環境
上傳jdk1.8安裝包

2.安裝jdk1.8環境變量

vi /etc/profile
export JAVA_HOME=/usr/local/jdk1.8.0_181
export ZOOKEEPER_HOME=/usr/local/zookeeper
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$JAVA_HOME/bin:$ZOOKEEPER_HOME/bin:$PATH

刷新profile文件
source /etc/profile

關閉防火牆

3.下載zookeeper安裝包

wget https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz

4.解壓Zookeeper安裝包

tar -zxvf zookeeper-3.4.10.tar.gz 

5.修改Zookeeper文件夾名稱

重命名: mv zookeeper-3.4.10 zookeeper
  1. 修改zoo_sample.cfg文件

    cd /usr/local/zookeeper/conf
    mv zoo_sample.cfg zoo.cfg
    修改conf: vi zoo.cfg 修改兩處
    (1) dataDir=/usr/local/zookeeper/data(注意同時在zookeeper創建data目錄)
    (2)最後面添加
    server.0=192.168.212.154:2888:3888
    server.1=192.168.212.156:2888:3888
    server.2=192.168.212.157:2888:3888
    修改Zookeeper文件夾名稱

  2. 創建服務器標識
    服務器標識配置:
    創建文件夾: mkdir data
    創建文件myid並填寫內容爲0: vi
    myid (內容爲服務器標識 : 0)

複製zookeeper
進行復制zookeeper目錄到hadoop01和hadoop02
還有/etc/profile文件
把hadoop01、 hadoop02中的myid文件裏的值修改爲1和2
路徑(vi /usr/local/zookeeper/data/myid)
啓動zookeeper
啓動zookeeper:
路徑: /usr/local/zookeeper/bin
執行: zkServer.sh start
(注意這裏3臺機器都要進行啓動)
狀態: zkServer.sh
status(在三個節點上檢驗zk的mode,一個leader和倆個follower)
常用命令
zkServer.sh status 查詢狀態

關閉所有防火牆
systemctl stop firewalld

/usr/local/jdk1.8.0_181

JAVA_HOME=/usr/local/jdk1.8.0_181
CLASSPATH=$JAVA_HOME/lib/
PATH=$PATH:$JAVA_HOME/bin
export JAVA_HOME CLASSPATH PATH

/etc/profile

export ZOOKEEPER_HOME=/usr/local/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin:$ZOOKEEPER_HOME/conf
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章