一篇有趣的负载均衡算法实现

2024-06-07 18:58

本文主要是介绍一篇有趣的负载均衡算法实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

负载平衡(Load balancing)是一种在多个计算机(网络、CPU、磁盘)之间均匀分配资源,以提高资源利用的技术。使用负载均衡可以最大化服务吞吐量,可能最小化响应时间,同时由于使用负载均衡时,会使用多个服务器节点代单点服务,也提高了服务的可用性。

负载均衡的实现可以软件可以硬件,硬件如大名鼎鼎的 F5 负载均衡设备,软件如 NGINX 中的负载均衡实现,又如 Springcloud Ribbon 组件中的负载均衡实现。

如果看到这里你还不知道负载均衡是干嘛的,那么只能放一张图了,毕竟没图说个啥。

负载均衡要做到在多次请求下,每台服务器被请求的次数大致相同。但是实际生产中,可能每台机器的性能不同,我们会希望性能好的机器承担的请求更多一些,这也是正常需求。

如果这样说下来你看不懂,那我就再举个例子好了,一排可爱的小熊(服务器)站好。

这时有人(用户)要过来打脸(请求访问)。

那么怎么样我们才能让这每一个可爱的小熊被打的次数大致相同呢?

又或者熊 4 比较胖,抗击打能力是别人的两倍,我们怎么提高熊 4 被打的次数也是别人的两倍呢?

又或者每次出手的力度不同,有重有轻,恰巧熊 4 总是承受这种大力度啪啪打脸,熊 4 即将不省熊事,还要继续打它吗?

这些都是值的思考的问题。

说了那么多,口干舌燥,我双手已经饥渴难耐了,迫不及待的想要撸起代码了。

1. 随机访问

上面说了,为了负载均衡,我们必须保证多次出手后,熊 1 到熊 4 被打次数均衡。比如使用随机访问法,根据数学上的概率论,随机出手次数越多,每只熊被打的次数就会越相近。代码实现也比较简单,使用一个随机数,随机访问一个就可以了。

/** 服务器列表 */
private static List<String> serverList = new ArrayList<>();
static {serverList.add("192.168.1.2");serverList.add("192.168.1.3");serverList.add("192.168.1.4");serverList.add("192.168.1.5");
}/*** 随机路由算法*/
public static String random() {// 复制遍历用的集合,防止操作中集合有变更List<String> tempList = new ArrayList<>(serverList.size());tempList.addAll(serverList);// 随机数随机访问int randomInt = new Random().nextInt(tempList.size());return tempList.get(randomInt);
}

因为使用了非线程安全的集合,所以在访问操作时操作的是集合的拷贝,下面几种轮训方式中也是这种思想。

写一个模拟请求方法,请求10w次,记录请求结果。

