Nginx负载均衡算法之加权轮询 java实现

今天在看《大型网站技术架构》时,里面介绍负载均衡算法时提到加权轮询(Weighted Round Robin) , 对其具体实现不太熟悉,网上的介绍有点乱,不太好理解,所以这里自己实现一下这个算法,以自己能通俗理解的方式记录一下

一. 加权轮询在nginx中的部分配置
http {  
    upstream cluster {  
        server 192.168.1.2 weight=5;  
        server 192.168.1.3 weight=3;  
        server 192.168.1.4 weight=1;  
    }  
    ...
location / {
       proxy_set_header X-Real-IP $remote_addr;               //返回真实IP
       proxy_pass http://cluster;                           //代理指向cluster 
   }

二. 加权轮询算法介绍

  1. 由于不同服务器的配置、部署的应用不同,其处理能力会不一样。所以,加权轮询算法的原理就是:根据服务器的不同处理能力,给每个服务器分配不同的权值,使其能够接受相应权值数的服务请求。
  2. Nginx加权轮询算法中,每个节点有三个权重变量
  • weight : 配置的权重,即在配置文件或初始化时约定好的每个节点的权重
  • currentWeight : 节点当前权重,会一直变化
  • effectiveWeight :有效权重,初始值为weight, 通讯过程中发现节点异常,则-1 ,之后再次选取本节点,调用成功一次则+1,直达恢复到weight 。 用于健康检查,处理异常节点,降低其权重。
  1. 算法逻辑
    (1) 轮询所有节点,计算当前状态下所有节点的effectiveWeight之和totalWeight
    (2) currentWeight = currentWeight + effectiveWeight; 选出所有节点中currentWeight中最大的一个节点作为选中节点;
    (3) 选中节点的currentWeight = currentWeight - totalWeight
    注:(为了简单清晰,后面的实现不考虑健康检查effectiveWeight这个功能实现,假设所有节点都是100%可用,所以上面的逻辑要把使用effectiveWeight的地方换成weight
  2. 例子
    假设有三个节点{serverA , serverB , serverC} , 权重为分别为{4,3,2} ,现在请求7次的分配情况如下:
请求次数 请求前currentWeight 选中的节点 请求后currentWeight
1 [ serverA= 4,serverB= 3,serverC= 2 ] serverA [ serverA= -1,serverB= 6,serverC= 4 ]
2 [ serverA= -1,serverB= 6,serverC= 4 ] serverB [ serverA= 3,serverB= 0,serverC= 6 ]
3 [ serverA= 3,serverB= 0,serverC= 6 ] serverC [ serverA= 7,serverB= 3,serverC= -1 ]
4 [ serverA= 7,serverB=3,serverC= -1 ] serverA [ serverA= 2,serverB= 6,serverC= 1 ]
5 [ serverA= 2,serverB= 6,serverC= 1 ] serverB [ serverA= 6,serverB= 0,serverC= 3 ]
6 [ serverA= 6,serverB= 0,serverC= 3 ] serverA [ serverA= 1,serverB= 3,serverC= 5 ]
7 [ serverA= 1,serverB= 3,serverC= 5 ] serverC [ serverA= 5,serverB= 6,serverC= -2 ]

totaoWeight = 4 + 3 + 2 = 9 ,
第一次请求 serverA= 4 + 4(原始比重) = 8 ,serverB= 3+3(原始比重) = 6,serverC= 2+2(原始比重) = 4 , 最大的是serverA , 所以 serverA= 8 - 9 (权重总和) = -1 ,所以最后值为 serverA= -1,serverB= 6,serverC= 4
第二次请求 serverA= -1 + 4(原始比重) = 3 ,serverB= 6+3(原始比重) = 9,serverC= 4+2(原始比重) = 6 , 最大的是serverB , 所以 serverB= 9 - 9 (权重总和) = 0 ,所以最后值为 serverA= 3,serverB= 0,serverC= 6
依此类推...

三. 代码实现(java)

  • DemoApplication main方法
package com.example.demo;

public class DemoApplication {
    public static void main(String[] args) {
        /**
         * 假设有三个服务器权重配置如下:
         * server A  weight = 4 ;
         * server B  weight = 3 ;
         * server C  weight = 2 ;
         */
        Node serverA = new Node("serverA", 4);
        Node serverB = new Node("serverB", 3);
        Node serverC = new Node("serverC", 2);
        
        SmoothWeightedRoundRobin smoothWeightedRoundRobin = new SmoothWeightedRoundRobin(serverA,serverB ,serverC);
        for (int i = 0; i < 7; i++) {
            Node i1 = smoothWeightedRoundRobin.select();
            System.out.println(i1.getServerName());
        }
    }
}
  • SmoothWeightedRoundRobin 加权轮询算法
package com.example.demo;

import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

public class SmoothWeightedRoundRobin {
    private volatile List nodeList = new ArrayList<>() ; // 保存权重
    private ReentrantLock lock = new ReentrantLock() ;

    public SmoothWeightedRoundRobin(Node ...nodes) {
        for (Node node : nodes) {
            nodeList.add(node) ;
        }
    }

    public Node select(){
        try {
            lock.lock();
            return this.selectInner() ;
        }finally {
            lock.unlock();
        }
    }

    private Node selectInner(){
        int totalWeight = 0 ;
        Node maxNode = null ;
        int maxWeight = 0 ;

        for (int i = 0; i < nodeList.size(); i++) {
            Node n = nodeList.get(i);
            totalWeight += n.getWeight() ;

            // 每个节点的当前权重要加上原始的权重
            n.setCurrentWeight(n.getCurrentWeight() + n.getWeight());

            // 保存当前权重最大的节点
            if (maxNode == null || maxWeight < n.getCurrentWeight() ) {
                maxNode = n ;
                maxWeight = n.getCurrentWeight() ;
            }
        }
        // 被选中的节点权重减掉总权重
        maxNode.setCurrentWeight(maxNode.getCurrentWeight() - totalWeight);

//        nodeList.forEach(node -> System.out.print(node.getCurrentWeight()));
        return maxNode ;
    }
}
  • Node 服务节点
package com.example.demo;

public class Node {
    private final int weight ;  // 初始权重 (保持不变)
    private final String serverName ; // 服务名

    private int currentWeight ; // 当前权重

    public Node( String serverName, int weight) {
        this.weight = weight;
        this.serverName = serverName ;
        this.currentWeight = weight ;
    }

    public int getCurrentWeight() {
        return currentWeight;
    }

    public int getWeight() {
        return weight;
    }

    public void setCurrentWeight(int currentWeight) {
        this.currentWeight = currentWeight;
    }

    public String getServerName() {
        return serverName;
    }
}

这个算法相对保证了的平滑性和均匀性

你可能感兴趣的:(Nginx负载均衡算法之加权轮询 java实现)