文章转载自https://www.cnblogs.com/markcd/p/8456870.html,只有代码是自己实现的,其他全是转载。
Nginx的负载均衡默认算法是加权轮询算法,本文简单介绍算法的逻辑,并给出算法的Java实现版本。
本文参考了Nginx的负载均衡 - 加权轮询 (Weighted Round Robin) 。
算法简介
有三个节点{a, b, c},他们的权重分别是{a=5, b=1, c=1}。发送7次请求,a会被分配5次,b会被分配1次,c会被分配1次。
一般的算法可能是:
1、轮训所有节点,找到一个最大权重节点;
2、选中的节点权重-1;
3、直到减到0,恢复该节点原始权重,继续轮询;
这样的算法看起来简单,最终效果是:{a, a, a, a, a, b, c},即前5次可能选中的都是a,这可能造成权重大的服务器造成过大压力的同时,小权重服务器还很闲。
Nginx的加权轮询算法将保持选择的平滑性,希望达到的效果可能是{a, b, a, a, c, a, a},即尽可能均匀的分摊节点,节点分配不再是连续的。
Nginx加权轮询算法
1、概念解释,每个节点有三个权重变量,分别是:
(1) weight: 约定权重,即在配置文件或初始化时约定好的每个节点的权重。
(2) effectiveWeight: 有效权重,初始化为weight。
在通讯过程中发现节点异常,则-1;
之后再次选取本节点,调用成功一次则+1,直达恢复到weight;
此变量的作用主要是节点异常,降低其权重。
(3) currentWeight: 节点当前权重,初始化为0。
2、算法逻辑
(1) 轮询所有节点,计算当前状态下所有节点的effectiveWeight之和totalWeight;
(2) currentWeight = currentWeight + effectiveWeight; 选出所有节点中currentWeight中最大的一个节点作为选中节点;
(3) 选中节点的currentWeight = currentWeight - totalWeight;
基于以上算法,我们看一个例子:
这时有三个节点{a, b, c},权重分别是{a=4, b=2, c=1},共7次请求,初始currentWeight值为{0, 0, 0},每次分配后的结果如下:
| 请求序号 | 请求前currentWeight值 | 选中节点 | 请求后currentWeight值 |
| 1 | {c=1,b=2,a=4} | a | {c=1,b=2,a=-3} |
| 2 | {c=2,b=4,a=1} | b | {c=2,b=-3,a=1} |
| 3 | {c=3,b=-1,a=5} | a | {c=3,b=-1,a=-2} |
| 4 | {c=4,b=1,a=2} | c | {c=-3,b=1,a=2} |
| 5 | {c=-2,b=3,a=6} | a | {c=-2,b=3,a=-1} |
| 6 | {c=-1,b=5,a=3} | b | {c=-1,b=-2,a=3} |
| 7 | {c=0,b=0,a=7} | a | {c=0,b=0,a=0} |
观察到七次调用选中的节点顺序为{a, b, a, c, a, b, a},a节点选中4次,b节点选中2次,c节点选中1次,算法保持了currentWeight值从初始值{c=0,b=0,a=0}到7次调用后又回到{c=0,b=0,a=0}。
以下是加权轮询代码实现,还增加了监控器:
package com.aliware.tianchi;
import org.apache.dubbo.rpc.Invocation;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static com.aliware.tianchi.WeightLunxun.Servers.servers;
/**
* @ClassName WeightLunxun 加权轮询
* @Date
* @Author
* @Description TODO
**/
public class WeightLunxun {
static int totalWeight = 0;
static Server maxWeightServer = null;
static List currentList = new ArrayList();
static boolean WARM_UP = false;
public static void main(String args[]) {
WeightLunxun.Servers.servers.add(new WeightLunxun.Server(20870));
WeightLunxun.Servers.servers.add(new WeightLunxun.Server(20880));
WeightLunxun.Servers.servers.add(new WeightLunxun.Server(20890));
Monitor.wrappers.add(new Monitor.serverWrapper(WeightLunxun.Servers.servers.get(1)));
Monitor.wrappers.add(new Monitor.serverWrapper(WeightLunxun.Servers.servers.get(2)));
Monitor.wrappers.add(new Monitor.serverWrapper(WeightLunxun.Servers.servers.get(0)));
for (int i = 0; i < 7; i++) {
System.out.println("第" + (i + 1) + "次轮询开始");
select();
System.out.println("第" + (i + 1) + "次轮询结束");
System.out.println("===============================================");
}
Monitor.printMonitors();
}
public static Integer select() {
/* 算法逻辑:
* 1. 对于每个请求,遍历集群中的所有可用后端,对于每个后端peer执行:
* peer->current_weight += peer->effecitve_weight。
* 同时累加所有peer的effective_weight,保存为total。
* 2. 从集群中选出current_weight最大的peer,作为本次选定的后端。
* 3. 对于本次选定的后端,执行:peer->current_weight -= total。
* */
Integer result = null;
currentList.clear();
servers.forEach(server -> currentList.add(server.currentWeight));
System.out.println("轮询前currentWeight:" + currentList);
currentList.clear();
for (int i = 0; i < servers.size(); i++) {
Server server = servers.get(i);
server.currentWeight += server.effectiveWeight;
totalWeight += server.effectiveWeight;
if (maxWeightServer != null) {
if (server.currentWeight > maxWeightServer.currentWeight) {
maxWeightServer = server;
}
} else {
maxWeightServer = server;
}
currentList.add(server.currentWeight);
}
System.out.println("轮询后currentWeight:" + currentList);
System.out.println("选择节点:" + maxWeightServer.port);
maxWeightServer.currentWeight -= totalWeight;
totalWeight = 0;
for (int i = 0; i < servers.size(); i++) {
if (maxWeightServer.port == servers.get(i).port) {
result = i;
}
}
Monitor.addMonitor(result);
return result;
}
public static class Servers {
//服务列表
static List<Server> servers = new ArrayList<>();
}
public static class Server {
public int weight;
public int port;
//有效权重(如果该次请求失败,有效权重理应-1,成功+1,直到等于weight。这里并没有实现)
public int effectiveWeight;
//当前权重
public int currentWeight = 0;
Server(int port) {
super();
this.port = port;
switch (port) {
case 20870:
this.weight = 2;
break;
case 20880:
this.weight = 1;
break;
case 20890:
this.weight = 4;
break;
default:
;
}
this.effectiveWeight = this.weight;
}
Server(int port, String name) {
super();
this.weight = weight;
this.effectiveWeight = this.weight;
}
public Server() {
}
}
}
package com.aliware.tianchi;
import jdk.nashorn.internal.ir.IfNode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName Monitor 监控器
* @Date
* @Author
* @Description TODO
**/
public class Monitor {
static List<serverWrapper> wrappers = new ArrayList<>();
public static void addMonitor(int index) {
WeightLunxun.Server server = WeightLunxun.Servers.servers.get(index);
wrappers.forEach(wrapper -> {
if (server.port == wrapper.port)
wrapper.index++;
});
}
public static void printMonitors() {
wrappers.forEach(wrapper -> System.out.print(wrapper.port + ":" + wrapper.index + ","));
System.out.println();
}
//包装服务端
public static class serverWrapper extends WeightLunxun.Server {
private int index;
serverWrapper(int port) {
super(port);
}
serverWrapper(int port, String name) {
super(port, name);
}
serverWrapper(WeightLunxun.Server server) {
this.port = server.port;
this.currentWeight = server.currentWeight;
this.effectiveWeight = server.effectiveWeight;
this.weight = server.weight;
}
}
}
以下是输出结果:
第1次轮询开始
轮询前currentWeight:[0, 0, 0]
轮询后currentWeight:[2, 1, 4]
选择节点:20890
第1次轮询结束
===============================================
第2次轮询开始
轮询前currentWeight:[2, 1, -3]
轮询后currentWeight:[4, 2, 1]
选择节点:20870
第2次轮询结束
===============================================
第3次轮询开始
轮询前currentWeight:[-3, 2, 1]
轮询后currentWeight:[-1, 3, 5]
选择节点:20890
第3次轮询结束
===============================================
第4次轮询开始
轮询前currentWeight:[-1, 3, -2]
轮询后currentWeight:[1, 4, 2]
选择节点:20880
第4次轮询结束
===============================================
第5次轮询开始
轮询前currentWeight:[1, -3, 2]
轮询后currentWeight:[3, -2, 6]
选择节点:20890
第5次轮询结束
===============================================
第6次轮询开始
轮询前currentWeight:[3, -2, -1]
轮询后currentWeight:[5, -1, 3]
选择节点:20870
第6次轮询结束
===============================================
第7次轮询开始
轮询前currentWeight:[-2, -1, 3]
轮询后currentWeight:[0, 0, 7]
选择节点:20890
第7次轮询结束
===============================================
20880:1,20890:4,20870:2,
本文结束。如果不足或疑问,请回复交流。
本文详细介绍了Nginx负载均衡中的加权轮询算法原理及其实现方式,通过具体示例展示了如何通过Java代码实现该算法,确保请求能够更均匀地分配到各个服务器上。
3062

被折叠的 条评论
为什么被折叠?