public static void main(String[] args) {HashMap<String, Integer> serverMap = new HashMap<>();for (int i = 0; i < 20000; i++) {String server = random();Integer count = serverMap.get(server);if (count == null) {count = 1;} else {count++;}// 记录serverMap.put(server, count);}// 路由总体结果for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {System.out.println("IP:" + entry.getKey() + ",次数:" + entry.getValue());}
}

运行得到请求结果。

IP:192.168.1.3,次数:24979
IP:192.168.1.2,次数:24896
IP:192.168.1.5,次数:25043
IP:192.168.1.4,次数:25082

2. 轮训访问

轮训访问就简单多了,拿上面的熊1到熊4来说,我们一个接一个的啪啪 - 打脸,熊1打完打熊2,熊2打完打熊3,熊4打完打熊1,最终也是实现了被打均衡。但是保证均匀总是要付出代价的,随机访问中需要随机,轮训访问中需要什么来保证轮训呢?

/** 服务器列表 */
private static List<String> serverList = new ArrayList<>();
static {serverList.add("192.168.1.2");serverList.add("192.168.1.3");serverList.add("192.168.1.4");serverList.add("192.168.1.5");
}
private static Integer index = 0;/*** 随机路由算法*/
public static String randomOneByOne() {// 复制遍历用的集合,防止操作中集合有变更List<String> tempList = new ArrayList<>(serverList.size());tempList.addAll(serverList);String server = "";synchronized (index) {index++;if (index == tempList.size()) {index = 0;}server = tempList.get(index);;}return server;
}

由代码里可以看出来,为了保证轮训,必须记录上次访问的位置,为了让在并发情况下不出现问题,还必须在使用位置记录时进行加锁,很明显这种互斥锁增加了性能开销。

依旧使用上面的测试代码测试10w次请求负载情况。

IP:192.168.1.3,次数:25000
IP:192.168.1.2,次数:25000
IP:192.168.1.5,次数:25000
IP:192.168.1.4,次数:25000

3. 轮训加权

上面演示了轮训方式,还记的一开始提出的熊4比较胖抗击打能力强,可以承受别人2倍的挨打次数嘛?上面两种方式都没有体现出来熊 4 的这个特点,熊 4 窃喜,不痛不痒。但是熊 1 到 熊 3 已经在崩溃的边缘,不行,我们必须要让胖着多打,能者多劳,提高整体性能。

/** 服务器列表 */
private static HashMap<String, Integer> serverMap = new HashMap<>();
static {serverMap.put("192.168.1.2", 2);serverMap.put("192.168.1.3", 2);serverMap.put("192.168.1.4", 2);serverMap.put("192.168.1.5", 4);
}
private static Integer index = 0;/*** 加权路由算法*/
public static String oneByOneWithWeight() {List<String> tempList = new ArrayList();HashMap<String, Integer> tempMap = new HashMap<>();tempMap.putAll(serverMap);for (String key : serverMap.keySet()) {for (int i = 0; i < serverMap.get(key); i++) {tempList.add(key);}}synchronized (index) {index++;if (index == tempList.size()) {index = 0;}return tempList.get(index);}
}

这次记录下了每台服务器的整体性能,给出一个数值,数值越大,性能越好。可以承受的请求也就越多,可以看到服务器 192.168.1.5 的性能为 4,是其他服务器的两倍,依旧 10 w 请求测试。

IP:192.168.1.3,次数:20000
IP:192.168.1.2,次数:20000
IP:192.168.1.5,次数:40000
IP:192.168.1.4,次数:20000

192.168.1.5 承担了 2 倍的请求。

4. 随机加权

随机加权的方式和轮训加权的方式大致相同,只是把使用互斥锁轮训的方式换成了随机访问,按照概率论来说,访问量增多时,服务访问也会达到负载均衡。

/** 服务器列表 */
private static HashMap<String, Integer> serverMap = new HashMap<>();
static {serverMap.put("192.168.1.2", 2);serverMap.put("192.168.1.3", 2);serverMap.put("192.168.1.4", 2);serverMap.put("192.168.1.5", 4);
}
/*** 加权路由算法*/
public static String randomWithWeight() {List<String> tempList = new ArrayList();HashMap<String, Integer> tempMap = new HashMap<>();tempMap.putAll(serverMap);for (String key : serverMap.keySet()) {for (int i = 0; i < serverMap.get(key); i++) {tempList.add(key);}}int randomInt = new Random().nextInt(tempList.size());return tempList.get(randomInt);
}

依旧 10 w 请求测试,192.168.1.5 的权重是其他服务器的近似两倍,

IP:192.168.1.3,次数:19934
IP:192.168.1.2,次数:20033
IP:192.168.1.5,次数:39900
IP:192.168.1.4,次数:20133

5. IP-Hash

上面的几种方式要么使用随机数,要么使用轮训,最终都达到了请求的负载均衡。但是也有一个很明显的缺点,就是同一个用户的多次请求很有可能不是同一个服务进行处理的,这时问题来了,如果你的服务依赖于 session ,那么因为服务不同, session 也会丢失,不是我们想要的,所以出现了一种根据请求端的 ip 进行哈希计算来决定请求到哪一台服务器的方式。这种方式可以保证同一个用户的请求落在同一个服务上。

private static List<String> serverList = new ArrayList<>();
static {serverList.add("192.168.1.2");serverList.add("192.168.1.3");serverList.add("192.168.1.4");serverList.add("192.168.1.5");
}/*** ip hash 路由算法*/
public static String ipHash(String ip) {// 复制遍历用的集合,防止操作中集合有变更List<String> tempList = new ArrayList<>(serverList.size());tempList.addAll(serverList);// 哈希计算请求的服务器int index = ip.hashCode() % serverList.size();return tempList.get(Math.abs(index));
}

6. 总结

上面的四种方式看似不错,那么这样操作下来真的体现了一开始说的负载均衡吗?答案是不一定的。就像上面的最后一个提问。

又或者每次出手的力度不同,有重有轻,恰巧熊 4 总是承受这种大力度啪啪打脸,熊 4 即将不省熊事,还要继续打它吗?

服务器也是这个道理,每次请求进行的操作对资源的消耗可能是不同的。比如说某些操作它对 CPU 的使用就是比较高,也很正常。所以负载均衡有时不能简单的通过请求的负载来作为负载均衡的唯一依据。还可以结合服务的当前连接数量、最近响应时间等维度进行总体均衡,总而言之,就是为了达到资源使用的负载均衡。

这篇关于一篇有趣的负载均衡算法实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1040022

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

通过SSH隧道实现通过远程服务器上外网

搭建隧道 autossh -M 0 -f -D 1080 -C -N user1@remotehost##验证隧道是否生效,查看1080端口是否启动netstat -tuln | grep 1080## 测试ssh 隧道是否生效curl -x socks5h://127.0.0.1:1080 -I http://www.github.com 将autossh 设置为服务,隧道开机启动

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测 目录 时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测基本介绍程序设计参考资料 基本介绍 MATLAB实现LSTM时间序列未来多步预测-递归预测。LSTM是一种含有LSTM区块(blocks)或其他的一种类神经网络,文献或其他资料中LSTM区块可能被描述成智能网络单元,因为

vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器 官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html 源码地址:https://github.com/Hufe921/canvas-editor 前提声明: 由于CanvasEditor目前不支持vue、react 等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主

代码随想录算法训练营:12/60

非科班学习算法day12 | LeetCode150:逆波兰表达式 ,Leetcode239: 滑动窗口最大值  目录 介绍 一、基础概念补充: 1.c++字符串转为数字 1. std::stoi, std::stol, std::stoll, std::stoul, std::stoull(最常用) 2. std::stringstream 3. std::atoi, std

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

基于Springboot + vue 的抗疫物质管理系统的设计与实现

目录 📚 前言 📑摘要 📑系统流程 📚 系统架构设计 📚 数据库设计 📚 系统功能的具体实现    💬 系统登录注册 系统登录 登录界面   用户添加  💬 抗疫列表展示模块     区域信息管理 添加物资详情 抗疫物资列表展示 抗疫物资申请 抗疫物资审核 ✒️ 源码实现 💖 源码获取 😁 联系方式 📚 前言 📑博客主页:

人工智能机器学习算法总结神经网络算法(前向及反向传播)

1.定义,意义和优缺点 定义: 神经网络算法是一种模仿人类大脑神经元之间连接方式的机器学习算法。通过多层神经元的组合和激活函数的非线性转换,神经网络能够学习数据的特征和模式,实现对复杂数据的建模和预测。(我们可以借助人类的神经元模型来更好的帮助我们理解该算法的本质,不过这里需要说明的是,虽然名字是神经网络,并且结构等等也是借鉴了神经网络,但其原型以及算法本质上还和生物层面的神经网络运行原理存在

探索蓝牙协议的奥秘:用ESP32实现高质量蓝牙音频传输

蓝牙(Bluetooth)是一种短距离无线通信技术,广泛应用于各种电子设备之间的数据传输。自1994年由爱立信公司首次提出以来,蓝牙技术已经经历了多个版本的更新和改进。本文将详细介绍蓝牙协议,并通过一个具体的项目——使用ESP32实现蓝牙音频传输,来展示蓝牙协议的实际应用及其优点。 蓝牙协议概述 蓝牙协议栈 蓝牙协议栈是蓝牙技术的核心,定义了蓝牙设备之间如何进行通信。蓝牙协议

python实现最简单循环神经网络(RNNs)

Recurrent Neural Networks(RNNs) 的模型: 上图中红色部分是输入向量。文本、单词、数据都是输入,在网络里都以向量的形式进行表示。 绿色部分是隐藏向量。是加工处理过程。 蓝色部分是输出向量。 python代码表示如下: rnn = RNN()y = rnn.step(x) # x为输入向量,y为输出向量 RNNs神经网络由神经元组成, python