常见的负载均衡算法有:随机算法、加权轮询、一致性hash、最小活跃数算法
定义一个服务器列表,没个负载均衡的算法会从中挑出一个服务器作为算法的结果。
public class ServerIps {
public static final List<String> LIST = Arrays.asList(
"192.168.0.1",
"192.168.0.2",
"192.168.0.3",
"192.168.0.4",
"192.168.0.5",
"192.168.0.6",
"192.168.0.7",
"192.168.0.8",
"192.168.0.9",
"192.168.0.10"
);
}
1、先来个最简单的实现。
/**
* 随机算法
*/
public class Random {
public static String getServer() {
// 生成一个随机数作为list的下标值
java.util.Random random = new java.util.Random();
int randomPos = random.nextInt(ServerIps.LIST.size());
return ServerIps.LIST.get(randomPos);
}
public static void main(String[] args) {
//连续调用10次
for (int i=0; i<10; i++) {
System.out.println(getServer());
}
}
}
当调用次数比较少时,Random产生的随机数可能会比较集中,此时多数请求会落到同一台服务器上,只有在经过多次请求后,才能使调用请求进行“均衡”分配。调用量少这一点并没有什么关系,负载均衡机制不正是为了应对请求量多的情况吗,所以随机算法也是用的比较多的一种算法。
但是,上面的随机算法适用于每台机器的性能差不多的时候,实际上,生产中可能某些机器的性能更高一点,它可以处理更多的请求,所以,我们可以对每台服务器设置一个权重。
在ServerIps类中增加服务器权重对应关系MAP,权重之和为50:
public class ServerIps {
public static final List<String> LIST = Arrays.asList(
"192.168.0.1",
"192.168.0.2",
"192.168.0.3",
"192.168.0.4",
"192.168.0.5",
"192.168.0.6",
"192.168.0.7",
"192.168.0.8",
"192.168.0.9",
"192.168.0.10"
);
public static final Map<String, Integer> WEIGHT_LIST = new HashMap<String, Integer>();
static {
// 权重之和为50
WEIGHT_LIST.put("192.168.0.1", 1);
WEIGHT_LIST.put("192.168.0.2", 8);
WEIGHT_LIST.put("192.168.0.3", 3);
WEIGHT_LIST.put("192.168.0.4", 6);
WEIGHT_LIST.put("192.168.0.5", 5);
WEIGHT_LIST.put("192.168.0.6", 5);
WEIGHT_LIST.put("192.168.0.7", 4);
WEIGHT_LIST.put("192.168.0.8", 7);
WEIGHT_LIST.put("192.168.0.9", 2);
WEIGHT_LIST.put("192.168.0.10", 9);
}
}
那么现在的随机算法应该要改成权重随机算法,当调用量比较多的时候,服务器使用的分布应该近似对应权重的分布。
简单的实现思路时,把每个服务器按照它所对应的服务器进行复制,具体看代码更加容易理解。
/**
* 简单的权重随机算法
*/
public class WeightRandom {
public static String getServer() {
// 生成一个随机数作为listd的下标志
List<String> ips = new ArrayList<String>();
for (String ip : ServerIps.WEIGHT_LIST.keySet()) {
Integer weight = ServerIps.WEIGHT_LIST.get(ip);
// 按权重进行复制
for (int i=0; i<weight; i++) {
ips.add(ip);
}
}
java.util.Random random = new java.util.Random();
int randomPos = random.nextInt(ips.size());
return ips.get(randomPos);
}
public static void main(String[] args) {
// 连续调用10次
for (int i=0; i<10; i++) {
System.out.println(getServer());
}
}
}
这种实现方法在遇到权重之和特别大的时候就会比较消耗内存,因为需要对IP地址进行复杂,权重之和越大你那么上文中的ips就需要越多的内存,下面介绍另外一种实现思路。
假设现在随机数offset=7:
1、offset<5 is false,所以不再[0,5)区间,将offset=offset-5(offset=2);
2、offset<3 is true,所以处于[5,8)区间,所以应该选用B服务器。
实现如下:
/**
* 权重随机算法2
*/
public class WeightRandomV2 {
public static String getServer() {
int totalWeight = 0;
boolean sameWeight = true; // 如果所以权重都相等,那么随机一个ip就好了
Object[] weights = ServerIps.WEIGHT_LIST.values().toArray();
for (int i = 0; i < weights.length; i++) {
Integer weight = (Integer) weights[i];
totalWeight += weight;
if (sameWeight && i > 0 && !weight.equals(weights[i - 1])) {
sameWeight = false;
}
}
java.util.Random random = new java.util.Random();
int randomPos = random.nextInt(totalWeight);
if (!sameWeight) {
for (String ip : ServerIps.WEIGHT_LIST.keySet()) {
Integer value = ServerIps.WEIGHT_LIST.get(ip);
if (randomPos < value) {
return ip;
}
randomPos = randomPos - value;
}
}
return (String) ServerIps.WEIGHT_LIST.keySet().toArray()[new
java.util.Random().nextInt(ServerIps.WEIGHT_LIST.size())];
}
public static void main(String[] args) {
// 连续调用10次
for (int i = 0; i < 10; i++) {
System.out.println(getServer());
}
}
}
/**
* 简单轮询算法
*/
public class RoundRobin {
//当前循环的位置
private static Integer pos = 0;
public static String getServer() {
String ip = null;
// pos同步
synchronized (pos) {
if (pos >= ServerIps.LIST.size()) {
pos = 0;
}
ip = ServerIps.LIST.get(pos);
pos++;
}
return ip;
}
public static void main(String[] args) {
//连续调用10次
for (int i = 0; i < 10; i++) {
System.out.println(getServer());
}
}
}
另一种算法就是:调用编号,比如第1次调用为1,第2次调用为2,第100次调用为100,调用编号是递增的,所以我们可以根据这个调用编号推算出服务器。
Servers=[A,B,C] 对应权重 Weights = [2,5,1] 那么权重和为8;
比如调用10次,其调用顺序为AABBBBBCAA
调用编号会越来越大,而服务器是固定的,所以需要把调用编号“缩小”,这里对调用编号进行取余,除数为总权重和,比如:
1、1号调用,1%8=1;
2、2号调用,2%8=2;
2、3号调用,3%8=3;
4、8号调用,8%8=0;
5、9号调用,9%8=1;
6、100号调用,100%8=4;
我们发现调用编号可以被缩小为0-7之间的8个数字,问题是怎么根据这8个数字找到对应的服务器咧?和我们随机算法类似,这里也可以把权重想象为一个坐标轴"0-----2----7—8"。
实现:
public class ServerIps {
c static final Map<String, Integer> WEIGHT_LIST = new HashMap<String, Integer>();
static {
// 权重之和为50
// WEIGHT_LIST.put("192.168.0.1", 1);
// WEIGHT_LIST.put("192.168.0.2", 8);
// WEIGHT_LIST.put("192.168.0.3", 3);
// WEIGHT_LIST.put("192.168.0.4", 6);
// WEIGHT_LIST.put("192.168.0.5", 5);
// WEIGHT_LIST.put("192.168.0.6", 5);
// WEIGHT_LIST.put("192.168.0.7", 4);
// WEIGHT_LIST.put("192.168.0.8", 7);
// WEIGHT_LIST.put("192.168.0.9", 2);
// WEIGHT_LIST.put("192.168.0.10", 9);
WEIGHT_LIST.put("A", 2);
WEIGHT_LIST.put("B", 5);
WEIGHT_LIST.put("C", 1);
}
}
/**
* 权重加轮询算法
*/
public class WeightRoundRobin {
public static Integer num = 0;
public static String getServer() {
int totalWeight = 0;
boolean sameWeight = true; // 如果所以权重都相等,那么随机一个ip就好了
Object[] weights = ServerIps.WEIGHT_LIST.values().toArray();
for (int i = 0; i < weights.length; i++) {
Integer weight = (Integer) weights[i];
totalWeight += weight;
if (sameWeight && i > 0 && !weight.equals(weights[i - 1])) {
sameWeight = false;
}
}
Integer sequenceNum = getAndIncrement();
Integer offset = sequenceNum % totalWeight;
offset = offset == 0 ? totalWeight : offset;
if (!sameWeight) {
for (String ip : ServerIps.WEIGHT_LIST.keySet()) {
Integer weight = ServerIps.WEIGHT_LIST.get(ip);
if (offset <= weight) {
return ip;
}
offset = offset - weight;
}
}
return null;
}
public static Integer getAndIncrement() {
return ++num;
}
public static void main(String[] args) {
连续调用10次
for (int i = 0; i < 10; i++) {
System.out.println(getServer());
}
}
}
但是这种算法有一个缺点:一台服务器的权重特别大的时候,他需要连续的处理请求,但是实际上我们想达到的效果是对于100次请求,只要100*8%50=16次就够了,这16次不一定要连续的访问,比如假设我们有三台服务器Servers=[A,B,C] 对应权重 Weights = [5,1,1] 那么权重和为7,
那么上述这个算法的结果就是:AAAAABC,那么如果能够是这么一个结果呢:AABACAA,把B和C平均插入到5个A中间,这样是比较均衡的了。
我们这里可以改成平滑加权轮询,先讲一下思路:
下列表中,比如
请求编号1中,初始值为[5, 1, 1],数组中最大的值5,那么选择结果为A,选择后最大的值5减去权重和7,变成了[-2, 1, 1];
请求编号2来的时候,[-2, 1, 1]加上[5, 1, 1],那么就变成编号2中的初始值为[3, 2, 2],数组中最大的值3,那么选择结果为A,最大的值3减去权重和7,变成了[-4, 2, 2];
请求编号3来的时候,[-4, 2, 2]加上[5, 1, 1],那么就变成编号2中的初始值为[1, 3, 3],数组中最大的值3,那么选择结果为B,最大的值3减去权重和7,变成了[1, -4, 3];
以此类推。。。。。。。。
实现:
ServerIps为:
public static final Map<String, Integer> WEIGHT_LIST = new HashMap<String, Integer>();
static {
WEIGHT_LIST.put("A", 5);
WEIGHT_LIST.put("B", 1);
WEIGHT_LIST.put("C", 1);
}
}
/**
* 权重平滑加轮询算法
*/
public class WeightRoundRobinV2 {
private static Map<String, Weight> weightMap = new HashMap<String, Weight>();
public static String getServer() {
// java8
int totalWeight = ServerIps.WEIGHT_LIST.values().stream().reduce(0, (w1, w2) -> w1+w2);
// 初始化weightMap,初始值currentWeight复制为weight
if (weightMap.isEmpty()) {
ServerIps.WEIGHT_LIST.forEach((key, value) -> {
weightMap.put(key, new Weight(key, value, value));
});
}
// 找出currentWeight最大值
Weight maxCurrentWeight = null;
for (Weight weight : weightMap.values()) {
if (maxCurrentWeight == null || weight.getCurrentWeight() >
maxCurrentWeight.getCurrentWeight()) {
maxCurrentWeight = weight;
}
}
// 将maxCurrentWeight减去总权重和
maxCurrentWeight.setCurrentWeight(maxCurrentWeight.getCurrentWeight() -
totalWeight);
// 所有的ip的currentWeight统一加上权重
for (Weight weight : weightMap.values()) {
weight.setCurrentWeight(weight.getCurrentWeight() + weight.getWeight());
}
// 返回maxCurrentWeight所对应的ip
return maxCurrentWeight.getIp();
}
public static void main(String[] args) {
//连续调用10次
for (int i = 0; i < 10; i++) {
System.out.println(getServer());
}
}
}
以上均为鲁班学院学习资料,欢迎大家报班学习,真心推荐!