来到一家新公司,要求做zookeeper注册中心的服务器上线下线的动态感知。结果把要求看成了zookepper注册中心的动态感知。哎,好尴尬呀!结果看到这篇文章我才恍然大悟。讲的也很细致!特发表一篇文章做为收藏。
我们现在有一个分布式应用系统提供服务,服务器有很多台,服务器根据我们的负载会增加或减少,这样服务器就会有动态上下线的情况。这样我们的客户端怎么知道我们的服务器有哪几天,服务器宕机我们怎么知道?
所以我们可以利用zookeeper集群实现这个需求(客户端能实时洞察到服务器上下线的变化)
实现方法
- 我们的服务器一启动的时候就去zookeeper去注册,zookeeper记录注册服务器的IP等信息。
- 去zookeeper注册的节点必须是临时节点,这样当服务器宕机下线的时候,zookeeper会把这个节点删除掉,这样才会产生事件,客户端才能监听到。
- 客户端一启动的时候就去连接zookeeper,然后去getChildren并且注册监听,获取当前在线服务器列表,然后选择服务器进行连接(可以获取到服务器的IP或者服务器的连接数量,这样还可以通过连接数量来进行负载均衡)
- 一旦服务器触发上下线的事件,就会通知到注册监听的客户端,客户端的监听器的process()方法就会调用,然后在process()方法内又可以继续getChildren并且注册监听(重新获取服务器列表并注册监听)
服务端代码
package cn.itcast.bigdata.zkdist;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
public class DistributedServer {
private static final String connectString = "mini1:2181,mini2:2181,mini3:2181";
private static final int sessionTimeout = 2000;
private static final String parentNode = "/servers";
private ZooKeeper zk = null;
/**
* 创建到zk的客户端连接
*
* @throws Exception
*/
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());
}
});
}
/**
* 向zk集群注册服务器信息
*
* @param hostname
* @throws Exception
*/
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);
}
/**
* 业务功能
*
* @throws InterruptedException
*/
public void handleBussiness(String hostname) throws InterruptedException {
System.out.println(hostname + "start working.....");
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.handleBussiness(args[0]);
}
}
客户端代码
package cn.itcast.bigdata.zkdist;
import java.util.ArrayList;
import java.util.List;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
public class DistributedClient {
private static final String connectString = "mini1:2181,mini2:2181,mini3:2181";
private static final int sessionTimeout = 2000;
private static final String parentNode = "/servers";
// 注意:加volatile的意义何在?
private volatile List<String> serverList;
private ZooKeeper zk = null;
/**
* 创建到zk的客户端连接
*
* @throws Exception
*/
public void getConnect() throws Exception {
zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
// 收到事件通知后的回调函数(应该是我们自己的事件处理逻辑)
try {
//重新更新服务器列表,并且注册了监听
getServerList();
} catch (Exception e) {
}
}
});
}
/**
* 获取服务器信息列表
*
* @throws Exception
*/
public void getServerList() throws Exception {
// 获取服务器子节点信息,并且对父节点进行监听
List<String> children = zk.getChildren(parentNode, true);
// 先创建一个局部的list来存服务器信息
List<String> servers = new ArrayList<String>();
for (String child : children) {
// child只是子节点的节点名
byte[] data = zk.getData(parentNode + "/" + child, false, null);
servers.add(new String(data));
}
// 把servers赋值给成员变量serverList,已提供给各业务线程使用
serverList = servers;
//打印服务器列表
System.out.println(serverList);
}
/**
* 业务功能
*
* @throws InterruptedException
*/
public void handleBussiness() throws InterruptedException {
System.out.println("client start working.....");
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
// 获取zk连接
DistributedClient client = new DistributedClient();
client.getConnect();
// 获取servers的子节点信息(并监听),从中获取服务器信息列表
client.getServerList();
// 业务线程启动
client.handleBussiness();
}
}
注意:加volatile的意义何在?
成员变量serverList在堆内存里面,每个线程工作的时候都有自己的工作栈空间,每个线程操作serverList的时候都会拷贝一份副本到自己的工作栈中,然后对副本进行操作,操作完之后再把副本同步回去。如果在副本同步回去的过程中,另一个线程拷贝了一份副本到自己的线程工作栈,这时候这个线程的serverList的服务器列表就会少一个。加了volatile之后,volatile保证了serverList的可见性,也就是说在每个线程中对serverList操作之后,在其他线程中都能立刻看到修改后的值。相当于这些线程都不拷贝副本而是直接去主存中操作serverList。因为现在我们getServerList()方法的线程和业务线程不在同一个线程里面;所以不加volatile的话,业务线程看到的serverList服务列表可能会不一样。
测试
我们可以把客户端打一个jar包,把服务器打一个jar包。
现在我们来启动server.jar,启动服务器mini1:
然后我们再把mini2和mini3的服务器也启动了:
现在可以开启客户端了:
然后我们开启新的mini4服务器,我们客户端也可以查看到服务器上线:
关掉服务器min1之后,客户端也可以查看到服务器下线:
这就是我们服务器的动态上下线感知。