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

news2024/10/6 0:27:40

引言

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

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

什么是负载均衡

负载均衡,英文名称为 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);
        }

    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1469525.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VMware使用虚拟机,开启时报错:无法连接虚拟设备 0:0,因为主机上没有相应的设备。——解决方法

检查虚拟机配置文件并确保物理设备已正确连接。 操作&#xff1a; 选中虚拟机&#xff0c;打开设置&#xff0c;点击CD/DVD。在连接处选择使用ISO镜像文件

被动收入 | Audible 联盟营销计划:如何每月赚取 5000 美元?

你是否正在寻求被动收入的方式&#xff0c;或者在你的网站或平台上寻求赚钱的方式&#xff1f;亚马逊的Audible Depot联盟营销计划是一个不错的选择。作为会员&#xff0c;可以向听众推广有声读物&#xff0c;并从中获得收益。每月有可能赚取高达5000美元的收入&#xff0c;现在…

【Vuforia+Unity】AR04-地面、桌面平面识别功能(Ground Plane Target)

不论你是否曾有过相关经验,只要跟随本文的步骤,你就可以成功地创建你自己的AR应用。 官方教程Ground Plane in Unity | Vuforia Library 这个功能很棒,但是要求也很不友好,只能支持部分移动设备,具体清单如下: 01.Vuforia的地面识别功能仅支持的设备清单: Recommended…

【MySQL】MySQL从0到0.9 - 持续更新ing

MySQL SQL 基础 DDL 语句 show databases; show tables; desc table_name; # 获取表信息 show create table 表名; // 查询指定表的建表语句 数据类型 char(10) 不满10个会用空格填充&#xff0c;性能好一点 varchar(10) 变长字符串&#xff0c;性能差一点 CREATE TABLE tabl…

数据库-MySQL-01

这里写目录标题 数据库开发-MySQL首先来了解一下什么是数据库。1. MySQL概述1.1 安装1.1.1 版本1.1.2 安装1.1.3 连接1.1.4 企业使用方式(了解) 1.2 数据模型1.3 SQL简介1.3.1 SQL通用语法1.3.2 分类 2. 数据库设计-DDL2.1 项目开发流程2.2 数据库操作2.2.1 查询数据库2.2.2 创…

从SDRAM到DDR的变化

1、结构概述 在此之前&#xff0c;曾经通过一篇文章从SDRAM的内部芯片框图出发&#xff0c;分析过SDRAM的功能实现&#xff0c;本文开始继续分析DDR、DDR2、DDR3的芯片内部框图&#xff0c;从而认识他们各自的区别&#xff0c;便于后续使用。 下图时镁光的128Mb的SDRAM内存芯片…

大学生多媒体课程学习网站thinkphp+vue

开发语言&#xff1a;php 后端框架&#xff1a;Thinkphp 前端框架&#xff1a;vue.js 服务器&#xff1a;apache 数据库&#xff1a;mysql 运行环境:phpstudy/wamp/xammp等开发背景 &#xff08;一&#xff09; 研究课程的提出 &#xff08;二&#xff09;学习网站的分类与界定…

RabbitMq:RabbitMq 主从镜像模式②

一、模式思想 所有的技术设计思想&#xff0c;基本都在两点上下功夫&#xff1a;1. 生产力上 2. 稳定上 二、集群模式 今天又有人问起来rabbitmq的高可用方式&#xff0c;因为和常见的主从模式有点区别&#xff0c;所以就记录一下。 rabbitmq集群的镜像队列提供了更高级的主从…

数字化转型导师坚鹏:数据安全法解读与政府数字化转型

网络安全法、数据安全法、个人信息保护法解读与政府数字化转型 课程背景&#xff1a; 很多机构存在以下问题&#xff1a; 不清楚网络安全法、数据安全法、个人信息保护法立法背景&#xff1f; 不知道如何理解网络安全法、数据安全法、个人信息保护法政策&#xff1f; 不…

LeetCode 第一题: 两数之和

