}
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;
}
@Override
public 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;
@Override
public 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]);
}
@Override
public String next() {
int total = 0;
Wrr shed = cachedWeights[0];
for(Wrr item : cachedWeights){
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
笔者福利
以下是小编自己针对马上即将到来的金九银十准备的一套“面试宝典”,不管是技术还是HR的问题都有针对性的回答。
有了这个,面试踩雷?不存在的!
回馈粉丝,诚意满满!!!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
利
以下是小编自己针对马上即将到来的金九银十准备的一套“面试宝典”,不管是技术还是HR的问题都有针对性的回答。
有了这个,面试踩雷?不存在的!
回馈粉丝,诚意满满!!!
[外链图片转存中…(img-A2N2kGsm-1712856420024)]
[外链图片转存中…(img-xKCesYUM-1712856420024)]
[外链图片转存中…(img-GHmCxX2z-1712856420024)]
[外链图片转存中…(img-bDovHrWE-1712856420025)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!