基于Zookeeper集群环境下的负载均衡

转载请说明出处: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的工作服务器 :

猜你喜欢

转载自blog.csdn.net/LiaoHongHB/article/details/84973879