朝不同往昔,卖惨成为主流旋律,也加剧了从业人员的焦虑。很多人,工作了十来年没碰过算法,如今却不得不像蹲自习室一样,捧起大头书死命去看。

呜呼哀哉。

最近和不少参加面试的小伙伴交流了一下,发现出现了一个比较高频的算法题。不同于链表、树、动态规划这些有规律可循的算法题,加权轮询算法有很多小的技巧,在实际应用中也比较多。最平滑的Nginx轮询算法,如果你没有见过的话,那自然是永远无法写出来的。

所谓的加权轮询算法,其实就是Weighted Round Robin,简称wrr。在我们配置Nginx的upstream的时候,带权重的轮询,其实就是wrr。

upstream backend {ip_hash;server 192.168.1.232 weight=4; server 192.168.1.233 weight=3;server 192.168.1.234 weight=1;
}
复制代码

1. 核心数据结构

为了方便编码,对于每一个被调度的单元来说,我们抽象出一个叫做Element的类。其中,peer指的是具体的被调度资源,比如IP地址,而weight指的是这个资源的相关权重。

public class Element {protected String peer;protected int weight;public Element(String peer, int weight){this.peer = peer;this.weight = weight;}
}
复制代码

那么我们具体的调度接口,将直接返回peer的地址。

public interface IWrr {String next();
}
复制代码

我们将在代码中直接测试IWrr接口的调度情况。比如,分配7、2、1权重的三个资源,其测试代码如下。

Element[] elements = new Element[]{new Element("A", 7),new Element("B", 2),new Element("C", 1),
};
int count = 10;
IWrr wrr = new WrrSecurityLoopTreeMap(elements);
for (int i = 0; i < count; i++) {System.out.print(wrr.next() + ",");
}
System.out.println();
复制代码

上面的代码调用了10次接口,我们希望代码实现,将以7,2,1的比例进行调度。

2. 随机数版本

最简单的方式,就是使用随机数去实现。当然,只有在请求量比较大的情况下,随机分布才会向7、2、1的比例逼近。这通常都没什么问题,比如SpringCloud的Robion组件,就是使用随机轮询的方式。

我们首先计算总的权重值,记作total,然后每次调用都取total区间的随机数,再依次遍历所有的权重数据。

next方法的时间复杂度,在最坏的情况下是O(n)。

随机调度获取的调用顺序也是随机的,对类似于微服务节点轮询这种场景,比较友好。但对于一些调用量比较小的服务,可能有些节点就会被饿死,毕竟是随机数嘛。

public class WrrRnd implements IWrr {final int total;final Element[] elements;final Random random = new SecureRandom();public WrrRnd(Element[] elements) {this.total = Arrays.stream(elements).mapToInt(ele -> ele.weight).sum();this.elements = elements;}@Overridepublic String next() {final int n = elements.length;int index = n - 1;int hit = random.nextInt(total);for(int i = 0; i < n; i++){if(hit >= 0) {hit -= elements[i].weight;}else{index = i - 1;break;}}return elements[index].peer;}
}
复制代码

3. 递增版本

随机数大多数情况下是美好的,但有时候我们确实需要非常准确的调度结果。这种情况下,使用一个原子递增的计数器,去存放当前的调度次数,是常见的方式。

所以逻辑就比较清晰了,我们可以直接使用原子类去实现这个计数器。

代码与上面的类似,只不过在获取hit变量的时候,我们把随机数的获取方式,替换成自增的方式。

//原来的
int hit = random.nextInt(total);
复制代码

现在的。当然,它还有一个小小的问题,那就是int的数值很可能会被用完了,这个小问题在下面的代码一并修复。

int hit = count.getAndIncrement() % total;
复制代码

4. 红黑树版本

不论是随机数还是按照顺序轮询,它们的时间复杂度都是比较高的,因为它每次都需要遍历所有的配置项,直到达到我们所需要的数值。要想提高其运行效率,我们可以借助于Java的TreeMap,空间上换时间。

下面是一个线程安全版本的实现方法,使用物理上的存储来解决时间上的耗费。TreeMap底层是红黑树,实现了根据Key的大小进行排序的功能,它的平均时间复杂度是log(n)。

我们把上面代码的逻辑,直接转化成TreeMap存储,就可以通过ceilingEntry方法获取最近的调度单元。

在并发上面,直接使用了CAS原语。这时候,我们不再自增,而是将最大值严格控制在total以下,通过自旋来处理冲突。

