详解 负载均衡技术 的基本实现

Youzg LOGO

一段时间内 客户端的访问量较多,很容易造成 服务器崩溃
(例如:某东618、桃饱双十一、志愿填报结束前半小时 等)
这时候,就需要我们采用 负载均衡技术,来 减轻服务器的压力

那么,在本篇博文中,本人就来 基本实现负载均衡技术

基本 概念:

首先,本人来介绍下什么是 负载均衡

定义:

负载均衡,英文名称为Load Balance
其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行
例如FTP服务器、Web服务器、企业核心应用服务器和其它主要任务服务器等,从而协同完成工作任务


那么,使用 负载均衡 技术,有什么 好处 呢?

功能:

负载均衡构建在原有网络结构之上,
它提供了一种透明廉价有效的方法 来 扩展服务器和网络设备的带宽加强网络数据处理能力增加吞吐量提高网络的可用性 和 灵活性


那么,在本篇博文中,本人就来 基本实现负载均衡技术

在本篇博文中,主要实现 两种 负载均衡 —— 轮询式随机式

实现 代码:

由于 负载均衡针对网络编程、在 现有的服务器列表 中 通过 一定的策略 挑选出来 部分的服务器列表

因此,本人在这里先来提供一个 节点信息获取 功能接口

节点信息获取 功能接口 —— INetNode:

package edu.youzg.balance;

/**
 * 网络节点 基本功能接口
 */
public interface INetNode {
    String getIp();
    int getPort();
    void setIp(String ip);
    void setPort(int port);
}

接下来,本人来提供一个 上述接口的 默认实现类

默认网络节点 —— DefaultNetNode:

package edu.youzg.balance;

import java.util.Objects;

/**
 * 网络节点 默认实现类
 */
public class DefaultNetNode implements INetNode {
    private String ip;  // 当前网络节点 的ip
    private int port;   // 当前网络节点 的port

    public DefaultNetNode() {
    }

    public DefaultNetNode(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }

    @Override
    public void setIp(String ip) {
        this.ip = ip;
    }

    @Override
    public void setPort(int port) {
        this.port = port;
    }

    @Override
    public String getIp() {
        return this.ip;
    }

    @Override
    public int getPort() {
        return this.port;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        DefaultNetNode that = (DefaultNetNode) o;
        return port == that.port &&
                Objects.equals(ip, that.ip);
    }

    @Override
    public int hashCode() {
        return Objects.hash(ip, port);
    }

    @Override
    public String toString() {
        return "[" + this.ip + ":" + this.port + "]";
    }

}

那么,我们就通过上述的 接口和类,提供一个 负载均衡功能接口

负载均衡 功能接口 —— INetNodeBalance:

实现 思路:

由于本人在上文中讲到过:

负载均衡实现思路 为:
现有的服务器列表 中 通过 一定的策略 挑选出 部分的服务器列表

因此,我们提供的 负载均衡 功能接口,要具有如下功能

  • 录入 现有的服务器 节点信息
  • 移除 现有的服务器 节点信息
    (与上个功能 呼应)
  • 录入的 服务器节点信息 列表 中,按照所选的策略 提取节点信息

实现 代码:

package edu.youzg.balance;

import java.util.List;

/**
 * 负载均衡 功能接口<br/>
 * 用于 增、删、获取 网络节点
 */
public interface INetNodeBalance {
    void addNode(DefaultNetNode node);
    void addNodeList(List<DefaultNetNode> nodeList);
    INetNode removeNode(INetNode node);
    DefaultNetNode getNode();
}

那么,针对上面的接口,为了方便扩展,本人在这里提供一个 负载均衡 功能抽象类

负载均衡 功能抽象类 —— AbstractNetNodeBalance:

package edu.youzg.balance;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 负载均衡 基本功能 基本实现类<br/>
 * 设置了一个 以当前网络节点的hashcode为键,以当前节点的信息为值 的map
 */
public abstract class AbstractNetNodeBalance implements INetNodeBalance {
    protected final Map<Integer, INetNode> nodePool;

    public AbstractNetNodeBalance() {
        this.nodePool = new ConcurrentHashMap<>();
    }

