Dubbo-负载均衡原理解析(1),一个本科渣渣是怎么逆袭从咸鱼到Offer收割机的

简介: Dubbo-负载均衡原理解析(1),一个本科渣渣是怎么逆袭从咸鱼到Offer收割机的
System.out.println(getServer());
}
}
}

执行结果如下

192.168.0.6

192.168.0.10

192.168.0.10

192.168.0.6

192.168.0.4

192.168.0.2

192.168.0.6

192.168.0.2

192.168.0.4

192.168.0.6

这种实现方法在遇到权重之和特别大的时候就会比较消耗内存,因为需要对ip地址进行复制,权重之和越大,那么上文中的ips就需要越多的内存。

方式二:区间范围实现

假设我们有一组服务器servers=[A,B,C],他们对应的权重为weights=[5,3,2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围 [0, 10) 之间的随机数,然后计算在这个随机数会落在哪个区间上。比如数字3会落在服务器A对应的区间上,此时返回服务器A即可。权重越大的机器,在坐标轴上对应的区间范围就越大,因此随机数生成器生成的数字就会有更大的概率落在此区间内。只要随机数生成器产生的随机数分布性很好,在经过多次选择后,每个服务器被选中的次数比例接近其权重比例。

比如,经过一万次选择后,服务器A被选中的次数大约为5000次,服务器B被选中的次数约为3000次,服务器C被选中的次数约为2000次。

假设现在随机数offset=7;

  1. offset<5 is false,所以不在[0, 5)区间,将offset = offset - 5(offset=2)
  2. offset<3 is true,所以处于[5, 8)区间,所以应该选用B服务器
public class WeightRandomV2 {
public static String getServer() {
int totalWeight = 0;
boolean sameWeight = true; //如果所有权重都相等,随机一个ip即可
Object[] weights = ServerIps.WEIGHT_LIST.values().toArray();
for (int i = 0; i < weights.length; i++) {
Integer weight = (Integer) weights[i];
totalWeight += weight;
//判断权重是否都相等
if (sameWeight && i > 0 && !weight.equals(weights[i - 1])) {
sameWeight = false;
}
}
Random random = new Random();
int randomPos = random.nextInt(totalWeight);
//权重值不相等
if (!sameWeight) {
for (String ip : ServerIps.WEIGHT_LIST.keySet()) {
Integer value = ServerIps.WEIGHT_LIST.get(ip);
if (randomPos < value) {
return ip;
}
randomPos = randomPos - value;
}
}
return (String) ServerIps.WEIGHT_LIST.keySet().toArray()[new Random().nextInt(ServerIps.WEIGHT_LIST.size())];
}
public static void main(String[] args) {
//连续调用10次
for (int i = 0; i < 10; i++) {
System.out.println(getServer());
}
}
}

轮询算法-RoundRobinLoadBalance


特点

  • 加权轮询,按公约后的权重设置轮询比率,循环调用节点
  • 缺点:同样存在慢的提供者累积请求的问题。

代码实现(简单的轮询算法)

public class RoundRobin {
private static Integer pos = 0;
public static String getServer() {
String ip = “”;
//pos同步
synchronized (pos) {
if (pos >= ServerIps.LIST.size()) {
pos = 0;
}
ip = ServerIps.LIST.get(pos);
pos++;
}
return ip;
}
public static void main(String[] args) {
//连续调用10次
for (int i = 0; i < 11; i++) {
System.out.println(getServer());
}
}
}

执行结果如下:

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

192.168.0.1

这种算法很简单,也很公平,每台服务轮流来进行服务,但是有的机器性能好,所以能者多劳,和随机算法一样,加上权重维度以后,其中一种实现方法就是复制法,复制法的缺点是,比较消耗内存。

介绍另外一种算法:这种算法引入一个概念:调用编号,比如第1次调用为1,第2次调用为2,第100次调用为100,调用编号是递增的,所以我们可以根据这个调用编号推算出服务器。

假设我们有三台服务器servers = [A, B, C],对应的权重为 weights = [ 2, 5, 1], 总权重为8,我们可以理解为有8台“服务器”,这是8台“不具有并发功能”,其中有2台为A,5台为B,1台为C,一次调用过来的时候,需要按顺序访问,比如有10次调用,那么服务器调用顺序为AABBBBBCAA,调用编号会越来越大,而服务器是固定的,所以需要把调用编号“缩小”,这里对调用编号进行取余,除数为总权重和

比如:

  1. 1号调用,1%8=1;
  2. 2号调用,2%8=2;
  3. 3号调用,3%8=3;
  4. 8号调用,8%8=0;
  5. 9号调用,9%8=1;
  6. 100号调用,100%8=4;

我们发现调用编号可以被缩小为0-7之间的8个数字,问题是怎么根据这个8个数字找到对应的服务器呢?和我们随机算法类似,这里也可以把权重想象为一个坐标轴“0-----2-----7-----8”

  1. 1号调用,1%8=1,offset = 1, offset <= 2 is true,取A;
  2. 2号调用,2%8=2;offset = 2,offset <= 2 is true, 取A;
  3. 3号调用,3%8=3;offset = 3, offset <= 2 is false, offset = offset - 2, offset = 1, offset <= 5,取B
  4. 8号调用,8%8=0;offset = 0, 特殊情况,offset = 8,offset <= 2 is false, offset = offset - 2, offset = 6, offset <= 5 is false, offset = offset - 5, offset = 1, offset <= 1 is true, 取C;
  5. 9号调用,9%8=1;// …
  6. 100号调用,100%8=4; //…

模拟调用编号获取工具:

public class Sequence {
public static Integer num=0;
public static Integer getAndIncrement(){
return ++num;
}
}
public class WeightRoundRobin {
private static Integer pos = 0;
public static String getServer() {
int totalWeight = 0;
boolean sameWeight = true;
Object[] weights = ServerIps.WEIGHT_LIST.values().toArray();
for (int i = 0; i < weights.length; i++) {
Integer weight = (Integer) weights[i];
totalWeight += weight;
if (sameWeight && i > 0 && !weight.equals(weights[i - 1])) {
sameWeight = false;
}
}
Integer sequenceNum = Sequence.getAndIncrement();
Integer offset = sequenceNum % totalWeight;
offset = offset == 0 ? totalWeight : offset;
if (!sameWeight) {
for (String ip : ServerIps.WEIGHT_LIST.keySet()) {
Integer weight = ServerIps.WEIGHT_LIST.get(ip);
if (offset <= weight) {
return ip;
}
offset = offset - weight;
}
}
String ip = “”;
synchronized (pos) {
if (pos >= ServerIps.LIST.size()) {
pos = 0;
}
ip = ServerIps.LIST.get(pos);
pos++;
}
return ip;
}
public static void main(String[] args) {
// 连续调用11次
for (int i = 0; i < 11; i++) {
System.out.println(getServer());
}
}
}

执行结果如下

192.168.0.2

192.168.0.2

192.168.0.2

192.168.0.2

192.168.0.2

192.168.0.2

192.168.0.2

192.168.0.2

192.168.0.1

192.168.0.4

192.168.0.4

但是这种算法有一个缺点:一台服务器的权重特别大的时候,他需要连续的的处理请求,但是实际上我们想达到的效果是,对于100次请求,只要有100*8/50=16次就够了,这16次不一定要连续的访问,比如假设我们有三台服务器 servers = [A, B, C],对应的权重为 weights = [5, 1, 1] , 总权重为7,那么上述这个算法的结果是:AAAAABC,那么如果能够是这么一个结果呢:AABACAA,把B和C平均插入到5个A中间,这样是比较均衡的了。

我们这里可以改成平滑加权轮询。

平滑加权轮询


特点

思路:每个服务器对应两个权重,分别为weight 和currentWeight.其中weight是固定的,currentWeight会动态调整,初始值为0.当有新的请求进来时,遍历服务器列表,让它的currentWeight加上自身权重。遍历完成后,找到最大的 currentWeight,并将其减去权重总和,然后返回相应的服务器即可。

假设我们有三台服务器 servers = [A, B, C],对应的权重为 weights = [5, 1, 1] , 总权重为7

| 请求编号 | currentWeight 数组 (current_weight += weight) | 选择结果(max(currentWeight)) | 减去权重总和后的currentWeight 数组(max(currentWeight) -= sum(weight)) |
| — | — | — | — |
| 1 | [5, 1, 1] | A | [-2, 1, 1] |
| 2 | [3, 2, 2] | A | [-4, 2, 2] |
| 3 | [1, 3, 3] | B | [1, -4, 3] |
| 4 | [6, -3, 4] | A | [-1, -3, 4] |
| 5 | [4, -2, 5] | C | [4, -2, -2] |
| 6 | [9, -1, -1] | A | [2, -1, -1] |
| 7 | [7, 0, 0] | A | [0, 0, 0] |

如上,经过平滑性处理后,得到的服务器序列为 [A, A, B, A, C, A, A],相比之前的序列 [A, A, A, A, A, B, C],分布性要好一些。初始情况下 currentWeight = [0, 0, 0],第7个请求处理完后,currentWeight 再次变为 [0, 0, 0]。

代码实现

// 增加一个Weight类,用来保存ip, weight(固定不变的原始权重), currentweight(当前会变化的权重)
public class Weight {
private String ip;
private Integer weight;
private Integer currentWeight;
public Weight(String ip, Integer weight, Integer currentWeight) {
this.ip = ip;
this.weight = weight;
this.currentWeight = currentWeight;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
public Integer getCurrentWeight() {
return currentWeight;
}
public void setCurrentWeight(Integer currentWeight) {
this.currentWeight = currentWeight;
}
}
public class WeightRoundRobinV2 {
private static Map weightMap = new HashMap();
public static String getServer() {
// 获取权重之和
int totalWeight = ServerIps.WEIGHT_LIST1.values().stream().reduce(0, (w1, w2) -> w1 + w2);
//初始化weightMap,初始时将currentWeight赋值为weight
if (weightMap.isEmpty()) {
ServerIps.WEIGHT_LIST1.forEach((key, value) -> {
weightMap.put(key, new Weight(key, value, value));
});
}
//找出currentWeight最大值
Weight maxCurrentWeight = null;
for (Weight weight : weightMap.values()) {
if (maxCurrentWeight == null || weight.getCurrentWeight() > maxCurrentWeight.getCurrentWeight()) {
maxCurrentWeight = weight;
}
}
//将maxCurrentWeight减去总权重和
maxCurrentWeight.setCurrentWeight(maxCurrentWeight.getCurrentWeight() - totalWeight);
//所有的ip的currentWeight统一加上原始权重
for (Weight weight : weightMap.values()) {
weight.setCurrentWeight(weight.getCurrentWeight() + weight.getWeight());
}
//返回maxCurrentWeight所对应的ip
return maxCurrentWeight.getIp();
}
public static void main(String[] args) {
// 连续调用10次
for (int i = 0; i < 10; i++) {
System.out.println(getServer());
}
}
}

ServerIps里添加数据WEIGHT_LIST1:

public static final Map WEIGHT_LIST1 = new HashMap();
static {
// 权重之和为50
WEIGHT_LIST1.put(“A”, 5);
WEIGHT_LIST1.put(“B”, 1);
WEIGHT_LIST1.put(“C”, 1);
}

执行结果如下:

A

A

B

A

C

A

A

A

A

B

一致性哈希算法-ConsistentHashLoadBalance


服务器集群接收到一次请求调用时,可以根据请求的信息,比如客户端的Ip地址,或请求路径与请求参数等信息进行哈希,可以得到一个哈希值,特点是对于相同的ip地址,或请求路径和请求参数哈希出来的值是不一样的,只要能再增加一个算法,能够把这个哈希值映射成一个服务端ip地址,就可以使相同的请求(相同的ip地址,或请求路径和请求参数)落到同一服务器上。

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

  • 哈希值如果需要ip1和ip2之间的,则应该选择ip2作为结果;


相关文章
|
运维 持续交付 云计算
深入解析云计算中的微服务架构:原理、优势与实践
深入解析云计算中的微服务架构:原理、优势与实践
852 86
|
安全 算法 网络协议
解析:HTTPS通过SSL/TLS证书加密的原理与逻辑
HTTPS通过SSL/TLS证书加密,结合对称与非对称加密及数字证书验证实现安全通信。首先,服务器发送含公钥的数字证书,客户端验证其合法性后生成随机数并用公钥加密发送给服务器,双方据此生成相同的对称密钥。后续通信使用对称加密确保高效性和安全性。同时,数字证书验证服务器身份,防止中间人攻击;哈希算法和数字签名确保数据完整性,防止篡改。整个流程保障了身份认证、数据加密和完整性保护。
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
479 14
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
330 22
解析静态代理IP改善游戏体验的原理
|
机器学习/深度学习 数据可视化 PyTorch
深入解析图神经网络注意力机制:数学原理与可视化实现
本文深入解析了图神经网络(GNNs)中自注意力机制的内部运作原理,通过可视化和数学推导揭示其工作机制。文章采用“位置-转移图”概念框架,并使用NumPy实现代码示例,逐步拆解自注意力层的计算过程。文中详细展示了从节点特征矩阵、邻接矩阵到生成注意力权重的具体步骤,并通过四个类(GAL1至GAL4)模拟了整个计算流程。最终,结合实际PyTorch Geometric库中的代码,对比分析了核心逻辑,为理解GNN自注意力机制提供了清晰的学习路径。
830 7
深入解析图神经网络注意力机制:数学原理与可视化实现
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
1055 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
机器学习/深度学习 缓存 自然语言处理
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
Tiktokenizer 是一款现代分词工具,旨在高效、智能地将文本转换为机器可处理的离散单元(token)。它不仅超越了传统的空格分割和正则表达式匹配方法,还结合了上下文感知能力,适应复杂语言结构。Tiktokenizer 的核心特性包括自适应 token 分割、高效编码能力和出色的可扩展性,使其适用于从聊天机器人到大规模文本分析等多种应用场景。通过模块化设计,Tiktokenizer 确保了代码的可重用性和维护性,并在分词精度、处理效率和灵活性方面表现出色。此外,它支持多语言处理、表情符号识别和领域特定文本处理,能够应对各种复杂的文本输入需求。
1502 6
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
13637 46
|
传感器 人工智能 监控
反向寻车系统怎么做?基本原理与系统组成解析
本文通过反向寻车系统的核心组成部分与技术分析,阐述反向寻车系统的工作原理,适用于适用于商场停车场、医院停车场及火车站停车场等。如需获取智慧停车场反向寻车技术方案前往文章最下方获取,如有项目合作及技术交流欢迎私信作者。
996 2
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
2028 12

相关实验场景

更多

推荐镜像

更多
  • DNS
  • 下一篇
    开通oss服务