负载均衡算法实现

一.负载均衡介绍

1.什么是负载均衡?

用户->负载均衡平衡器->多台服务器(一个集群下) 负载均衡就是负载均衡器中实现的算法或者硬件设施.同时解决大量并发访问服务器问题.

2.负载均衡的方式?

软件负载均衡:Nginx.LVS,HAProxy

硬件负载均衡:Arrays,F5

3.负载均衡算法

	//正常分配
	public static final List<String> LIST = Arrays.asList(
            "192.168.0.1",
            "192.168.0.2",
            "192.168.0.3",
            "192.168.0.4",
            "192.168.0.5",
            "192.168.0.6",
            "192.168.0.7",
            "192.168.0.8",
            "192.168.0.9",
            "192.168.0.10"  //这一台性能高
    );
	//考虑权重
    public static final Map<String,Integer> WEIGHT_MAP = new LinkedHashMap<>();
    static {
        WEIGHT_MAP.put("192.168.0.1",2);
        WEIGHT_MAP.put("192.168.0.2",8);
        WEIGHT_MAP.put("192.168.0.3",1);
        WEIGHT_MAP.put("192.168.0.4",9);
        WEIGHT_MAP.put("192.168.0.5",4);
        WEIGHT_MAP.put("192.168.0.6",6);
    }
3.1随机算法

1.正常随机

	public static String getServer() {
        Random random = new Random();
        return ClentTest.LIST.get(random.nextInt(ClentTest.LIST.size()));s
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(getServer());s
        }
    }

2.ip + 权重系数

	public static String getServer() {
        List<String> ips = new ArrayList<>();
        ClentTest.WEIGHT_MAP.keySet().stream().forEach(ip -> {
            Integer weight = ClentTest.WEIGHT_MAP.get(ip);
            for (int i = 0; i < weight; i++) {
                ips.add(ip);
            }
        });
        Random random = new Random();
        return ClentTest.LIST.get(random.nextInt(ClentTest.LIST.size()));

    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(getServer());
        }
    }

3.随机分配理解和改进

A服务器权重系数为5 也是服务器调用的次数

B 为 3

C 为 2

看作数组为[5,3,2],看作定点长 5+3+2 = 10 权重总和

0-----5—8–10

​ A B C 服务器所在位置

1.假设一个随机数offest = 7;随机0-5则取A,5-8则取B,8-10则取C,但是0-5的距离长所以占权重也多.

2.因为随机是7所以定点落在A-B上.

offest > 5; offest = offest - 5; offest = 2;

定点坐标改变为 0—3–5 :因为减去了5

offest < 3;return B; 减去的步骤是将A服务器排除,同时offest一定要比服务器数值小.

	public static String getServer() {
        //流计算整数和
        int totalWeight = ClentTest.WEIGHT_MAP.values().stream().mapToInt(weight -> weight).sum();
        //System.out.println(totalWeight);
        int offest = new Random().nextInt(totalWeight);
        for (String ip : ClentTest.WEIGHT_MAP.keySet()) {
            Integer weight = ClentTest.WEIGHT_MAP.get(ip);
            if (offest < weight) {
                return ip;
            }
            offest = offest - weight;
        }
        Random random = new Random();
        return ClentTest.LIST.get(random.nextInt(ClentTest.LIST.size()));

    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(getServer());
        }
    }
3.2轮询算法

1.正常实现

	private static Integer pos = 0; //轮询下标
    public static String getServer() {
        if (pos >= ClentTest.LIST.size()) {
            pos = 0;
        }
        String ip = ClentTest.LIST.get(pos);
        pos ++;
        return ip;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            System.out.println(getServer());
        }
    }

2.考虑权重去实现

假设offest = 1,2,3,4,5,…有规律的递增的轮询,也可以通过上述实现(递增的范围跟服务器的权重总和有关)

A服务器权重系数为5 也是服务器调用的次数

B 为 3

C 为 2

offest > 5; offest = offest - 5; offest = 2;

offest < 3;return B;

则出现: AAAAABBBCC

同时我们可以得出上述逻辑根据offest的结果不同则产生的影响也不同,在轮询随机中都可以采用这种算法

我们实现轮询可以根据requestID(主键递增的),如果ID到达1000,1001…

//requestID的生成
	public static Integer num = 0;
    public static Integer getAndIncrement() {
        return num++;
    }

1.我们采用%实现

	public static String getServer() {
        int totalWeight = 0;
        for (Integer weight : ClentTest.WEIGHT_MAP.values()) {
            totalWeight += weight;
        }
        int requestID = RequestID.getAndIncrement(); //0--10000
        int offset = requestID % totalWeight;
        for (String ip : ClentTest.WEIGHT_MAP.keySet()) {
            Integer weight = ClentTest.WEIGHT_MAP.get(ip);

            if (offset < weight) {
                return ip;
            }

            offset = offset - weight;
        }

        return null;

    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            System.out.println(getServer());
        }
    }

