解密高并发系统设计:聊聊负载均衡算法

引言

随着公司业务的飞速发展,以及业务的多样性,用户数会迅猛增长,系统的流量会越来越大。因此,大规模的并发用户访问会对系统的处理能力造成巨大的压力,系统必须要有足够强的处理能力才能应对。

这篇文章就来介绍一下高并发系统的通用设计原则之一:负载均衡。

什么是负载均衡

负载均衡,英文名称为 Load Balance,它的核心思想就是在用户和服务器中间加一层负载均衡服务,该层服务通过相应的负载均衡算法,将用户请求分发给应用服务器集群。

以前的单体应用时代:

随着用户规模不断扩大,单机对外提供服务越发显得力不从心:

集群时代:

常见的负载均衡服务器有:LVS、Nginx、Haproxy

负载均衡服务器会根据 应用服务器的健康状态来判断当前节点是否可以被转发,依次来保证整个应用系统的可用性。

常见的负载均衡算法包括:

  • 随机算法
  • 轮询算法
  • 加权随机算法
  • 加权轮询算法
  • IP-Hash 算法
  • 最小活跃连接算法

几种常见的负载均衡算法

定义两个个公用类 IpInfo、ServerRegister ,用来表示 IP 节点信息以及 IP 信息的存储。

package com.markus.service.load.balanced;

/**
 * @author: markus
 * @date: 2024/2/25 1:49 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class IpInfo {

    private String ipAddr;
    private Integer weight;

    private Integer activeLink;

    public IpInfo(String ipAddr, Integer weight) {
        this.ipAddr = ipAddr;
        this.weight = weight;
        this.activeLink = 0;
    }

    public String getIpAddr() {
        return ipAddr;
    }

    public void setIpAddr(String ipAddr) {
        this.ipAddr = ipAddr;
    }

    public Integer getWeight() {
        return weight;
    }

    public void setWeight(Integer weight) {
        this.weight = weight;
    }

    public Integer getActiveLink() {
        return activeLink;
    }

    public void setActiveLink(Integer activeLink) {
        this.activeLink = activeLink;
    }

    @Override
    public String toString() {
        return "IpInfo{" +
                "ipAddr='" + ipAddr + '\'' +
                ", weight=" + weight +
                ", activeLink=" + activeLink +
                '}';
    }
}
package com.markus.service.load.balanced;

import java.util.HashMap;
import java.util.Map;

/**
 * @author: markus
 * @date: 2024/2/25 2:05 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class ServerRegister {
    public static final Map<String, IpInfo> ipInfoMap = new HashMap<>();

    static {
        ipInfoMap.put("192.168.163.1", new IpInfo("192.168.163.1", 1));
        ipInfoMap.put("192.168.163.2", new IpInfo("192.168.163.2", 2));
        ipInfoMap.put("192.168.163.3", new IpInfo("192.168.163.3", 3));
        ipInfoMap.put("192.168.163.4", new IpInfo("192.168.163.4", 4));
    }
}

随机算法

随机算法就是在可用的应用服务器节点中随机选择一个节点来访问。例如当前有 4 个 IP 节点,那么通过随机算法每次随机生成一个 [0,size) 范围内的随机数,然后就可以得到要访问的节点,代码如下所示:

package com.markus.service.load.balanced;

import java.util.*;

/**
 * @author: markus
 * @date: 2024/2/25 1:35 PM
 * @Description: 随机算法
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
   */
   public class RandomAlgorithm {

   private static Map<String, IpInfo> ipInfoMap = new HashMap<>();

   static {
       ipInfoMap.put("192.168.163.1", new IpInfo(1));
       ipInfoMap.put("192.168.163.2", new IpInfo(2));
       ipInfoMap.put("192.168.163.3", new IpInfo(3));
       ipInfoMap.put("192.168.163.4", new IpInfo(4));
   }

   private static List<String> getIpList() {
       List<String> result = new ArrayList<>(ipInfoMap.size());
       result.addAll(ipInfoMap.keySet());
       return result;
   }

   public static String getIpByRandomAlgorithm() {
       List<String> ipList = getIpList();

       Random random = new Random();
       // 在 [0,size) 中选择一个随机数
       int index = random.nextInt(ipList.size());
       return ipList.get(index);

   }

   public static void main(String[] args) {
       for (int i = 0; i < 20; i++) {
           String ip = getIpByRandomAlgorithm();
           System.out.println("选择的 IP 为 : " + ip);
       }
   }
}

