详解 负载均衡技术 的基本实现
当 一段时间内 客户端的访问量较多,很容易造成 服务器崩溃
(例如:某东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
心得体会:
那么,到这里,负载均衡 技术
就基本实现了
我们在使用时,只需要将 目标服务器节点信息列表 录入,再通过选取的 负载均衡 策略
逐个取出
这样,就能在 一定的程度 上减少 服务器的压力
至于使用,将在本人之后的博文《【多文件自平衡云传输】专栏总集篇》中 进行巧妙地运用,
并在最后会有视频展示,有兴趣的同学请前往围观哦!
(最后,附上 本人《多文件自平衡云传输框架》专栏 展示视频的封面,希望大家多多支持哦!)