轮询算法是最简单的一种负载均衡算法,它的原理是将用户的请求轮流分配给内部的服务器,并且轮询算法并不需要记录当前所有连接的状态,所以它是一种无状态的调度.
下面是简单轮询算法的实现
public class RouteRound implements LoadBalance {
private int count = 0;
@Override
public Worker route(String jobId, List workers) {
int index = count++ % workers.size();
return workers.get(index);
}
}
简单轮询算法假设所有的服务器性能都相同,在生产环境下如果所有的服务机器的性能都一样,那么这种算法没有问题,如果服务器性能不同,那么会造成服务器负载不均衡,而基于服务器性能权重的负载均衡算法可以很好的解决这个问题
此种算法的思路是对于一组性能权值为{a:2,b:4,c:4}的服务器列表,在十次请求的情况下,a要接收两次请求,b要接收4次请求,c要接收4四次请求
加权轮询算法解决方案如下
1. 在服务器数组S中,首先计算所有服务器权重的最大值max(S),以及所有服务器权重的最大公约数gcd(S)。
2. index表示本次请求到来时,选择的服务器的索引,初始值为-1;current_weight表示当前调度的权值,初始值为max(S)。
3. 当请求到来时,从index+1开始轮询服务器数组S,找到其中权重大于current_weight的第一个服务器,用于处理该请求。记录其索引到结果序列中。
4. 在轮询服务器数组时,如果到达了数组末尾,则重新从头开始搜索,并且减小current_weight的值:current_weight -= gcd(S)。如果current_weight等于0,则将其重置为max(S)。
/**
* 获取worker权值列表最大公约数
*
* @return
*/
private int getMaxGcd(List servers) {
int gcd = servers.get(0).getWeight();
for (int i = 1; i < servers.size(); i++) {
gcd = getGcd(gcd, servers.get(i).getWeight());
}
return gcd;
}
/**
* 使用辗转相除法获取两个数的最大公约数
*
* @param a
* @param b
* @return
*/
private int getGcd(int a, int b){
int c;
while (b > 0) {
c = b;
b = a % b;
a = c;
}
return a;
}
/**
* 获取worker列表的最大权重
*
* @param servers
* @return
*/
private int getMaxWeight(List servers) {
int max = servers.get(0).getWeight();
for (int i = 1; i < servers.size(); i++) {
if (max < servers.get(i).getWeight())
max = servers.get(i).getWeight();
}
return max;
}
public Worker route(String jobId, List workers) {
/**
* 在第一次调用的情况下,我们需要初始化如下三个参数
* 1.最大公约数
* 2.最大服务器性能权值
* 3. 当前权值
*/
if (curWeight < 0) {
maxGcd = getMaxGcd(workers);
maxWeight = getMaxWeight(workers);
curWeight = maxWeight;
}
while (true) {
for (; curIndex + 1 < workers.size(); ) {
curIndex += 1;
if (workers.get(curIndex).getWeight() >= curWeight) {
return workers.get(curIndex);
}
}
curWeight -= maxGcd;
curIndex = -1;
if (curWeight <= 0) {
curWeight = maxWeight;
}
}
}
我们利用上面实现的算法验证下是否达到了我们的需求,对于服务器列表{a,b,c}权值分别为{5,1,1},7次请求调用服务器a,b,c分别应该承受5,1,1次请求
public static void main(String[] args) {
WeightedRouteRound strategy = new WeightedRouteRound();
List workers = LoadBalanceUtil.getWorkers();
System.out.println("show workers >>>");
workers.stream().forEach(System.out::println);
System.out.println("route >>>");
for (int i = 0; i < 7; i++) {
Worker worker = strategy.route("jobId", workers);
System.out.println(worker);
}
}
结果如下
show workers >>>
Worker(ip=a, weight=5)
Worker(ip=b, weight=1)
Worker(ip=c, weight=1)
route >>>
Worker(ip=a, weight=5)
Worker(ip=a, weight=5)
Worker(ip=a, weight=5)
Worker(ip=a, weight=5)
Worker(ip=a, weight=5)
Worker(ip=b, weight=1)
Worker(ip=c, weight=1)
复合我的预期
加权轮询算法仍然不完美,它存在着在某一个时间点请求集中的落在权重较高的机器上,我们需要的负载均衡算法有平滑的分配请求的功能,比如对于上述的{a,a,a,a,a,b,c}负载我们希望的结果是{a,a,b,a,c,a,a}
算法如下
对于每个服务器有两个权重,一个是服务器性能权重weight,一个是服务器当前权重current_weight(初始值为0)
当请求到来的时候进行如下两步
1. 每次当请求到来,选取服务器时,会遍历数组中所有服务器。对于每个服务器,它的currentWeight=currentWeight+Weight;同时累加所有服务器的weight,并保存为total
2. 遍历完所有服务器之后,如果该服务器的current_weight是最大的,就选择这个服务器处理本次请求。最后把该服务器的current_weight减去total
//排序算子,让worker列表按当前权值的从大到小排序
private Comparator cmp = (o1, o2) -> o2.currentWeight - o1.currentWeight;
private LinkedList list = new LinkedList<>();
private boolean first = true;
@AllArgsConstructor
private class WrapedWorker {
Worker worker;
int currentWeight;
public int getWeight() {
return worker.getWeight();
}
}
@Override
public Worker route(String jobId, List workers) {
/**
* 如果是第一次调用该算法,创建对象WrapedWorker列表
*/
if (first) {
for (Worker worker : workers) {
list.add(new WrapedWorker(worker, 0));
}
first = false;
/**
* 创建完所有的WrapedWorker后进行排序
*/
list.sort(cmp);
}
int count = list.size();
int total = 0;
/**
* 遍历所有的元素,用元素当前权重=元素的权重+元素的当前权重
* 同时计算元素权重之和
*/
while (--count >= 0) {
WrapedWorker wrapedWorker = list.pollFirst();
wrapedWorker.currentWeight = wrapedWorker.getWeight() + wrapedWorker.currentWeight;
list.add(wrapedWorker);
total += wrapedWorker.currentWeight;
}
/**
* 排序选出最大的临时权重
*/
list.sort(cmp);
WrapedWorker worker = list.pollFirst();
/**
* 最大的临时权重减去所有权重
*/
worker.currentWeight = worker.currentWeight - total;
list.add(worker);
return worker.worker;
}
验证算法的可行性
public static void main(String[] args) {
SmoothWeightedRouteRound strategy = new SmoothWeightedRouteRound();
List workers = LoadBalanceUtil.getWorkers();
for (int i = 0; i < 7; i++) {
System.out.println(strategy.route("jobId", workers));
}
}
结果如下
Worker(ip=a, weight=5)
Worker(ip=a, weight=5)
Worker(ip=b, weight=1)
Worker(ip=a, weight=5)
Worker(ip=c, weight=1)
Worker(ip=a, weight=5)
Worker(ip=a, weight=5)