public class WrrSecurityLoopTreeMap implements IWrr {final int total;final AtomicInteger count = new AtomicInteger();final TreeMap<Integer, Element> pool = new TreeMap<>();public WrrSecurityLoopTreeMap(Element[] elements) {int total = 0;for (Element ele : elements) {total += ele.weight;pool.put(total - 1, ele);}this.total = total;}@Overridepublic String next() {final int modulo = total;for (; ; ) {int hit = count.get();int next = (hit + 1) % modulo;if (count.compareAndSet(hit, next) && hit < modulo) {return pool.ceilingEntry(hit).getValue().peer;}}}
}
复制代码

5. LVS版本

上面的这些版本(除了随机),有一个最大的问题,就是调度不均衡。当我们的比例是7、2、1,它的调度结果是A,A,A,A,A,A,A,B,B,C,。

我们希望调度能够平滑一些,而不是一股脑的压在A节点上。下面是LVS代码里的一个算法,采用的是最大公约数来实现轮询。虽然它不能实现非常平滑的轮询,但起码比上面的自增式代码强多了。

这段代码的执行过程就包含两部分,一部分是计算最大公约数gcd,一部分是轮询算法。

对于7、2、1的权重,它的调度结果是A,A,A,A,A,A,B,A,B,C,,相比较按顺序轮询的方式,有了一些改善。当这些节点的权重数值差不多的时候,LVS版本会表现出较好的负载均衡效果。

我们首先在构造函数里,算出最大公约数的gcd。然后,基于这个最大公约数,进行轮询算法的运算。

根据介绍的地址,可以很容易写出对应的算法。

http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling
复制代码

下面是具体的代码。

public class WrrGcd implements IWrr {final int gcd;final int max;final Element[] elements;public WrrGcd(Element[] elements) {Integer gcd = null;int max = 0;for (Element ele : elements) {gcd = gcd == null ? ele.weight : gcd(gcd, ele.weight);max = Math.max(max, ele.weight);}this.gcd = gcd;this.max = max;this.elements = elements;}int i = -1;int cw = 0;@Overridepublic String next() {for (; ; ) {final int n = elements.length;i = (i + 1) % n;if (i == 0) {cw = cw - gcd;if (cw <= 0) {cw = max;if (cw == 0) {return null;}}}if(elements[i].weight >= cw){return elements[i].peer;}}}private int gcd(int a, int b) {return b == 0 ? a : gcd(b, a % b);}
}
复制代码

6. Nginx版本

nginx这个版本就更上一层楼,可以达到A,A,B,A,A,C,A,A,B,A,的效果。在保证准确的权重前提下,实现了调用尽量的分散。

这个算法比较巧妙,可以说是非常天才的算法。如果你没有接触过的话,是绝对写不出来的。

虽然算法比较简单,但要证明算法的准确性却不是一件容易的事情。证明的具体过程可以参考以下链接。

https://tenfy.cn/2018/11/12/smooth-weighted-round-robin/
复制代码

看我们的代码,封装了一个叫做Wrr的类。这个类在原来权重的基础上,增加了一个当前的权重值current。current没次调用都会改变。

在每一轮调用中,都会在current上加上对应节点的weight值,然后选择current值最大的那一个,当作本轮的调度节点。

被选中的节点,将会减去所有的权重值total,然后进行下一次调度。唯一的问题是,当节点比较多的时候,它的时间复杂度总是O(n),执行效率上要打一些折扣。

public class WrrSmooth implements IWrr {class Wrr {Element ele;int current = 0;Wrr(Element ele){this.ele = ele;}}final Wrr[] cachedWeights;public WrrSmooth(Element[] elements) {this.cachedWeights = Arrays.stream(elements).map(Wrr::new).collect(Collectors.toList()).toArray(new Wrr[0]);}@Overridepublic String next() {int total = 0;Wrr shed = cachedWeights[0];for(Wrr item : cachedWeights){int weight = item.ele.weight;total +=  weight;item.current += weight;if(item.current > shed.current){shed = item;}}shed.current -= total;return shed.ele.peer;}
}
复制代码

Nginx的这个版本,写法非常简单。建议好好理解,掌握红黑树和Ningx版本的写法即可。

End

一般的面试,其实集中在随机数和递增版本上,当然红黑树这一版也可以考虑一下。至于LVS和Nginx的这些写法,如果以前没有碰到过,大概率是写不出来的,除非你是天才。

但是如果你是天才,还用得着这样粗俗的面试么?