轮询算法

轮询算法就是,在可用的算法节点中,按照固定的顺序依次访问节点。例如当前有 4 个 IP 节点,那么通过预先指定的 round 起始索引位开始访问节点,拿到节点数据。round 每次 + 1 并对 ipList.size 取模(保证 round 始终在 [0,ipList.size) 范围内)。代码实现如下:

package com.markus.service.load.balanced;

import java.util.ArrayList;
import java.util.List;

import static com.markus.service.load.balanced.ServerRegister.ipInfoMap;

/**
 * @author: markus
 * @date: 2024/2/25 2:04 PM
 * @Description: 轮询算法
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class RoundAlgorithm {

    private static List<String> getIpList() {
        List<String> result = new ArrayList<>(ipInfoMap.size());
        result.addAll(ipInfoMap.keySet());
        return result;
    }

    // 起始位置
    private static Integer round = 0;

    public static String getIpByRoundAlgorithm() {
        List<String> ipList = getIpList();
        String ip = ipList.get(round);
        round = (round + 1) % ipList.size();
        return ip;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            String ip = getIpByRoundAlgorithm();
            System.out.println("选择的 IP 为 : " + ip);
        }
    }
}

加权随机算法

加权随机算法就是,在随机算法的基础上给每个节点添加一个权重,从而使每个节点被访问到的概率不相同,即权重大的节点被访问到的概率会更高,权重小的节点被访问到的概率会越小。示例代码如下:

package com.markus.service.load.balanced;

import java.util.*;

import static com.markus.service.load.balanced.ServerRegister.ipInfoMap;

/**
 * @author: markus
 * @date: 2024/2/25 1:35 PM
 * @Description: 加权随机算法
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class WeightRandomAlgorithm {

    private static List<String> getIpList() {
        List<String> result = new ArrayList<>(ipInfoMap.size());
        for (Map.Entry<String, IpInfo> entry : ipInfoMap.entrySet()) {
            String ip = entry.getKey();
            IpInfo ipInfo = entry.getValue();
            // 根据权重 像可用列表里增加相应 IP 出现的次数
            for (int i = 0; i < ipInfo.getWeight(); i++) {
                result.add(ip);
            }
        }

        return result;
    }

    public static String getIpByRandomAlgorithm() {
        List<String> ipList = getIpList();

        Random random = new Random();
        // 在 [0,size) 中选择一个随机数
        int index = random.nextInt(ipList.size());
        return ipList.get(index);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            String ip = getIpByRandomAlgorithm();
            System.out.println("选择的 IP 为 : " + ip);
        }
    }
}

加权轮询算法

加权轮询算法就是,在轮询算法的基础上,给每个节点增加权重,从而使得每个节点被访问到的概率不相同。即权重大的节点被访问到的概率就越高,权重小的节点被访问到的概率就越低。示例代码如下:

package com.markus.service.load.balanced;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.markus.service.load.balanced.ServerRegister.ipInfoMap;

/**
 * @author: markus
 * @date: 2024/2/25 2:04 PM
 * @Description: 加权轮询算法
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class WeightRoundAlgorithm {

    private static List<String> getIpList() {
        List<String> result = new ArrayList<>(ipInfoMap.size());
        for (Map.Entry<String, IpInfo> entry : ipInfoMap.entrySet()) {
            String ip = entry.getKey();
            IpInfo ipInfo = entry.getValue();
            // 根据权重 像可用列表里增加相应 IP 出现的次数
            for (int i = 0; i < ipInfo.getWeight(); i++) {
                result.add(ip);
            }
        }

        return result;
    }

    // 起始位置
    private static Integer round = 0;

    public static String getIpByRoundAlgorithm() {
        List<String> ipList = getIpList();
        String ip = ipList.get(round);
        round = (round + 1) % ipList.size();
        return ip;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            String ip = getIpByRoundAlgorithm();
            System.out.println("选择的 IP 为 : " + ip);
        }
    }
}

IP-HASH 算法

IP-HASH 算法也称一致性 Hash 算法,是通过某个 Hash 函数把同一来源的请求都映射到同一个节点上。其核心思想就是:同一个来源的请求只会分配到同一个服务节点上(具有记忆功能),只有当应用服务节点不可用时,才会将其分配到相邻的其他节点上去。示例代码如下所示:

package com.markus.service.load.balanced;

import java.util.ArrayList;
import java.util.List;

import static com.markus.service.load.balanced.ServerRegister.ipInfoMap;

/**
 * @author: markus
 * @date: 2024/2/25 2:41 PM
 * @Description: IP-HASH 一致性 HASH 算法
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class IpHashAlgorithm {

    private static List<String> getIpList() {
        List<String> result = new ArrayList<>(ipInfoMap.size());
        result.addAll(ipInfoMap.keySet());
        return result;
    }

    // 这里 请求入参 仅是简单的设置 String remoteIP
    public static String getIpByIpHashAlgorithm(String remoteIp) {
        List<String> ipList = getIpList();

        int hashCode = remoteIp.hashCode();
        int index = hashCode % ipList.size();
        return ipList.get(index);
    }

    public static void main(String[] args) {
        String remoteIp = "127.0.0.1";
        for (int i = 0; i < 20; i++) {
            String ip = getIpByIpHashAlgorithm(remoteIp);
            System.out.println("remoteIp " + remoteIp + " 选择的 IP 为 : " + ip);
        }

        remoteIp = "192.168.163.11";
        for (int i = 0; i < 20; i++) {
            String ip = getIpByIpHashAlgorithm(remoteIp);
            System.out.println("remoteIp " + remoteIp + " 选择的 IP 为 : " + ip);
        }
    }
}

最小活跃连接算法

最少活跃连接算法就是,每次都选择连接数最少的服务节点来转发请求。由于后台服务器配置不尽相同,对请求的处理能力也各有不同,因此我们可以动态的选取集群节点中连接数最少的一个节点来处理当前请求。示例代码如下所示:

package com.markus.service.load.balanced;

import java.util.ArrayList;
import java.util.List;

import static com.markus.service.load.balanced.ServerRegister.ipInfoMap;

/**
 * @author: markus
 * @date: 2024/2/25 2:56 PM
 * @Description: 最小活跃连接算法
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class MinimumActiveLinkAlgorithm {

    private static List<String> getIpList() {
        List<String> result = new ArrayList<>(ipInfoMap.size());
        result.addAll(ipInfoMap.keySet());
        return result;
    }

    public static String getIpByMinimumActiveLinkAlgorithm() {
        List<String> ipList = getIpList();

        IpInfo minimumActiveLinkIp = null;
        int minimumActiveLinkCount = Integer.MAX_VALUE;

        for (String ip : ipList) {
            IpInfo ipInfo = ipInfoMap.get(ip);
            int activeLinkCount = ipInfo.getActiveLink();
            if (activeLinkCount < minimumActiveLinkCount) {
                minimumActiveLinkCount = activeLinkCount;
                minimumActiveLinkIp = ipInfo;
            }
        }

        System.out.println("当前服务集群节点状态 :" + ipInfoMap);
        if (minimumActiveLinkIp != null) {
            // 本次 连接数加 +1
            Integer activeLink = minimumActiveLinkIp.getActiveLink();
            minimumActiveLinkIp.setActiveLink(activeLink + 1);
        }
        return minimumActiveLinkIp != null ? minimumActiveLinkIp.getIpAddr() : null;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            String ip = getIpByMinimumActiveLinkAlgorithm();
            System.out.println("选择的 IP 为 :" + ip);
        }

    }
}
  • 13
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值