    /**
     * 向nodePool中 新增一个 网络节点
     * @param node 目标 网络节点
     */
    @Override
    public void addNode(DefaultNetNode node) {
        if (node == null) {
            return;
        }

        int nodeHashCode = node.hashCode();
        if (nodePool.containsKey(nodeHashCode)) {
            return;
        }

        nodePool.put(nodeHashCode, node);
    }

    /**
     * 向nodePool中 新增一个 网络节点列表
     * @param nodeList 目标 网络节点列表
     */
    @Override
    public void addNodeList(List<DefaultNetNode> nodeList) {
        if (nodeList == null) {
            return;
        }
        for (DefaultNetNode node : nodeList) {
            addNode(node);
        }
    }

    /**
     * 判断nodepool是否为空
     * @return
     */
    public boolean isNodePoolEmpty() {
        return nodePool.isEmpty();
    }

    /**
     * 删除指定的网络节点
     * @param node
     * @return
     */
    @Override
    public INetNode removeNode(INetNode node) {
        if (node == null) {
            return null;
        }
        return nodePool.remove(node.hashCode());
    }

}

那么,在上面 几个类和接口 的基础上,本人来实现下 “随机式”负载均衡

[核心]“随机式”负载均衡 —— RandomBalance:

package edu.youzg.balance;

import java.util.LinkedList;
import java.util.List;

/**
 * 随机实现 负载均衡
 */
public class RandomBalance extends AbstractNetNodeBalance {
    private final List<INetNode> nodeList = new LinkedList();

    public RandomBalance() {
    }

    /**
     * 增加一个 网络节点
     * @param node 要增加的网络节点
     */
    @Override
    public void addNode(DefaultNetNode node) {
        super.addNode(node);
        if (node!=null && !this.nodeList.contains(node)) {
            this.nodeList.add(node);
        }
    }

    @Override
    public void addNodeList(List<DefaultNetNode> nodeList) {
        if (nodeList == null) {
            return;
        }
        for (DefaultNetNode node : nodeList) {
            addNode(node);
        }
    }

    /**
     * 删除一个 网络节点
     * @param node 要删除的网络节点
     * @return 被删除的网络节点
     */
    @Override
    public INetNode removeNode(INetNode node) {
        if (node != null && this.nodeList.contains(node)) {
            this.nodeList.remove(node);
            return super.removeNode(node);
        } else {
            return null;
        }
    }

    /**
     * 通过随机数,<br/>
     * 获取一个网络节点
     * @return 随机获取的网络节点
     */
    @Override
    public DefaultNetNode getNode() {
        if (this.isNodePoolEmpty()) {
            return null;
        } else {
            int index = (int) Math.random() * (nodeList.size() + 1);
            return (DefaultNetNode) this.nodeList.get(index);
        }
    }

}

那么,接下来本人就来实现下 “轮询式”负载均衡
不过,既然要 轮询,就需要 双向链表类似的 数据存储结构

那么,本人在这里提供一个 INetNode接口“轮询式”网络节点:

“轮询式” 网络节点 —— PollingNetNode:

package edu.youzg.balance;

/**
 * 用于“轮询” 的 双向链表式 网络节点
 */
public class PollingNetNode extends DefaultNetNode {
    private PollingNetNode next = this;
    private PollingNetNode pre = this;

    public PollingNetNode() {
    }

    public PollingNetNode(String ip, int port) {
        super(ip, port);
    }

    public void setNext(PollingNetNode next) {
        this.next = next;
    }

    public void setPre(PollingNetNode pre) {
        this.pre = pre;
    }

    public PollingNetNode getNext() {
        return next;
    }

    public PollingNetNode getPre() {
        return pre;
    }

}

package edu.youzg.balance;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 负载均衡 基本功能 基本实现类<br/>
 * 设置了一个 以当前网络节点的hashcode为键,以当前节点的信息为值 的map
 */
public abstract class AbstractNetNodeBalance implements INetNodeBalance {
    protected final Map<Integer, INetNode> nodePool;

    public AbstractNetNodeBalance() {
        this.nodePool = new ConcurrentHashMap<>();
    }

    /**
     * 向nodePool中 新增一个 网络节点
     * @param node 目标 网络节点
     */
    @Override
    public void addNode(DefaultNetNode node) {
        if (node == null) {
            return;
        }

        int nodeHashCode = node.hashCode();
        if (nodePool.containsKey(nodeHashCode)) {
            return;
        }

        nodePool.put(nodeHashCode, node);
    }

