#1:业务需求
客户端需要及时的知道当前服务器集群中那些服务器在线以及服务器的负载线程数。 并且取其中线程负载最小的服务器进行socket连接。
#2:实现思路
- 1.)服务器启动的时候就需要在zookeeper哪里注册,并且将自己的线程数设置为0
- 2.)client启动的时候,就像zookeeper注册一个监听器,监听服务器的在线情况和线程数变化情况。
- 3.)只要服务器上下线或者线程数改变,都会出发客户端注册的监听器,此时客户端就会去zookeeper获取当前在线服务器以及线程数。
#3:设计流程图
#4:实现的关键代码
服务器上面创建zookeeper记录信息和后期线程数的修改
if(path == null){
/**
* 刚上线,没有对应的数据,所以需要先创建一个数据点。
* 返回这个路径很重要,因为后续的要对这个字段的线程数进行修改。
*/
path = zk.create("/servers/" + hostName, (threadCount+"").getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
}else{
//这里是线程数改变的时候用来修改线程数的。
zk.setData(path, (threadCount+"").getBytes("utf-8"), -1);
}
客户端分别为服务器上下线和服务器的线程数各自注册监听器
//实现了服务器上下线通知
List<String> serversList = zk.getChildren("/servers", new GetChildrenEvent());
//每次要和服务器连接的时候,就根据服务器的在线列表去查相关的信息。
其中利用到了自己定义的一个“数据类结构”并且实现排序接口。
#5:具体代码实现
ZookeeperClient.java
package org.zookeeper.com.findserver;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.zookeeper.com.findserver_cacheserver.HostAndThreadNum;
import org.apache.zookeeper.ZooKeeper;
public class ZookeeperClientOf_JunHeng{
private ZooKeeper zk = null;
private List<String> onlineServers = null;
private static String BastGoodHost = null;
public void connectTozk() throws IOException, KeeperException, InterruptedException{
zk = new ZooKeeper("hadoop1:2181", 2000, new ConnectionEvent());
}
public void getServers() throws KeeperException, InterruptedException{
if((zk.exists("/servers", null) != null)){
//实现了服务器上下线通知
onlineServers = zk.getChildren("/servers", new GetChildrenEvent());
}else{
zk.create("/servers", "".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(zk.getChildren("/servers", new GetChildrenEvent()));
}
}
/**
* [@return](https://my.oschina.net/u/556800)
* 每次访问服务器之前,拿之前存下来的服务器列表去zookeeper哪里查询,
* 拿到所有的服务器对应的线程,排序拿最小线程的服务器访问
*/
public String getMinThreadServer(){
if(onlineServers.size() > 0){
LinkedList<HostAndThreadNum> hostAndThreadNums = new LinkedList<HostAndThreadNum>();
for(String hostName:onlineServers){
if(hostName != null){
//zk.getData这个不要监听,否则的话会使得监听器极限上升,反而会大大的增加了zookeeper的访问负担。
String threadMun = null;
try {
threadMun = new String(zk.getData("/servers/" + hostName, null, null));
} catch (KeeperException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
hostAndThreadNums.add(new HostAndThreadNum(hostName, Integer.valueOf(threadMun)));
}
}
Collections.sort(hostAndThreadNums, new HostAndThreadNum());
BastGoodHost = hostAndThreadNums.get(0).getHost();
System.out.println("服务器线程负载有变化,向zookeeper请求得到的最有服务器==>:" + BastGoodHost);
}
return null;
}
public static void main(String[] args) throws InterruptedException, IOException, KeeperException {
ZookeeperClientOf_JunHeng zookeeperClient = new ZookeeperClientOf_JunHeng();
zookeeperClient.connectTozk();
zookeeperClient.getServers();
ArrayList<Socket> sockets = new ArrayList<>();
while (true) {
Thread.sleep(1000);
if(BastGoodHost != null){
sockets.add(new Socket(BastGoodHost, 4700));
}
}
}
//下面为一些内部类进行监听器的作用///
class ConnectionEvent implements Watcher{
@Override
public void process(WatchedEvent event) {
System.out.println("创建连接成功!!");
}
}
/**
* [@author](https://my.oschina.net/arthor) liufu
* 如果服务器上下线了,就去更新本地存储的在线服务器列表
*/
class GetChildrenEvent implements Watcher{
@Override
public void process(WatchedEvent event) {
try {
getServers();
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
public static String getBastGoodHost() {
return BastGoodHost;
}
public static void setBastGoodHost(String bastGoodHost) {
BastGoodHost = bastGoodHost;
}
}
ZookeeperServer.java
package org.zookeeper.com.findserver;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
public class ZookeeperServerOf_JunHeng implements Runnable{
private final String ZKSERVERS = "hadoop1:2181";
private ZooKeeper zk = null;
private int threadCount = 0;
private ServerSocket server = null;
private String path = null;
private String myHostName = null;
private Socket socket = null;
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
ZookeeperServerOf_JunHeng zookeeperServer = new ZookeeperServerOf_JunHeng();
//机器一启动就先向zookeeper服务器发送本机的状态,以及机器的名称和线程数。
@SuppressWarnings("resource")
Scanner scanner = new Scanner(System.in);
System.out.println("请输入主机IP");
zookeeperServer.setMyHostName(scanner.nextLine());
zookeeperServer.connectZookeeper();
//机器第一次运行,所以要先到zookeeper哪里注册,线程数为0.返回信息存放的路径,后续可以通过这个字段来修改线程数。
zookeeperServer.setPath(zookeeperServer.sendMsg(zookeeperServer.getMyHostName(), 0));
//最后启动本机的相关业务逻辑
zookeeperServer.startServer();
zookeeperServer.acceptConnection();
}
/**
* @throws IOException
* 这是一个创建zookeeper连接的方法
*/
public void connectZookeeper() throws IOException{
zk = new ZooKeeper(ZKSERVERS, 2000, new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("已经成功连接了!");
}
});
}
public String sendMsg(String hostName, int threadCount) throws UnsupportedEncodingException, KeeperException, InterruptedException{
if (zk.exists("/servers", null) == null) {
zk.create("/servers", "aa".getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
}
if(path == null){
/**
* 刚上线,没有对应的数据,所以需要先创建一个数据点。
* 返回这个路径很重要,因为后续的要对这个字段的线程数进行修改。
*/
path = zk.create("/servers/" + hostName, (threadCount+"").getBytes("utf-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
}else{
//这里是线程数改变的时候用来修改线程数的。
zk.setData(path, (threadCount+"").getBytes("utf-8"), -1);
}
System.out.println("信息上传到了zookeeper的: " + path + " 中!");
System.out.println("value是:" + new String(zk.getData(path, null, null)));
return path;
}
public boolean startServer(){
try {
server = new ServerSocket(4700);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public void acceptConnection() throws IOException, KeeperException, InterruptedException{
while (true) {
socket = server.accept();
threadCount++;
new Thread(new ZookeeperServerOf_JunHeng()).start();
sendMsg(myHostName, threadCount);
}
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
* 这个本来就是线程类了
*/
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(5000);
socket.close();
//线程断开后,把线程数减一,并且修改zookeeper上的信息。
this.sendMsg(this.getMyHostName(), this.getThreadCount() - 1);
this.setThreadCount(this.getThreadCount() - 1);
} catch (InterruptedException | IOException | KeeperException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public String getMyHostName() {
return myHostName;
}
public void setMyHostName(String myHostName) {
this.myHostName = myHostName;
}
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
public int getThreadCount() {
return threadCount;
}
public void setThreadCount(int threadCount) {
this.threadCount = threadCount;
}
public ServerSocket getServer() {
return server;
}
public void setServer(ServerSocket server) {
this.server = server;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getZKSERVERS() {
return ZKSERVERS;
}
}
HostAndThreadCount
package org.zookeeper.com.findserver;
import java.util.Comparator;
/**
* @author liufu
* 这个类用来进行存储host 和 线程数
* 实现comparetor是为了在用Collections工具排序是指定用threadNum
*/
public class HostAndThreadCount implements Comparator<HostAndThreadCount>{
private String host = null;
private Integer threadNum = null;
public HostAndThreadCount(){
}
public HostAndThreadCount(String _host, Integer _threadNum){
host = _host;
threadNum = _threadNum;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getThreadNum() {
return threadNum;
}
public void setThreadNum(Integer threadNum) {
this.threadNum = threadNum;
}
@Override
public int compare(HostAndThreadCount o1, HostAndThreadCount o2) {
// TODO Auto-generated method stub
return o1.getThreadNum() - o2.getThreadNum();
}
}
#总结:
- 1:动态感知服务器的上下线,只能够通过zk.getChildren("/servers", new GetChildrenEvent());
- 2:不需要动态的监听服务器的线程数,因为服务器的线程数会变化的非常频繁而且还是多个服务器的线程。而监听就是为了减少对zookeeper的轮询访问,以减少zookeeper的负担。如果监听的太频繁反而会适得其反。