文章目录 第一题: 两数之和题目描述示例 解题思路Go语言实现 - 一遍哈希表法C实现算法分析 排序和双指针法Go语言实现 - 排序和双指针法C算法分析 暴力法Go语言实现 - 暴力法C算法分析 二分搜索法Go语言实现 - 二分搜索法C算法分析 第一题: 两数之和 ‍ 题目描述 给定一个整…

【统计分析数学模型】聚类分析: 系统聚类法

【统计分析数学模型】聚类分析&#xff1a; 系统聚类法 一、聚类分析1. 基本原理2. 距离的度量&#xff08;1&#xff09;变量的测量尺度&#xff08;2&#xff09;距离&#xff08;3&#xff09;R语言计算距离 三、聚类方法1. 系统聚类法2. K均值法 三、示例1. Q型聚类&#x…

变量与数据类型(详解版)

新年的第一篇博客&#xff0c;我也开始步入了对于java的学习&#xff0c;感觉c语言还是有很多的不懂&#xff0c;还是会继续学习c语言的&#xff0c;毕竟还是练习太少了&#xff01; 话不多说&#xff0c;我们直接开整&#xff01; 1. 字面常量 如上图中的输出语句&#xff0…

(202402)多智能体MetaGPT入门1:MetaGPT环境配置

文章目录 前言拉取MetaGPT仓库1 仅仅安装最新版2 拉取源码本地安装MetaGPT安装成果全流程展示 尝试简单使用1 本地部署大模型尝试&#xff08;失败&#xff09;2 讯飞星火API调用 前言 感谢datawhale组织开源的多智能体学习内容&#xff0c;飞书文档地址在https://deepwisdom.…

Java内部类的使用与应用

内部类的使用与应用 1. 内部类用法 普通内部类&#xff1a; 实例化内部类对象需要先实例化外部类对象&#xff0c;然后再通过OuterClassName.new InnerClassName()方式实例化内部类。内部类对象在创建后会与外部类对象秘密链接&#xff0c;因此无法独立于外部类创建内部类对象…

智慧农业—农业资源数据中心

综述 农业资源数据中心建设的目标是将大量的农业生产信息通过采集、清洗、核准后实现统一存储、统一管理,实现数据的共享和集中管理,保障数据的安全,也为数据的挖掘分析提供决策分析创造条件。 农业资源数据中心的数据架构如下图所示: (1)农业专家数据库。主要收录国内、…

手撕Transformer(二)| Transformer掩码机制的两个功能,三个位置的解析及其代码

文章目录 1 掩码的两个功能1.1 功能1 输入掩码&#xff0c;统一长度1.2 功能2 遮挡未预测信息 2 掩码存在的三个位置3 代码实现 Transformer的掩码机制是非常重要的 三个位置&#xff0c;两个功能 1 掩码的两个功能 1.1 功能1 输入掩码&#xff0c;统一长度 实现不同的长度句…

使用C++和SFML库创建2D游戏

FML&#xff08;Simple and Fast Multimedia Library&#xff09;是一个跨平台的C库&#xff0c;用于开发2D游戏和多媒体应用程序。它提供了许多功能&#xff0c;包括图形、声音、网络、窗口管理和事件处理等。 ———————————————不怎么完美的分割线——————…

使用命令符用cd切换不了

bug:cd 切换不进去 解决办法&#xff1a; 在cd后面加 /d

(HAL)STM32F103C6T8——软件模拟I2C驱动0.96寸OLED屏幕

一、电路接法 电路接法参照江科大视频。 二、相关代码及文件 说明&#xff1a;代码采用hal库&#xff0c;通过修改江科大代码实现。仅OLED.c文件关于引脚定义作了hal库修改&#xff0c;并将宏定义OLED_W_SCL(x)、OLED_W_SDA(x)作了相关修改。 1、OLED.c void OLED_I2C_Init(voi…

32单片机基础:旋转编码器计次

接线图如上图所示。 我们初始化一下PB0和PB1两个GPIO口外设中断&#xff0c;当然&#xff0c;这里只初始化一个外部中断也能完成功能的对于编码器而言&#xff0c;下图所示为正转的波形。如果把一相的下降沿用作触发中断&#xff0c;在中断时刻读取另一相的电平&#xff0c;正…