实现思路:
一个应用系统中,对外提供服务的服务器有多台,而服务器的数量动态地变化。
客户端每次只能请求一个服务器,因此服务器的变化(上下线)必须通知到客户端,客户端必须知道当前哪些服务器在,哪些服务器不在。
实现方法:
利用zookeeper集群。
服务器方面:服务器启动时到zookeeper上去注册,注册的节点必须为临时节点,因为产生临时节点的客户端一旦断开连接就会被zookeeper自动删除,进而产生事件被客户端感知。
客户端方面:客户端启动后,调用getChildren('/servers/', watch) 获取有哪些机器在线,选择连接数最小的服务器进行连接(做到负载均衡),并且注册监听,一旦有服务器宕机或上线,就会通知到客户端,客户端调用process()函数进行响应(重新获取服务器列表并注册监听)。
具体代码:
1.分布式系统的服务器
import org.apache.zookeeper.ZooKeeper;
public class DistributedServer{
private static final String connectString="192.168.179.200:2181,192.168.179.201:2181,192.168.179.202:2181";
private static final int sessionTimeout = 2000;
private ZooKeeper zk = null;
private static final String parentNode = "/servers";
// 创建到zk的客户端连接
public void getConnect() throws Exception{
zk = new ZooKeeper(connectString,sessionTimeout,new Watcher() {
@Override
public void process(WatchedEvent event) {
// 收到事件通知后的回调函数(事件处理逻辑)
System.out.println(event.getType() + "---" + event.getPath());
try {
zk.getChildren("/", true); //再次绑定监听器,因为监听器只生效一次。这样做可以实现永久的监听
} catch (Exception e) {
}
}
});
}
// 向zk集群注册服务器信息
public void registerServer(String hostname) throws Exception{
String create = zk.create(parentNode + "/server", hostname.getBytes() , Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(hostname + "is online.." + create);
}
// 业务功能
public void handleBusiness() throws InterruptedException{
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception{
// 获取zk连接
DistributedServer server = new DistributedServer();
server.getConnect();
// 利用zk连接注册服务器信息
server.registerServer(args[0]);
// 启动业务功能
server.handleBusiness();
}
}
2.分布式系统的客户端
public class DistributedClient{
private static final String connectString="192.168.179.200:2181,192.168.179.201:2181,192.168.179.202:2181";
private static final int sessionTimeout = 2000;
private volatile List<String> serverList;
private ZooKeeper zk = null;
private static final String parentNode = "/servers";
// 创建到zk的客户端连接
public void getConnect() throws Exception{
zk = new ZooKeeper(connectString,sessionTimeout,new Watcher() {
@Override
public void process(WatchedEvent event) {
// 收到事件通知后的回调函数(事件处理逻辑)
try {
getServerList(); // 更新服务器列表,并且注册了监听
} catch (Exception e) {
}
}
});
}
// 获取服务器信息列表
public void getServerList() throws Exception{
//获取服务器子节点信息,并对父节点进行监听
List<String> children = zk.getChildren(parentNode, true);
//先创建一个局部的list来存服务器信息
List<String> servers = new ArrayList<String>();
for(String child:children){
byte[] data = zk.getData(parentNode+"/"+child, false, null);
servers.add(new String(data));
}
// 把servers赋值给成员变量serverList,提供给各业务线程使用
serveList = servers;
}
// 业务功能
public void handleBusiness() throws InterruptedException{
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception{
// 获取zk连接
DistributedClient client = new DistributedClient();
client.getConnect();
// 获取servers的子节点信息(并监听),从中获取服务器信息列表
client.getServerList();
// 业务线程启动
client.handleBusiness();
}
}
补充:volatile关键字
对象serverList在java的堆内存中。
java程序中有多个线程,每个线程有自己的工作栈空间,若多个线程都要对serverList对象进行修改操作,它们在使用对象serverList时,会对serverList中自己要操作的数据拷贝一个副本到自己的工作栈中,对副本进行相应操作,再把更改后的副本同步到堆内存的serverList对象上。这样可能会导致各个线程所看到的serverList对象是不同的。
如果给对象添加了volatile关键字,则对象不会再被拷贝到线程的工作栈中了,所有线程访问的都是该对象本身,且一个线程对对象操作完成之后才允许下一个线程进行操作。因此每个线程所观察到的serverList对象是同一个版本,这样就保证了该对象在所有线程上的一致性。
效果测试:
1.将DistributedServer.java和DistributedClient.java打成jar包:
找到以上文件所在的包,右击 - Export - Runnable JAR file - Launch configuration(DistributedServer/DistributedClient) - Export destination(C:server.jar) - finish
2.运行jar包:
进入jar包所在的目录 , 运行命令 java -jar server.jar (main方法的参数)