初探负载均衡算法【随机、轮询、加权随机、加权轮询、平滑加权轮询】

简介

负载平衡(Load balancing)是一种在多个计算机(网络、CPU、磁盘)之间均匀分配资源,以提高资源利用的技术。使用负载均衡可以最大化服务吞吐量,可能最小化响应时间,同时由于使用负载均衡时,会使用多个服务器节点代单点服务,也提高了服务的可用性。

负载均衡的实现可以软件可以硬件,硬件如大名鼎鼎的 F5 负载均衡设备,软件如 NGINX 中的负载均衡实现,又如 Springcloud Ribbon 组件中的负载均衡实现。本文主要从软件层面来说明其实现算法。

负载均衡算法主要分为以下6大类:

随机轮询加权随机加权轮询一致性哈希平滑加权轮询

代码示例: 先把三台服务器放在集合中去:

//服务器IP

public class ServerIps {

//不带权重的三台服务器,用List存储

public static final List LIST = Arrays.asList(

"A",

"B",

"C"

);

//带权重,用于加权xx算法

public static final Map WEIGHT_LIST = new LinkedHashMap();

static {

WEIGHT_LIST.put("A",2);

WEIGHT_LIST.put("B",3);

WEIGHT_LIST.put("C",5);

}

}

随机访问

很简单,对三台服务器随机抽取一个进行访问即可。

//随机

public class RandomSelect {

public static String getServer() {

Random random = new Random();

int rand = random.nextInt(ServerIps.LIST.size());

return ServerIps.LIST.get(rand);

}

}

轮询算法

也很简单,每次按照顺序依次对服务器进行访问即可,用一个pos指针来记录轮询的位置。

//轮询

public class RoundRobin {

//位置

public static Integer pos = 0;

public static String getServer() {

String ip = null;

synchronized (pos) {

if(pos >= ServerIps.LIST.size()) {

pos = 0;

} else {

ip = ServerIps.LIST.get(pos);

pos++;

}

}

return ip;

}

}

加权轮询

在前面ServerIps类的带权重的服务器定义中,每个服务器A、B、C被储存在一个WEIGHT_LIST中,并分别带有权重2、3、5,意思就是服务器C访问的次数可以相对最大…,前面我们的轮询算法中,遍历的集合是A、B、C组成的,现在无非是放入2个A,3个B,5个C即可,如下图所示: 相当于,10次请求里面有2次是去访问A,3次去访问B,5次去访问C,这样就实现了加权轮询的功能。

加权随机

和加权轮询类似,依旧是按照权重把List集合中放满服务器元素,再去随机取即可,无非是取每个服务器的概率不是均等的罢了。

//缺点 权重大 ips越大 占内存 带权重随机

public class WeightRandom {

public static String getServer() {

//生成随机数作为List下标

List ips = new ArrayList<>();

for (String ip: ServerIps.WEIGHT_LIST.keySet()) {

Integer weight = ServerIps.WEIGHT_LIST.get(ip);

//weight多少 在ips里面存多少 例 A 权重为2 在ips里面存两个

for (int i = 0; i < weight ; i++) {

ips.add(ip);

}

}

Random random = new Random();

int randomPos = random.nextInt(ips.size());

return ips.get(randomPos);

}

}

加权轮询优化

前面讲了加权轮询的实现很容易,无非是按照权重数量把服务器放进集合中,但是如果有一万个服务器呢,一百万个服务器呢?那我们还这样去装集合然后遍历,其实是很浪费资源的! 这里看另一种优化加权轮询的算法:这里总共是10的权重,我们的三台服务器的权重分别是2、3、5,那么我们的10次请求就应该按照比例分配到这三台服务器上,如下图所示: 这里能发现一个规律,如果请求次数小于该权重,就会放到该权重下,比如我第0次、第1次请求都小于2,所以都击中了第一台服务器。 简单来说:如果请求的次数小于某权重,就可以放到该权重中来,如果不小于,则继续往后找

代码:

//轮询优化

public class WeightRoundRobin {

public static String getServer(Integer num) {

int totalWeights = ServerIps.WEIGHT_LIST.values().stream().mapToInt(w -> w).sum();

//把请求对总权重和取模,就落在0到9之间

Integer pos = num % totalWeights;

for(String ip: ServerIps.WEIGHT_LIST.keySet()) {

Integer weight = ServerIps.WEIGHT_LIST.get(ip);

if(pos < weight) {

return ip;//如果小于该权重,直接返回该服务器(击中)

}

pos = pos - weight;//如果不小于,继续往后看哪个权重满足

}

return "";

}

public static void main(String[] args) {

for (int i=0 ; i<10 ; i++) {

System.out.println(getServer(i));

}

}

}

加权轮询、加权随机都是基于这种方式进行实现的,它解决了List放重复数据的问题,但是也有一个缺陷,就是如果某台服务器的权重很大的时候,那么在一段时间内就会一直击中它,服务器压力也很大,我们希望将请求尽可能的打散会好一点,所以就提出了平滑加权轮询算法。

平滑加权轮询算法

简单来说就是通过数学公式,在保证每次权重和不变的前提下,使用动态变化权重去更新每一次的权重,是的击中服务器更加分散,但是整体的权重又是满足初始化定义的。

public class WeightRoundRobinV2 {

//初始化动态权重

public static Map currWeights = new HashMap<>();

public static String getServer() {

int totalWeights = ServerIps.WEIGHT_LIST.values().stream().mapToInt(w -> w).sum();

//currentWeight 默认值 0

if(currWeights.isEmpty()) {

ServerIps.WEIGHT_LIST.forEach((ip,weight)->

{

currWeights.put(ip,new Weight(ip,weight,0));

});

}

for(Weight weight: currWeights.values()) {

weight.setCurrentWeight(weight.getCurrentWeight() + weight.getWeight());

}

//找最大值

Weight maxCurrentWeight = null;

for(Weight weight: currWeights.values()) {

if(maxCurrentWeight == null || weight.getCurrentWeight() > maxCurrentWeight.getCurrentWeight()) {

maxCurrentWeight = weight;

}

}

maxCurrentWeight.setCurrentWeight( maxCurrentWeight.getCurrentWeight() - totalWeights);

return maxCurrentWeight.getIp();

}

public static void main(String[] args) {

for (int i = 0; i < 10; i++) {

System.out.println(getServer());

}

}

}

整体来看,C有5个,B有3个,A有2个,和我们初始化定义的权重是一致的,只不过访问更加随机了,这样服务器的压力就变得更小了。


簇字的成语有哪些
升级经验和任务获得经验表