这是我参与8月更文挑战的第30天,活动详情查看: 8月更文挑战
负载均衡我们应该听的比较多,网关,Nginx,Dubbo这些组件都会用到负载均衡算法,将工作任务进行平衡的分摊到各个操作单元上去,例如Nginx就是将前端请求按照一定算法将请求交给后面的服务区响应,避免后端服务承受请求太多而打挂,也可以很好的将多个资源协同起来服务,需要将资源协同,就会涉及到任务如何分发,已经在分发的过程根据不同的资源来承受能力来指派不同的任务,这就需要负载均衡算法。常见的负载均衡算法主要有:轮询(RoundRobin) 、随机(Random) 、源地址哈希(Hash) 、加权轮询(WRR)、加权随机(WR)、最小连接数(LC)
轮询(RoundRobin)
- 概述
轮询就是排好队一个一个来,就像时钟一样,排成一个圈,然后一步一步轮着的走,原理就是将用户的请求从1到N分配,当到了N的时候将指针置为1,让后又从1到N开始执行。这种方式实现简答,对于节点资源可以随意的加减,当时对于有些节点资源性能弱的情况下,无法区别对待,性能弱的机器也要与性能强的机器达到一样的处理能力。
- 算法实现介绍
这种实现算法比较简单,可以采用双向链表来实现,在这里就不采用这种方法,可以采用数组方式来实现
- 简易代码
public class RoundRobin {
public static void main(String[] args) {
String[] ips = new String[]{"192,10.1.1", "192,10.1.2", "192,10.1.3"};
int i = 0;
while (true) {
System.out.println(ips[i++]);
if (i >= ips.length) {
i = 0;
}
}
}
}
随机(Random)
- 概述
从节点资源中采取随机数的方式获取一个节点资源来执行任务
- 算法实现介绍
也采用数组方式,在数组长度的范围内取一个随机数,然后通过数组的小标来获取里面的资源信息,时间起来也比较简单
- 简易代码
public class Random {
public static void main(String[] args) {
String[] ips = new String[]{"192,10.1.1", "192,10.1.2", "192,10.1.3"};
while (true) {
System.out.println(ips[new java.util.Random().nextInt(ips.length)]);
}
}
}
源地址哈希(Hash)
- 概述
例如在Nginx中,对于请求的IP地址做一个Hash值,然后将相同的Hash值路由到后端的同一台机器下,这种常用的用途是:后端的Session会话没有做集群,需要将相同的请求路由到相同的后端的服务中,如果路由到另外的机器,Session就不存在,就会频繁的让用户去重新登录,可以有效地做到会话保持。这种算法可能造成热点问题,在一段时间内,如果大量的请求IP地址的Hash值相同的Key一样就会被路由到同一台机器上,就会造成该机器访问量急剧增长,其他机器没有什么的访问量,严重的话可能会造成该机器宕机。
- 算法实现介绍
该算法的实现可以采用HashMap来实现,key值作为Hash值的key,Value则存放后端服务的IP,key采用请求IP的后3位对提供的机器数取余数存放
- 简易代码
public class Hash {
public static void main(String[] args) {
String[] ips = new String[]{"192.10.1.11", "192.10.12.22", "192.10.1.99"};
HashMap<Integer, String> map = new HashMap<>(ips.length);
//构建hash
for (String ip : ips) {
int key = Integer.parseInt(ip.split("\\.")[3]) % ips.length;
map.put(key, ip);
}
//模拟请求
for (int i = 0; i < 5; i++) {
//随机产生最后3位数字
int ip = new Random().nextInt(100);
int hashKey = ip % ips.length;
System.out.println("ip尾数:" + ip + ",hashKey:" + hashKey + ",获取IP:" + map.get(hashKey));
}
}
}
上面的算法针对如果key相同的话,会被覆盖掉,在这里只做效果展示对于太多的细节并没有考虑到。
加权轮询(WRR)
- 概述
加权轮询在轮询的基础上增加了权重,在轮询的前提下,权重大的机器被分配到的任务多一些,在Nginx中该算法用的比较多,可以有效地做到权重大(机器条件好)的机器接受到更多的请求,可以处理多的请求。加权轮询分为普通加权轮询算法及平滑的加权轮询。
- 普通的加权轮
- 算法实现介绍
我们可以根据每个机器的权重来维护一个队列,比如a,b,c三台机器,a权重5,b权重3,c权重1,当我们知道这些权重后,就根据权重的总和在内部维护一个队列,然后依次从队列中取出轮询,这种算法就是会造成机器分布均匀。所以Nginx就采用的是平滑的加权轮询
- 简易代码
public class WRR {
static class Node {
public String name;
public int weight;
public Node(String name, int weight) {
this.name = name;
this.weight = weight;
}
}
public static void main(String[] args) {
//普通加权轮询
List<String> list = init(new Node("a", 5), new Node("b", 3), new Node("c", 1));
System.out.println("构建的轮询队列:" + list.toString());
//模型请求
int i = 0;
for (int j = 0; j < 10; j++) {
System.out.println(list.get(i++));
if (i >= list.size()) {
i = 0;
}
}
}
private static List<String> init(Node... nodes) {
List<String> list = new ArrayList<>();
//这里需要对node按照weight排序,只是展示效果就不排序
for (Node node : nodes) {
for (int i = 0; i < node.weight; i++) {
list.add(node.name);
}
}
return list;
}
}
执行结果
可以看出执行的队列是:a, a, a, a, a, b, b, b, c,然后采用轮询的方式从轮询队列中获取数据
- 平滑的加权轮询
- 算法实现介绍
采用Nginx的实现方式: 每个节点两个权重,weight和currentWeight,weight永远不变是配置时的值,current不停变化 变化规律如下:选择前所有current+=weight,选current最大的响应,响应后让它的current-=total
- 简易代码
public class WRR {
static class Node {
public String name;
public int weight;
public int currentWeight;
public Node(String name, int weight) {
this.name = name;
this.weight = weight;
}
}
public List<Node> nodeList;
private int totalWeight;
public WRR(Node... nodes) {
nodeList = new ArrayList<>();
for (Node node : nodes) {
nodeList.add(node);
totalWeight += node.weight;
}
}
/**
* 获取当前执行节点
*
* @return
*/
private Node get() {
//在选取前: currentWeight = currentWeight + weight
for (Node node : nodeList) {
node.currentWeight += node.weight;
}
Node current = nodeList.get(0);
int i = 0;
for (Node node : nodeList) {
if (node.currentWeight > i) {
i = node.currentWeight;
current = node;
}
}
//在响应后:currentWeight = currentWeight + total
current.currentWeight -= totalWeight;
return current;
}
public static void main(String[] args) {
//普通加权轮询
WRR wrr = new WRR(new Node("a", 5), new Node("b", 3), new Node("c", 1));
for (int j = 0; j < 10; j++) {
System.out.println(wrr.get().name);
}
}
}
执行结果
从执行结果可以看出,散列执行,权重大的执行次数多
加权随机(WR)
- 概述
加权随机就是在随机的基础上增加权重,权重大的机器,在随机池中选中的概率大一点。
- 算法实现介绍
跟普通的加权轮询差不多,在初始之前,根据权重构建随机队列,然后随机选中一个节点
- 简易代码
public class WR {
static class Node {
public String name;
public int weight;
public Node(String name, int weight) {
this.name = name;
this.weight = weight;
}
}
public static void main(String[] args) {
//普通加权轮询
List<String> list = init(new Node("a", 5), new Node("b", 3), new Node("c", 1));
System.out.println("构建的加权队列:" + list.toString());
//模型请求
for (int j = 0; j < 5; j++) {
System.out.println(list.get(new Random().nextInt(list.size())));
}
}
private static List<String> init(Node... nodes) {
List<String> list = new ArrayList<>();
//这里需要对node按照weight排序,只是展示效果就不排序
for (Node node : nodes) {
for (int i = 0; i < node.weight; i++) {
list.add(node.name);
}
}
return list;
}
}
执行结果
最小连接数(LC)
- 概述
在每次选中机器的时候,选中当前连接数最小的机器来应用,将请求分发给该机器后,对于该机器的请求数+1,然后进行排序,每次来的时候就选择连接数最小的一个机器来处理请求
- 算法实现介绍
排序可以采用最小堆排序算法来进行排序,然后选择一个最小的连接数节点,使用后对于该节点的连接数+1,重新进行排序