负载均衡算法 - 平滑加权轮询算法

802 阅读2分钟

问题:

由于机器的配置不同,需要给不同的机器配置不同的流量比例,并且还需要轮询的访问,避免单点过热的问题

这来看简单的轮询算法(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()));

        }
    }
}

证明:

整个算法可以简化为两步

  1. 每次执行把当前权重加上节点设置的权重

  2. 选择最大权重的节点并且减去总权重。

这样的话我们只需证明两点

  1. 每一轮执行的次数是加权的,也就是按照比例执行的。

  2. 每次执行完的下个节点和上个节点不同,也就是平滑的。

证明加权:

假设:

  • 共有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

故得证:节点不会被连续选择