负载均衡算法WeightedRoundRobin(加权轮询)简介及算法实现
Nginx的负载均衡默认算法是加权轮询算法,本文简单介绍算法的逻辑,并给出算法的Java实现版本。
本文参考了Nginx的负载均衡 - 加权轮询 (Weighted Round Robin)。
算法简介
有三个节点{a, b, c},他们的权重分别是{a=5, b=1, c=1}。发送7次请求,a会被分配5次,b会被分配1次,c会被分配1次。
一般的算法可能是:
1、轮训所有节点,找到一个最大权重节点;
2、选中的节点权重-1;
3、直到减到0,恢复该节点原始权重,继续轮询;
这样的算法看起来简单,最终效果是:{a, a, a, a, a, b, c},即前5次可能选中的都是a,这可能造成权重大的服务器造成过大压力的同时,小权重服务器还很闲。
Nginx的加权轮询算法将保持选择的平滑性,希望达到的效果可能是{a, b, a, a, c, a, a},即尽可能均匀的分摊节点,节点分配不再是连续的。
Nginx加权轮询算法
1、概念解释,每个节点有三个权重变量,分别是:
(1) weight: 约定权重,即在配置文件或初始化时约定好的每个节点的权重。
(2) effectiveWeight: 有效权重,初始化为weight。
在通讯过程中发现节点异常,则-1;
之后再次选取本节点,调用成功一次则+1,直达恢复到weight;
此变量的作用主要是节点异常,降低其权重。
(3) currentWeight: 节点当前权重,初始化为0。
2、算法逻辑
(1) 轮询所有节点,计算当前状态下所有节点的effectiveWeight之和totalWeight;
(2) currentWeight = currentWeight + effectiveWeight; 选出所有节点中currentWeight中最大的一个节点作为选中节点;
(3) 选中节点的currentWeight = currentWeight - totalWeight;
基于以上算法,我们看一个例子:
这时有三个节点{a, b, c},权重分别是{a=4, b=2, c=1},共7次请求,初始currentWeight值为{0, 0, 0},每次分配后的结果如下:
请求序号 | 请求前currentWeight值 | 选中节点 | 请求后currentWeight值 |
1 | {c=1,b=2,a=4} | a | {c=1,b=2,a=-3} |
2 | {c=2,b=4,a=1} | b | {c=2,b=-3,a=1} |
3 | {c=3,b=-1,a=5} | a | {c=3,b=-1,a=-2} |
4 | {c=4,b=1,a=2} | c | {c=-3,b=1,a=2} |
5 | {c=-2,b=3,a=6} | a | {c=-2,b=3,a=-1} |
6 | {c=-1,b=5,a=3} | b | {c=-1,b=-2,a=3} |
7 | {c=0,b=0,a=7} | a | {c=0,b=0,a=0} |
观察到七次调用选中的节点顺序为{a, b, a, c, a, b, a},a节点选中4次,b节点选中2次,c节点选中1次,算法保持了currentWeight值从初始值{c=0,b=0,a=0}到7次调用后又回到{c=0,b=0,a=0}。
算法实现
下面附上笔者自己的Java版算法实现:
package com.example.demo.arithmetic; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map; /**
* Created by caojun on 2018/2/20.
*
* 基本概念:
* weight: 配置文件中指定的该后端的权重,这个值是固定不变的。
* effective_weight: 后端的有效权重,初始值为weight。
* 在释放后端时,如果发现和后端的通信过程中发生了错误,就减小effective_weight。
* 此后有新的请求过来时,在选取后端的过程中,再逐步增加effective_weight,最终又恢复到weight。
* 之所以增加这个字段,是为了当后端发生错误时,降低其权重。
* current_weight:
* 后端目前的权重,一开始为0,之后会动态调整。那么是怎么个动态调整呢?
* 每次选取后端时,会遍历集群中所有后端,对于每个后端,让它的current_weight增加它的effective_weight,
* 同时累加所有后端的effective_weight,保存为total。
* 如果该后端的current_weight是最大的,就选定这个后端,然后把它的current_weight减去total。
* 如果该后端没有被选定,那么current_weight不用减小。
*
* 算法逻辑:
* 1. 对于每个请求,遍历集群中的所有可用后端,对于每个后端peer执行:
* peer->current_weight += peer->effecitve_weight。
* 同时累加所有peer的effective_weight,保存为total。
* 2. 从集群中选出current_weight最大的peer,作为本次选定的后端。
* 3. 对于本次选定的后端,执行:peer->current_weight -= total。
*
*/
public class RoundRobinByWeightLoadBalance { //约定的invoker和权重的键值对
final private List<Node> nodes; public RoundRobinByWeightLoadBalance(Map<Invoker, Integer> invokersWeight){
if (invokersWeight != null && !invokersWeight.isEmpty()) {
nodes = new ArrayList<>(invokersWeight.size());
invokersWeight.forEach((invoker, weight)->nodes.add(new Node(invoker, weight)));
}else
nodes = null;
} /**
* 算法逻辑:
* 1. 对于每个请求,遍历集群中的所有可用后端,对于每个后端peer执行:
* peer->current_weight += peer->effecitve_weight。
* 同时累加所有peer的effective_weight,保存为total。
* 2. 从集群中选出current_weight最大的peer,作为本次选定的后端。
* 3. 对于本次选定的后端,执行:peer->current_weight -= total。
*
* @Return ivoker
*/
public Invoker select(){
if (! checkNodes())
return null;
else if (nodes.size() == 1) {
if (nodes.get(0).invoker.isAvalable())
return nodes.get(0).invoker;
else
return null;
}
Integer total = 0;
Node nodeOfMaxWeight = null;
for (Node node : nodes) {
total += node.effectiveWeight;
node.currentWeight += node.effectiveWeight; if (nodeOfMaxWeight == null) {
nodeOfMaxWeight = node;
}else{
nodeOfMaxWeight = nodeOfMaxWeight.compareTo(node) > 0 ? nodeOfMaxWeight : node;
}
} nodeOfMaxWeight.currentWeight -= total;
return nodeOfMaxWeight.invoker;
} public void onInvokeSuccess(Invoker invoker){
if (checkNodes()){
nodes.stream()
.filter((Node node)->invoker.id().equals(node.invoker.id()))
.findFirst()
.get()
.onInvokeSuccess();
}
} public void onInvokeFail(Invoker invoker){
if (checkNodes()){
nodes.stream()
.filter((Node node)->invoker.id().equals(node.invoker.id()))
.findFirst()
.get()
.onInvokeFail();
}
} private boolean checkNodes(){
return (nodes != null && nodes.size() > 0);
} public void printCurrenctWeightBeforeSelect(){
if (checkNodes()) {
final StringBuffer out = new StringBuffer("{");
nodes.forEach(node->out.append(node.invoker.id())
.append("=")
.append(node.currentWeight+node.effectiveWeight)
.append(","));
out.append("}");
System.out.print(out);
}
} public void printCurrenctWeight(){
if (checkNodes()) {
final StringBuffer out = new StringBuffer("{");
nodes.forEach(node->out.append(node.invoker.id())
.append("=")
.append(node.currentWeight)
.append(","));
out.append("}");
System.out.print(out);
}
} public interface Invoker{
Boolean isAvalable();
String id();
} private static class Node implements Comparable<Node>{
final Invoker invoker;
final Integer weight;
Integer effectiveWeight;
Integer currentWeight; Node(Invoker invoker, Integer weight){
this.invoker = invoker;
this.weight = weight;
this.effectiveWeight = weight;
this.currentWeight = 0;
} @Override
public int compareTo(Node o) {
return currentWeight > o.currentWeight ? 1 : (currentWeight.equals(o.currentWeight) ? 0 : -1);
} public void onInvokeSuccess(){
if (effectiveWeight < this.weight)
effectiveWeight++;
} public void onInvokeFail(){
effectiveWeight--;
}
} public static void main(String[] args){
Map<Invoker, Integer> invokersWeight = new HashMap<>(3);
Integer aWeight = 4;
Integer bWeight = 2;
Integer cWeight = 1; invokersWeight.put(new Invoker() {
@Override
public Boolean isAvalable() {
return true;
}
@Override
public String id() {
return "a";
}
}, aWeight); invokersWeight.put(new Invoker() {
@Override
public Boolean isAvalable() {
return true;
}
@Override
public String id() {
return "b";
}
}, bWeight); invokersWeight.put(new Invoker() {
@Override
public Boolean isAvalable() {
return true;
}
@Override
public String id() {
return "c";
}
}, cWeight); Integer times = 7;
RoundRobinByWeightLoadBalance roundRobin = new RoundRobinByWeightLoadBalance(invokersWeight);
for(int i=1; i<=times; i++){
System.out.print(new StringBuffer(i+"").append(" "));
roundRobin.printCurrenctWeightBeforeSelect();
Invoker invoker = roundRobin.select();
System.out.print(new StringBuffer(" ").append(invoker.id()).append(" "));
roundRobin.printCurrenctWeight();
System.out.println();
}
}
}
负载均衡算法WeightedRoundRobin(加权轮询)简介及算法实现的更多相关文章
- 负载均衡算法: 简单轮询算法, 平滑加权轮询, 一致性hash算法, 随机轮询, 加权随机轮询, 最小活跃数算法(基于dubbo) java代码实现
直接上干活 /** * @version 1.0.0 * @@menu <p> * @date 2020/11/17 16:28 */ public class LoadBlance { ...
- 负载均衡手段之DNS轮询
大多数域名注册商都支持对统一主机添加多条A记录,这就是DNS轮询,DNS服务器将解析请求按照A记录的顺序,随机分配到不同的IP上,这样就完成了简单的负载均衡.下图的例子是:有3台联通服务器.3台电信服 ...
- Nginx 做负载均衡的几种轮询策略
网上看见nginx的upstream目前支持的5种方式的分配,摘录备忘. 1.轮询(默认)每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除.upstream back ...
- Nginx做负载均衡的几种轮询策略
集群环境为了解决单点无法支撑高并发的情况,集群采用多台服务器提供服务,一般在集群中使用nginx 将来自客户端的请求转发给服务器端 nginx负载均衡可用提高网站的吞吐量,缓解单台服务器的压力. 一. ...
- Dubbo加权轮询负载均衡的源码和Bug,了解一下?
本文是对于Dubbo负载均衡策略之一的加权随机算法的详细分析.从2.6.4版本聊起,该版本在某些情况下存在着比较严重的性能问题.由问题入手,层层深入,了解该算法在Dubbo中的演变过程,读懂它的前世今 ...
- Java实现负载均衡算法--轮询和加权轮询
1.普通轮询算法 轮询(Round Robin,RR)是依次将用户的访问请求,按循环顺序分配到web服务节点上,从1开始到最后一台服务器节点结束,然后再开始新一轮的循环.这种算法简单,但是没有考虑到每 ...
- Nginx 负载均衡-加权轮询策略剖析
本文介绍的是客户端请求在多个后端服务器之间的均衡,注意与客户端请求在多个nginx进程之间的均衡相区别(Nginx根据每个工作进程的当前压力调整它们获取监听套接口的几率,那些当前比较空闲的工作进程有更 ...
- 【Nginx】负载均衡-加权轮询策略剖析
转自:江南烟雨 本文介绍的是客户端请求在多个后端服务器之间的均衡,注意与客户端请求在多个nginx进程之间的均衡相区别. 如果Nginx是以反向代理的形式配置运行,那么对请求的实际处理需要转发到后端服 ...
- Nginx的负载均衡 - 加权轮询 (Weighted Round Robin) 下篇
Nginx版本:1.9.1 我的博客:http://blog.csdn.net/zhangskd 上篇blog讲述了加权轮询算法的原理.以及负载均衡模块中使用的数据结构,接着我们来看看加权轮询算法的具 ...
随机推荐
- mybaties数据源配置类型(POOLED、JNDI、UNPOOLED)
dataSource的类型可以配置成其内置类型之一,如UNPOOLED.POOLED.JNDI. 如果将类型设置成UNPOOLED,mybaties会为每一个数据库操作创建一个新的连接,并关闭它.该方 ...
- 域名Whois数据和隐私是最大风险
在互联网安全大会上,东方联盟掌门人,东方联盟郭盛华呼吁RIR机构应为全球协调分配地址上作出改进,实现whois数据准确性的前进方向.他还说,域名whois隐私信息应默认对外关闭,同时最近APNIC合作 ...
- VUE 生成二维码插件
原文:https://www.jianshu.com/p/496fd1cbee8d npm install qrcodejs2 --save 页面中引入 dom 结构 JS 方法编写 export d ...
- springboot-启动一段时间图片不能上传
问题:[B2B]后台服务.PC服务.APP服务.仓储服务,启动一段时间图片不能上传. 原因:/tmp下以tomcat开头的目录被清理了. 处理方案:1.找到涉及服务器 注:后台服务.PC服务.APP服 ...
- Intent.java分析
代码位于frameworks/base/core/java/anroid/Content/Intent.java Intent是对要进行操作的一种抽象描述.用action抽象操作,用data(andr ...
- 科讯使用的:ckeditor编辑器.复制word图片.一直沾不上去.谁有好的解决办法呢
在之前在工作中遇到在富文本编辑器中粘贴图片不能展示的问题,于是各种网上扒拉,终于找到解决方案,在这里感谢一下知乎中众大神以及TheViper. 通过知乎提供的思路找到粘贴的原理,通过TheViper找 ...
- [hadoop](3) MapReduce:创建计数器、任务状态和写入日志
前言 本章主要讲述了如何在mapreduce任务中添加自定义的计数器,从所有任务中聚合信息,并且最终输出到mapreduce web ui中得到统计信息. 准备工作 数据集:ufo-60000条记录, ...
- git 几个commit点合并成一个commit点
在用git做版本控制器的时候,经常会遇到以下情况: 1.在做1个功能的时候,你自己觉得代码没问题了,就本地commit,然后提交代码,在gitlab上发起和并请求,老大看完之后,觉得你还有修改的地方, ...
- [CSP-S模拟测试]:赛(贪心+三分)
题目描述 由于出题人思维枯竭所以想不出好玩的背景.有$n$个物品,第$i$个物品的价格是$v_i$,有两个人,每个人都喜欢$n$个物品中的一些物品.要求选出正好$m$个物品,满足选出的物品中至少有$k ...
- 定时任务cron表达式解析
cron表达式2种: Seconds Minutes Hours DayofMonth Month DayofWeek Year或 Seconds Minutes Hours DayofMonth M ...