问题描述:服务器是一个集群,客户端可以访问任意一个服务器进行交互,但是假如服务器集群中有一台机器下线,此时若客户端不能感知到服务器的上下线情况,则有可能会向下线的那台服务器发送请求,这样就无法访问服务器。
思路:借助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();
}
}