問題描述:服務器是一個集羣,客戶端可以訪問任意一個服務器進行交互,但是假如服務器集羣中有一臺機器下線,此時若客戶端不能感知到服務器的上下線情況,則有可能會向下線的那臺服務器發送請求,這樣就無法訪問服務器。
思路:藉助zookeeper監聽服務器上下行動態感知。zookeeper不用關心服務器集羣的業務功能,只需要監聽服務器集羣的上下線即可。
解決方法:
1.服務端啓動時立即註冊信息
服務器一啓動就先在zookeeper中註冊信息,這樣zookeeper就會記錄所有的服務器信息。但是要注意註冊的數據節點必須是短暫節點。因爲只有短暫節點在掛掉的時候,zookeeper會自動刪除該節點,這樣才能產生一個監聽事件。可以將其設置成序列化短暫節點。可以保證每個節點都是唯一的。
2.對於客戶端
a.一啓動就先在zookeeper中getChildren,獲取當前的在線服務器列表。這時請求哪一臺就根據自己的策略請求。因爲下線的服務器已經被剔除列表了,所以不會訪問到下線服務器。
b.在geChildren時註冊一個監聽。這時客戶端就可以監聽到節點的變化(因爲上面提到是短暫節點),就可以感知到上下線的服務器。在感知到信息後,客戶端的process就可以做重新獲取服務器列表並註冊監聽(因爲監聽只能監聽到第一次事件變化,所以需要再次註冊監聽)的處理。
代碼實現:(親測有效,如果出錯,可能是你的zookeeper集羣沒有開啓或是其他不可抗因素!畢竟這種集羣每個人都會遇到不同的問題!)
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/jline/jline -->
<dependency>
<groupId>jline</groupId>
<artifactId>jline</artifactId>
<version>0.9.94</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
<groupId>com.sora</groupId>
<artifactId>com.sora</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
1.服務器端:
package zkdist;
import org.apache.zookeeper.*;
import java.util.concurrent.CountDownLatch;
/**
* 分佈式應用系統上下線動態感知服務端
* **/
public class DistributedSysServer {
private ZooKeeper zk = null;
private static final String connectString="192.168.159.128:2181,192.168.159.130:2181,192.168.159.131:2181";
private static final int sessionTimeout = 10000;
private static final String parentNode = "/servers";
//創建zk客戶端連接
public void getConnect() throws Exception{
final CountDownLatch countDownLatch = new CountDownLatch(1);
zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
public void process(WatchedEvent watchedEvent) {
if (Watcher.Event.KeeperState.SyncConnected.equals(watchedEvent.getState())){
System.out.println("連接成功" + watchedEvent);
countDownLatch.countDown();
}
try {
zk.getChildren("/", true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
if (ZooKeeper.States.CONNECTING.equals(zk.getState())){
System.out.println("連接中");
countDownLatch.await();
}
}
//向zk集羣註冊服務器信息
public void registServer(String hostname) throws Exception {
String create = zk.create(parentNode + "/server", hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(hostname+" is online..."+create);
}
//業務功能
public void handleBusiness(String hostname) throws Exception{
System.out.println(hostname + " start working...");
Thread.sleep(Long.MAX_VALUE);//線程睡眠,可以一直監聽(測試)
}
public static void main(String[] args) throws Exception{
//獲取zk連接
DistributedSysServer server = new DistributedSysServer();
server.getConnect();
//利用zk連接註冊服務器信息
server.registServer(args[0]);
//啓動業務功能,此係統只要能感知服務器上下線即可,因此這裏不用關心,這裏用一個通用的方法模擬
server.handleBusiness(args[0]);
}
}
客戶端:
package zkdist;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 分佈式系統服務器上線動態感知客戶端
* **/
public class DistributedSysClient {
private ZooKeeper zk = null;
private static final String connectString="192.168.159.128:2181,192.168.159.130:2181,192.168.159.131:2181";
private static final int sessionTimeout = 10000;
private static final String parentNode = "/servers";
//注意要加volatile
private volatile List<String> serverList;
//創建zk客戶端連接
public void getConnect() throws Exception{
final CountDownLatch countDownLatch = new CountDownLatch(1);
zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
public void process(WatchedEvent watchedEvent) {
if (Watcher.Event.KeeperState.SyncConnected.equals(watchedEvent.getState())){
System.out.println("連接成功" + watchedEvent);
countDownLatch.countDown();
}
try {
//重新更新服務器列表並且註冊了監聽
getServerList();
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
if (ZooKeeper.States.CONNECTING.equals(zk.getState())){
System.out.println("連接中");
countDownLatch.await();
}
}
//獲取服務器列表並監聽
public void getServerList() throws KeeperException, InterruptedException {
//獲取服務器子節點信息,並且對父節點進行監聽(監聽子節點的上下線)
List<String> children = zk.getChildren(parentNode, true);
//先創建一個局部的list存服務器信息
ArrayList<String> servers = new ArrayList<String>();
//子節點數據變化不需要監聽
for(String child:children){
byte[] data = zk.getData(parentNode + "/" + child, false, null);
servers.add(new String(data));
}
//把servers賦值給volatile成員變量serverList,以提供給各業務線程使用
serverList = servers;
//打印服務器列表信息
System.out.println(serverList.toString());
}
//業務功能
public void handleBusiness() throws Exception{
System.out.println("client start working ");
Thread.sleep(Long.MAX_VALUE);//線程睡眠,可以一直監聽(測試)
}
public static void main(String[] args) throws Exception {
/**
* 邏輯:
* 客戶機啓動後先連接zk,然後獲取服務列表,業務線程啓動等待(線程睡眠,持續監聽),
* 當監聽到事件時(節點發生變化),process會被調用(因爲在獲取服務列表的時候對父節點註冊了監聽),
* process再次調用獲取服務列表接口,就做到了重新更新列表並監聽,
* 執行業務邏輯......(循環下去)
* **/
//獲取zk連接
DistributedSysClient client = new DistributedSysClient();
client.getConnect();
//獲取servers的子節點信息並監聽,c從中獲取服務器信息列表
client.getServerList();
//業務功能啓動
client.handleBusiness();
}
}