问题:
由于机器的配置不同,需要给不同的机器配置不同的流量比例,并且还需要轮询的访问,避免单点过热的问题
这来看简单的轮询算法(1,2,3,1,2,3,1,2,3)这种方式就无法满足。
假设有三台机器,分别为1核、2核、4核,对应为a,b,c,分别对应权重1:2:4。
当a机器调用1次,b机器调用2次,c机器调用4次即为轮询1次。并且不能单点连续访问。
实现:
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @author chenwenxin
* @since 2020-03-26 16:50
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ServerNode {
private String hostname;
/** 设置的weight */
private int weight;
/** 当前weight */
private int currentWeight;
public void selected(int total) {
currentWeight -= total;
}
public void increaseCurrentWeight() {
currentWeight += weight;
}
}
import org.chenwenxin.algorithm.loadbalance.LoadBalance;
import org.chenwenxin.algorithm.loadbalance.ServerNode;
import java.util.ArrayList;
import java.util.List;
/**
* 负载均衡 - 加权轮询算法
* @author chenwenxin
* @since 2020-03-26 16:52
*/
public class WeightRoundRobinLoadBalance implements LoadBalance {
@Override
public ServerNode select(List<ServerNode> serverNodes) {
ServerNode serverNodeSelected = null;
int maxWeight = Integer.MIN_VALUE;
int totalWeight = 0;
for (ServerNode serverNode : serverNodes) {
// 增加各自的 currentWeight
serverNode.increaseCurrentWeight();
if (serverNode.getCurrentWeight() > maxWeight) {
maxWeight = serverNode.getCurrentWeight();
serverNodeSelected = serverNode;
}
totalWeight += serverNode.getWeight();
}
if (serverNodeSelected != null) {
// 被选中的节点,currentWeight 需要减去所有 weight 之和
serverNodeSelected.selected(totalWeight);
return serverNodeSelected;
}
// should not happen here
return serverNodes.get(0);
}
// 测试
public static void main(String[] args) {
WeightRoundRobinLoadBalance lb = new WeightRoundRobinLoadBalance();
List<ServerNode> servers = new ArrayList<>();
// 初始化3个节点:a、b、c,权重分别是:1、2、4
servers.add(new ServerNode("a", 1, 0));
servers.add(new ServerNode("b", 2, 0));
servers.add(new ServerNode("c", 4, 0));
for (int i = 0; i < 15; i++) {
if (i % 7 == 0) {
System.out.println(String.format("\n第 %s 轮请求======:", (i / 7) + 1));
}
ServerNode selected = lb.select(servers);
System.out.println(String.format("第 %s 次请求,选中机器:%s", i + 1, selected.getHostname()));
}
}
}
证明:
整个算法可以简化为两步
-
每次执行把当前权重加上节点设置的权重
-
选择最大权重的节点并且减去总权重。
这样的话我们只需证明两点
-
每一轮执行的次数是加权的,也就是按照比例执行的。
-
每次执行完的下个节点和上个节点不同,也就是平滑的。
证明加权:
假设:
-
共有n个节点
-
第i个节点的权重为Xi
-
总权重为S,S=X1+X2+X3···+Xn
证明:
因为:初始权重为[0,0,0···,0]
则:执行第一步后权重为[X1,X2,X3···,Xn]
如果:第j个节点权重最大
则:当前权重为[X1,X2,Xj-S···,Xn]
则:当前权重和为X1+X2+X3···+Xn-S = 0
设:节点i被选择了Mi次,节点i的当前权重为Wi
设:节点j在t(t<S)之前已经被选择了Xj
则:节点j的当前权重为Wj=t*Xj-Xj*S=Xj(t-S)
由于:t-S<0
则:Wj<0
由于:S>0,当t<S,Wj时,必然存在节点权重>0,所以节点j最多可选择Wj次。
设:节点j在t(t<S)时,执行了Xj-1次
则:Wj=t*Xj-(Xj-1)*S = (t-S)Xj+S
由于:Xj<S
则:必然存在一次使得Wj>0
综上所述:节点j在S次执行中只能被执行Xj次
故得证:节点的选择次数按照加权值来执行
证明平滑:
要证明平滑,只需证明节点不是一直被选择即可。
假设:
-
总权重为S
-
当前执行次数为t(S>t)
-
节点j可以被连续选择
-
当前被选择了t次
-
t=Xj-1
证明:
由上可得:
Wj=(Xj-1)*Xj-(Xj-1)S
=Xj*Xj-Xj-Xj*S+S
则:当下次执行时第一步执行完后
Wj=Xj*Xj-Xj*S+S
=(Xj-1)(Xj-S)+Xj
故只要证明此时别的节点的权重大于当前节点即可
由于:Xj-S<-1
则:放大后Wj<1
故:必然有另一个节点Wi=t*Xi>1
故得证:节点不会被连续选择