加权轮询算法(wrr),这个考点,概率有点高相关推荐

  1. 加权轮询算法(wrr),这个考点,概率有点高!

    原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处. 临近年关,招聘的和找工作的却忙的热火朝天,互相拿捏着. 今朝不同往昔,卖惨成为主流旋律,也加剧了从业人员的焦虑.很多人,工 ...

  2. 加权轮询算法(wrr),这个考点,概率有一点点高哦

    临近年关,招聘的和找工作的却忙的热火朝天,互相拿捏着. 今朝不同往昔,卖惨成为主流旋律,也加剧了从业人员的焦虑.很多人,工作了十来年没碰过算法,如今却不得不像蹲自习室一样,捧起大头书死命去看. 呜呼哀 ...

  3. 负载均衡之加权轮询算法

    在介绍加权轮询算法(WeightedRound-Robin)之前,首先介绍一下轮询算法(Round-Robin). 一:轮询算法(Round-Robin) 轮询算法是最简单的一种负载均衡算法.它的原理 ...

  4. 负载均衡--加权轮询算法(Weight Round)

    加权轮询算法:不同的后端服务器,在机器的配置和当前系统的负载方面,可能并不相同.因此,它们的抗压能力也不相同.给配置高.负载低的机器配置更高的权重,让其处理更多的请求:给配置低.负载高的机器分配较低的 ...

  5. 加权轮询算法PHP,PHP实现负载均衡的加权轮询方法分析

    本文实例讲述了PHP实现负载均衡的加权轮询方法.分享给大家供大家参考,具体如下: 1. 负载均衡算法有哪些? 轮询法:将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务 ...

  6. dubbo负载算法之加权轮询算法

    轮询是一种无状态负载均衡算法,实现简单,适用于每台服务器性能相近的场景下.但现实情况下,我们并不能保证每台服务器性能均相近.如果我们将等量的请求分配给性能较差的服务器,这显然是不合理的.因此,这个时候 ...

  7. 负载均衡算法 : 加权轮询

    1 加权轮询算法背景 轮询算法没有考虑每台服务器的处理能力,实际情况是每台服务器的配置.安装的业务应用等不同,其处理能力会不一样.所以,加权轮询算法的原理就是:根据服务器的不同处理能力,给每个服务器分 ...

  8. java轮训算法_负载均衡算法WeightedRoundRobin(加权轮询)简介及算法实现

    Nginx的负载均衡默认算法是加权轮询算法,本文简单介绍算法的逻辑,并给出算法的Java实现版本. 算法简介 有三个节点{a, b, c},他们的权重分别是{a=5, b=1, c=1}.发送7次请求 ...

  9. 负载均衡算法--加权轮询法(Weight Round Robin)

    接上一篇博文:负载均衡算法–轮询法(Round Robin),本文讲解加权轮询算法. 加权轮询算法:不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同.给配置高.负载低 ...

最新文章

  1. javascript中async await的用法
  2. ssh框架点击按钮就404_设置404错误页面的5大关键因素
  3. Spring Boot-@Configuration注解
  4. vscode could not establish connection to linux The VS Code Server failed to start
  5. 蓝桥杯 试题 基础练习 字母图形——13行代码AC
  6. 用JAVA开发网站,需要学哪些呢?
  7. 《DSP using MATLAB 》示例Example6.3
  8. 《转》不要过打折的生活,当你发现这些你有了,说明你开始成熟了
  9. sql 数据分组统计与合计
  10. 用脚踹?地震火灾中,如何快速打开人脸识别闸机门?
  11. 怎样学好python编程-怎样学习python编程?
  12. JavaScript基础--DOM部分02--李南江
  13. java 字符串中提取数字_java从字符串中提取数字的简单实例
  14. 健身运动App需求分析
  15. 高清美图和GIF动图素材网站推荐,拿走不谢!
  16. 遵循亚马逊标准!Kindle 电子书专业制作教程
  17. 计算机主机硬件图片,电脑主机里的各个硬件名称图片用途和使用说明
  18. QuickBI-云数据库数据源
  19. 两数之和、三数之和、四数之和、K数之和
  20. python imshow调整比例_如何在不拉伸图像的情况下更改matplotlib中imshow的比例?

热门文章

  1. 阅读笔记 PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation
  2. 无刷直流电机与有刷直流电机的区别
  3. 【线性稳压电源】0-60V-0-20A恒流恒压可调稳压电源原理图文件+PCB文件
  4. 网络拨测调研分析总结
  5. ArcGIS 与无人机视频集成应用新模式
  6. 无线智能插座运用关键技术介绍
  7. ubuntu 镜像下载网站汇总
  8. vue data不可以使用this定义
  9. 计算机主机箱内的各个组件都有哪些,电脑都有些什么部件构成?各个部件的主要功能是什么?...
  10. HTTP 中get、put、post的介绍