这样实现产生问题:如果

A服务器权重系数为5 也是服务器调用的次数

B 为 1

C 为 1

则产生AAAAABC,但是采用平衡加权轮询算法则可以产生AABACAA,可以使得某时刻A服务器的压力不是那么大

问题:那么轮询之后还是会使得A服务器上压力过大?

2.平衡加权轮询算法(Nginx中默认采用的)
在这里插入图片描述
同时他的结果也很平滑 AABACAA

class Weight {
    private String ip;
    private Integer weight;
    private Integer currentWeight;
}
	/**
     * 平衡加权轮询算法实现
     */
    private static Map<String,Weight> weightMap = new LinkedHashMap<>();
    public static String getServer() {

        if (weightMap.isEmpty()) {
            for (String ip : ClentTest.WEIGHT_MAP.keySet()) {
                Integer weight = ClentTest.WEIGHT_MAP.get(ip);

                weightMap.put(ip,new Weight(ip,weight,0));
            }
        }

        //1.currentWeight += weight
        for (Weight weight : weightMap.values()) {
            weight.setCurrentWeight(weight.getCurrentWeight() + weight.getWeight());
        }

        //2.max(currentWeight)
        Weight maxCurrentWeight = null;
        for (Weight weight : weightMap.values()) {
            if (maxCurrentWeight == null || weight.getCurrentWeight() >
                    maxCurrentWeight.getCurrentWeight()) {
                maxCurrentWeight = weight;
            }
        }

        int totalWeight = ClentTest.WEIGHT_MAP.values().stream().mapToInt(weight -> weight).sum();
        //4.max(currentWeight) -= sum(weight);
        maxCurrentWeight.setCurrentWeight(maxCurrentWeight.getCurrentWeight() - totalWeight);

        //5.return
        return maxCurrentWeight.getIp();
3.3一致哈希算法
  • 问题:服务器集群中,用户访问分配给第一台服务器有用户的session,但是再次请求中分配给第二台没有的session,重新登陆的不同步问题?

  • 解决:redis ,tomact ,一致hash算法

一致哈希算法:要保证同一个用户接下来的请求的负载均衡通过客户端的ip,或请求路径与请求参数产生hash值指向同一个服务器.

因为客户端发起的请求情况是无穷尽的(客户但地址不同,请求参数不同),所以对于的hash值也是无穷大的,所以我们不可能把所有的hash值都进行映射到服务器端ip上,所以这里需要用到hash环.

在这里插入图片描述

每一个IP对应的hashCode是存在环上的 ,同时呈现出来顺时针的走势,如果hash值在ip1到ip2之间则如图指向的是ip2作为结果.

  • 问题:但是如果ip4挂掉了,那么请求将放到ip1上面,因为是顺时针.产生了不公平?

  • 解决:需要加入虚拟节点,如:(是将请求平均分配了下)

其中ip2-1,ip3-1就是虚拟节点,并不能处理节点,而是等同于对应的ip2和ip3服务器.

实际上,这只是处理这种不均衡性的一种思路,实际上就算哈希环本身是均衡的,你也可以增加更多的虚拟节点来使这个环更加平滑,比如:
在这里插入图片描述
这个彩虹环也是"公平的",并且只有ip 1,2,3,4使实际的服务器ip,其他的都是虚拟IP;

步骤:

1.> hashCode,虚拟节点

2.采用treemap实现有序

	private static TreeMap<Integer,String> treeMap = new TreeMap<>();
    private static final int VIRTUAL_NODES = 160; //虚拟节点

    static {
        for (String ip : ClentTest.LIST) {
            for (int i = 0; i < VIRTUAL_NODES; i++) {
                int hash = getHash(ip + i);
                treeMap.put(hash,ip);
            }
        }
    }

    public static String getServer(String client) {
        int hash = getHash(client);
        //找大于hash,treeMap的子树的第一个firstKey
        SortedMap<Integer, String> subMap = treeMap.tailMap(hash);
        Integer firstKey = null;
        if (subMap == null) {
            firstKey = treeMap.firstKey();
        }else {
            firstKey = subMap.firstKey();
        }

        return treeMap.get(firstKey);

    }

	//计算hash
    private static int getHash(String str) {
        final int p = 16777619;
        int hash = (int)2166136261L;
        for (int i = 0;i < str.length();i++) {
            hash = (hash ^ str.charAt(i) * p);
        }
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;
        //算出为负数取其绝对值
        if(hash < 0) hash = Math.abs(hash);
        return hash;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 12; i++) {
            System.out.println(getServer("client" + i));
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值