转载请说明出处:https://blog.csdn.net/LiaoHongHB/article/details/84973879
1、原理
- 服务端启动创建临时节点(下图中servers下节点),临时节点数据包含负载信息
- 客户端启动获取服务器列表,并根据负载去连接一个负载较轻的服务器
- 服务端每次接收到客户端的连接,添加自己的负载,客户端断开与自己的连接则减少自己的负载
2、架构图
- Servers:服务器列表父节点
- work Server n :服务器节点
- Client n:客户端节点
3、客户端流程图
4、服务端流程图
5、新建工程zookeeper-balance,并新建子模块:balance-server1, balance-server2,balance-server2,balance-client,balance-client2,balance-client3以及balance-common。
6、balance-common:通用模块,只有一个ServerData类,用于存储各个工作服务器节点的数据信息
public class ServerData implements Serializable {
private static final long serialVersionUID = 2965518573348700597L;
private String serverName;
private String serverIp;
private Integer balanceNum;
public String getServerName() {
return serverName;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public String getServerIp() {
return serverIp;
}
public void setServerIp(String serverIp) {
this.serverIp = serverIp;
}
public Integer getBalanceNum() {
return balanceNum;
}
public void setBalanceNum(Integer balanceNum) {
this.balanceNum = balanceNum;
}
}
7、balance-server1:工作服务器,服务注册到zookeeper集群中,并且监听自己节点的负载数的数据变化;balance-server2和balance-server3同balance-server1.
WorkServer:功能实现类:
public class WorkServer {
private Logger logger = LoggerFactory.getLogger(getClass());
private ZkClient zkClient = null;
private String serverPath;
private int SESSIONTIMEOUT = 15000;
private int CONNECTIONTIMEOUT = 15000;
private ServerData serverData;
public WorkServer() {
}
public WorkServer(String ipAddress, String serverPath, ServerData serverData) {
this.zkClient = new ZkClient(ipAddress, SESSIONTIMEOUT, CONNECTIONTIMEOUT, new SerializableSerializer());
this.serverPath = serverPath;
this.serverData = serverData;
}
public void start() {
logger.info("balance-server1启动,准备初始化....");
init();
}
public void init() {
logger.info("balance-server1 开始初始化....");
boolean exists = zkClient.exists(serverPath);
if (!exists) {
zkClient.createPersistent(serverPath);
logger.info("创建{}持久节点成功", serverPath);
}
boolean exists1 = zkClient.exists(serverPath.concat(serverData.getServerName()));
if (!exists1) {
zkClient.createEphemeral(serverPath.concat(serverData.getServerName()), serverData);
logger.info("创建{}临时节点成功", serverPath.concat(serverData.getServerName()));
}
zkClient.subscribeDataChanges(serverPath.concat(serverData.getServerName()), new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
logger.info("节点{}数据发生改变...", s);
}
@Override
public void handleDataDeleted(String s) throws Exception {
logger.info("节点{}被删除.....", s);
}
});
}
}
WebListener:启动类;
@Component
public class WebListener implements ServletContextListener {
private Logger logger = LoggerFactory.getLogger(getClass());
private String ipAddress = "192.168.202.128:2181,192.168.202.129:2181,192.168.202.130:2181";
@Override
public void contextInitialized(ServletContextEvent sce) {
ServerData serverData = new ServerData();
serverData.setBalanceNum(0);
serverData.setServerIp("192.168.202.128");
serverData.setServerName("/WorkServer1");
WorkServer workServer = new WorkServer(ipAddress, "/servers", serverData);
workServer.start();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
8、balance-client1:客户端,负载均衡的访问工作服务器,balance-client2和balance-client3同balance-client1。
Client1:功能实现类;
public class Client1 {
private Logger logger = LoggerFactory.getLogger(getClass());
private ZkClient zkClient = null;
private String serverPath;
private int SESSIONTIMEOUT = 15000;
private int CONNECTIONTIMEOUT = 15000;
public Client1() {
}
public Client1(String ipAddress, String serverPath) {
this.zkClient = new ZkClient(ipAddress, SESSIONTIMEOUT, CONNECTIONTIMEOUT, new SerializableSerializer());
this.serverPath = serverPath;
}
public void start() {
logger.info("client1 服务启动,准备初始化...");
init();
}
public void init() {
logger.info("client1 开始初始化...");
logger.info("获取{}节点的子节点...", serverPath);
List<String> children = getChildren();
logger.info("{}子节点有{}:", serverPath, children.toString());
logger.info("client1 使用负载均衡调用工作服务器进行工作....");
String node = balance(children);
// release(node);
try {
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取serverPath的子节点列表
*
* @return
*/
public List<String> getChildren() {
List<String> list = zkClient.getChildren(serverPath);
List<String> list1 = new ArrayList<>();
for (String string : list) {
string = "/servers/".concat(string);
list1.add(string);
}
return list1;
}
/**
* 判断哪个工作服务器负载最小
*
* @param children
*/
public String balance(List<String> children) {
List<Integer> integerList = new ArrayList<>();
for (String string : children) {
ServerData serverData = zkClient.readData(string);
Integer balanceNum = serverData.getBalanceNum();
integerList.add(balanceNum);
}
Collections.sort(integerList);
for (String string : children) {
ServerData serverData = zkClient.readData(string);
Integer balanceNum = serverData.getBalanceNum();
Integer minBalanceNum = integerList.get(0);
if (balanceNum.equals(minBalanceNum)) {
logger.info("调用{}节点对应的服务器进行服务", string);
balanceNum++;
serverData.setBalanceNum(balanceNum);
logger.info("更新{}节点负载情况", string);
zkClient.writeData(string, serverData);
return string;
}
}
return null;
}
/**
* 调用完毕,释放负载
*
* @param string
*/
public void release(String string) {
logger.info("释放{}节点的负载", string);
ServerData serverData = (ServerData) zkClient.readData(string);
serverData.setBalanceNum(serverData.getBalanceNum() - 1);
zkClient.writeData(string, serverData);
}
}
9、运行及运行结果:首先运行server模块,然后运行client模块;运行结果如下:
如果重新启动client2或者client3,则根据负载数,又会去调用server1的工作服务器 :