    /**
     * 向nodePool中 新增一个 网络节点列表
     * @param nodeList 目标 网络节点列表
     */
    @Override
    public void addNodeList(List<DefaultNetNode> nodeList) {
        if (nodeList == null) {
            return;
        }
        for (DefaultNetNode node : nodeList) {
            addNode(node);
        }
    }

    /**
     * 判断nodepool是否为空
     * @return
     */
    public boolean isNodePoolEmpty() {
        return nodePool.isEmpty();
    }

    /**
     * 删除指定的网络节点
     * @param node
     * @return
     */
    @Override
    public INetNode removeNode(INetNode node) {
        if (node == null) {
            return null;
        }
        return nodePool.remove(node.hashCode());
    }

}

那么,最后,本人再来根据上面的铺垫,实现下 “轮询式”负载均衡

[核心]“轮询式”负载均衡 —— PollingBalance:

package edu.youzg.balance;

import java.util.List;

/**
 * “轮询式” 均衡策略
 */
public class PollingBalance extends AbstractNetNodeBalance {
    private PollingNetNode headNode; // 头节点
    private PollingNetNode currentNode; // 当前应该遍历的节点(还未遍历)

    public PollingBalance() {
    }

    /**
     * 新增一个 网络节点
     * @param node 要新增的节点
     */
    @Override
    public synchronized void addNode(DefaultNetNode node) {
        PollingNetNode newNode = new PollingNetNode(node.getIp(), node.getPort());
        if (this.headNode == null) {
            this.headNode = newNode;
            this.currentNode = this.headNode;
        } else {
            newNode.setPre(this.headNode.getPre());
            newNode.setNext(this.headNode);
            this.headNode.setPre(newNode);
            newNode.getPre().setNext(newNode);
        }
        super.addNode(newNode);
    }

    @Override
    public void addNodeList(List<DefaultNetNode> nodeList) {
        if (nodeList == null) {
            return;
        }
        for (DefaultNetNode node : nodeList) {
            addNode(node);
        }
    }

    /**
     * 删除一个指定的节点
     * @param node 要删除的节点
     * @return 被删除的节点
     */
    @Override
    public synchronized INetNode removeNode(INetNode node) {
        PollingNetNode target = new PollingNetNode(node.getIp(), node.getPort());
        target = (PollingNetNode) super.removeNode(target);
        if (target == null) {
            return null;
        } else if (this.isNodePoolEmpty()) {
            this.headNode = null;
            this.currentNode = null;
            return this.headNode;
        } else {
            if (this.currentNode == target) {
                this.currentNode = target.getNext();
            }

            if (this.headNode == target) {
                this.headNode = target.getNext();
            }

            target.getPre().setNext(target.getNext());
            target.getNext().setPre(target.getPre());
            target.setPre(target);
            target.setNext(target);
            return target;
        }
    }

    /**
     * 按照 存储顺序,依次遍历获取 一个网络节点
     * @return 一个网络节点
     */
    @Override
    public synchronized DefaultNetNode getNode() {
        PollingNetNode resNode = this.currentNode;
        if (resNode!=null) {
            this.currentNode = this.currentNode.getNext();
        }
        return new DefaultNetNode(resNode.getIp(), resNode.getPort());
    }

}

若有需要上述源码的同学,本人已将本文所讲解到的代码打成了Jar包:

工具 Jar包:

如有需要,请点击下方链接:
Load-Balance


心得体会:

那么,到这里,负载均衡 技术 就基本实现了
我们在使用时,只需要将 目标服务器节点信息列表 录入,再通过选取的 负载均衡 策略 逐个取出
这样,就能在 一定的程度 上减少 服务器的压力

至于使用,将在本人之后的博文《【多文件自平衡云传输】专栏总集篇》中 进行巧妙地运用,
并在最后会有视频展示,有兴趣的同学请前往围观哦!

(最后,附上 本人《多文件自平衡云传输框架》专栏 展示视频的封面,希望大家多多支持哦!)
视频展示

posted @ 2020-08-22 09:52  在下右转,有何贵干  阅读(125)  评论(0编辑  收藏  举报