如何利用zookeeper做负载均衡呢,并且能够让客户端动态监控服务端的状态,一旦有的服务器挂掉,客户端能够迅速感知,从而做出调整。
先演示一遍:注意,本地要运行一个zookeeper,让客户端和服务端分别和zookeeper进行连接,能实时跟zookeeper保持联系。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import org.I0Itec.zkclient.ZkClient;
import org.apache.log4j.Logger;
//服务端
public class SimpleServer implements Runnable {
private static Logger logger = Logger.getLogger(SimpleServer.class.getName());
public static void main(String[] args) throws IOException {
int port = 18081;
SimpleServer server = new SimpleServer(port);
Thread thread = new Thread(server);
thread.start();
}
private int port;
public SimpleServer(int port) {
this.port = port;
}
private void regServer() {
//向ZooKeeper注册当前服务器
ZkClient client = new ZkClient("127.0.0.1:2181", 60000, 1000);
String pathroot = "/test";
if (!client.exists(pathroot)) {
logger.info("创建根节点:" + pathroot);
client.createPersistent(pathroot);
}
String path = "/test/server" + port;
if(client.exists(path)) {
client.delete(path);
}
client.createEphemeral(path, "127.0.0.1:" + port);
}
@Override
public void run() {
ServerSocket server = null;
try {
server = new ServerSocket(port);
regServer();
System.out.println("Server started at " + port);
Socket socket = null;
while (true) {
socket = server.accept();
new Thread(new SimpleServerHandler(socket)).start();
}
} catch(IOException ex) {
ex.printStackTrace();
} finally {
if (server != null) {
try {
server.close();
} catch (IOException e) {}
}
}
}
}
class SimpleServerHandler implements Runnable {
private Socket socket;
public SimpleServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader in = null;
PrintWriter out = null;
try {
in = new BufferedReader(new InputStreamReader(
this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);
String body = null;
while (true) {
body = in.readLine();
if (body == null)
break;
System.out.println("Receive : " + body);
out.println("Hello, " + body);
}
} catch (Exception e) {
if (in != null) {
try {
in.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (out != null) {
out.close();
}
if (this.socket != null) {
try {
this.socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
this.socket = null;
}
}
}
}
package com.tecno.BoomPlayerLog.bigdata.zookeeper.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.ZkClient;
//客户端
public class SimpleClient {
private static Integer pos = 0;
private static List servers = new ArrayList<>();
public static void main(String[] args) {
initServerList();
SimpleClient client = new SimpleClient();
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String name;
try {
name = console.readLine();
if ("exit".equals(name)) {
System.exit(0);
}
client.send(name);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void initServerList() {
// 启动时从ZooKeeper读取可用服务器
String path = "/test";
ZkClient zkClient = new ZkClient("127.0.0.1:2181", 60000, 1000);
List childs = zkClient.getChildren(path);
servers.clear();
for (String p : childs) {
servers.add(zkClient.readData(path + "/" + p));
}
// 订阅节点变化事件
zkClient.subscribeChildChanges("/test", new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List currentChilds) throws Exception {
System.out.println(String.format("[ZookeeperRegistry] service list change: path=%s, currentChilds=%s",
parentPath, currentChilds.toString()));
servers.clear();
for (String p : currentChilds) {
servers.add(zkClient.readData(path + "/" + p));
}
System.out.println("Servers: " + servers.toString());
}
});
}
public static String getServer() {
// return servers.get(new Random().nextInt(servers.size())); 随机算法
// 轮询算法
String server = null;
if (pos >= servers.size()) {
pos = 0;
}
server = servers.get(pos);
pos++;
return server;
}
public SimpleClient() {
}
public void send(String name) {
String server = SimpleClient.getServer();
String[] cfg = server.split(":");
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = new Socket(cfg[0], Integer.parseInt(cfg[1]));
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
out.println(name);
while (true) {
String resp = in.readLine();
if (resp == null)
break;
else if (resp.length() > 0) {
System.out.println("Receive : " + resp);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close();
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
首先,先运行服务端的main方法发布服务,注意修改端口号,以免端口号冲突启动不了,这里我们运行三次服务端。运行完结果如下:
还有两个服务端的控制台也是类似;
然后运行客户端的main方法,运行完后,在控制台上可以输入任意字符串,回车之后(可以重复操作),服务端就会接收消息,并且返回给客户端,代表接收请求,并且处理请求。效果如下:
这里我发送三次消息,也得到服务端的回复,我们再看服务端的显示:
可见每个服务端都请求一次,就是轮询的效果。然后现在关闭一个服务端,结果如下(必须过一会,要进行心跳检查):
客户端就会感知,现在再来发送消息,就是其他两台服务端轮询处理消息了,同理 一旦新增服务端,客户端也可以有效感知。
数据同步呢?就是利用zookeeper的Watcher机制来监控zookeeper数据节点内容的变化,不过只能一次性,我们需要改良,进行实时监控。相关代码如下:
import java.io.IOException;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.junit.Test;
public class MonitoringZookeeperNodesContinuous {
private ZooKeeper zooKeeper;
{
try {
zooKeeper = new ZooKeeper("127.0.0.1:2181", 6000, null);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Test //我们让客户端订阅节点,就能收到节点变化的内容了
public void testProcessClient() throws KeeperException, InterruptedException {
Stat stat = new Stat();
// 客服端订阅节点
String path = "/server/address";
//获取zookeeper更新后的版本信息
String initResult = doProcess(path, stat);
System.err.println("initResult ==> " + initResult);
// 处理业务更新版本,加载最新版本的信息到内存中
//为了让程序不停止方便看效果
while (true) {
}
}
/**
* @Description: 以递归方式实现节点状态的持续监控的方法
* @author yunyao.huang
* @throws InterruptedException
* @throws KeeperException
* @date 2018年8月7日
*/
public String doProcess(final String path, final Stat stat) throws KeeperException, InterruptedException {
Watcher watcher = new Watcher() {
@Override
public void process(WatchedEvent event) {
try {
String innerResult = doProcess(path, stat);
System.out.println(innerResult);
} catch (KeeperException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
byte[] data = zooKeeper.getData(path, watcher, stat);
return new String(data);
}
@Test
public void testWatcherOnce() throws KeeperException, InterruptedException {
Stat stat = new Stat();
// 客户端订阅的zookeeper的配置信息所储存的路径节点
String path = "/server/address";
// 回调函数
Watcher watcher = new Watcher() {
@Override
public void process(WatchedEvent event) {
System.err.println("zookeeper的配置信息发生改变了");
}
};
zooKeeper.getData(path, watcher, stat);
// 不能让程序停止,否则看不到效果
while (true) {
}
}
@Test
public void testCMSSetDataServer() {
String path = "/server/address";
// CMS更新完的版本号
byte[] data = "Version71".getBytes();
try {
// 让zookeeper更新COM的版本号 ,一更新,订阅的client就会感知
zooKeeper.setData(path, data, -1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行testProcessClient()方法,然后运行testCMSSetDataServer()方法。效果如下:
每一次客户端只要修改节点中的值,zookeeper立刻通知所有的客户端,客户端自然就会得到最新的消息了,就可以实现数据同步了。
关于zookeeper,可以看下:这篇文章说的挺不错的 ,看完就会对上面的操作有更深的体会了,知道原理还是非常有必要的。