import org.apache.commons.lang3.tuple.Pair;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

/**
* 加权随机,nacos
*/
public class RouterWeightRandom {
/**
*
* @param list [{"a":1,"b":3,"c":6}]
*/
public String route(List<Pair<String,Double>> list) {
List<String> addresses = convert2Addresses(list);
// [0.1,0.4,1]
double[] cumulativeWeights = calcCumulativeWeight(list);
// 0.4/0.3/0.99999
double random = ThreadLocalRandom.current().nextDouble(0, 1);
// 下标(0.4时)或 -(插入点:第一个大于num的元素所在下标[0.3时返回1]/若都
// 小于num,则为arr.length[0.99999时返回3])-1
int index = Arrays.binarySearch(cumulativeWeights, random);
if (index < 0) {
// -2 -4
index = -index - 1;
} else {
// b
return addresses.get(index);
}
// 增强校验
if (index < cumulativeWeights.length && random < cumulativeWeights[index]) {
return addresses.get(index);
}
return null;
}

    /**
* 计算累计权重比例
* @param list [{"a":1,"b":3,"c":6}]
* @return index处元素的值= <=index所有元素的比例之和,如 [0.1,0.4,1]
*/
private double[] calcCumulativeWeight(List<Pair<String, Double>> list) {
// 所有权重值累加 10
double originWeightSum =  0D;
for (Pair<String,Double> item : list) {
double weight = item.getRight();
originWeightSum += weight;
}
// 每一项权重所占比例 [0.1,0.3,0.6]
double[] weightRatios = new double[list.size()];
int index = 0;
for (Pair<String,Double> item : list) {
double singleWeight = item.getRight();
weightRatios[index++] = singleWeight / originWeightSum;
}
// index处元素的值= <=index所有元素的比例之和 [0.1,0.4,1]
double[] cumulativeWeights = new double[list.size()];
double randomRange = 0D;
for (int i = 0; i < index; i++) {
cumulativeWeights[i] = randomRange + weightRatios[i];
randomRange += weightRatios[i];
}
// 允许的误差
double doublePrecisionDelta = 0.0001;
if (index == 0 || (Math.abs(cumulativeWeights[index - 1] - 1) < doublePrecisionDelta)) {
return cumulativeWeights;
}
throw new RuntimeException("累加权重计算出错,因为几率和不等于1");
}

    /**
* 获取地址列表
*/
private List<String> convert2Addresses(List<Pair<String, Double>> list) {
return list.stream().map(Pair::getLeft).collect(Collectors.toList());
}
}

好的,下面我将逐行详细解释你提供的 Java 代码,包括 RouterWeightRandom 类和 testRouterWeightRandom 测试方法的每一行逻辑、作用、运行效果,帮助你彻底理解 加权随机(Weighted Random)负载均衡算法 的实现原理。

🌟 一、整体说明
这段代码实现的是 加权随机负载均衡算法(Weighted Random Load Balancing),它根据每个服务器地址的“权重”来决定其被选中的概率。权重越高,被选中的概率越大。
• RouterWeightRandom:核心算法类,实现加权随机选择。
• RouterTest.testRouterWeightRandom():测试类,模拟地址列表并运行算法。

✅ 二、测试类 RouterTest 解析
Java
深色版本


public class RouterTest {
定义一个测试类,用于测试 RouterWeightRandom。

Java
深色版本

    
/**
* 加权随机
*/
@Test
public void testRouterWeightRandom() {
使用 JUnit 的 @Test 注解标记这是一个测试方法,用于验证加权随机算法是否工作正常。

Java
深色版本


RouterWeightRandom router = new RouterWeightRandom();
创建一个 RouterWeightRandom 实例,用于调用其 route() 方法进行路由选择。

Java
深色版本


List<String> list = mockAddressList();
调用 mockAddressList() 方法,获取一个模拟的服务器地址列表,内容如下:
深色版本


["http://1.1.1.1", "http://1.1.1.2", "http://1.1.1.3", "http://1.1.1.4", "http://1.1.1.5"]

Java
深色版本


List<Pair<String,Double>> finalList = new ArrayList<>();
创建一个 List<Pair<String, Double>> 类型的列表,用于存储每个地址及其对应的权重值。
• Pair 来自 Apache Commons Lang3,表示一个键值对:address 和 weight。

Java
深色版本

        
for
(String address : list) {
double weight = ThreadLocalRandom.current().nextDouble(1000
);
finalList.add(Pair.of(address, weight));
System.out.println("address:" + address +",权重:"
+ weight);
}
遍历每个地址,为每个地址分配一个 0 到 1000 之间的随机权重,并打印出来。
运行效果示例输出:
深色版本


address:http://1.1.1.1,权重:345.67
address:http://1.1.1.2,权重:892.34
...
✅ 这里权重是随机生成的,实际项目中权重可能来自配置中心(如 Nacos)或健康检查结果。

Java
深色版本

        
for (int i = 0; i < 10
; i++) {
String route = router.route(finalList);
System.out.println((i + 1) + ".route = "
+ route);
}
循环 10 次,每次调用 router.route(finalList) 执行一次加权随机选择,并打印选中的地址。
运行效果示例输出:
深色版本


1.route = http://1.1.1.2
2.route = http://1.1.1.5
...
✅ 权重高的地址被选中的概率更高。

Java
深色版本

    
private List<String> mockAddressList() 
{
List<String> list = new
ArrayList<>();
list.add("http://1.1.1.1"
);
list.add("http://1.1.1.2"
);
list.add("http://1.1.1.3"
);
list.add("http://1.1.1.4"
);
list.add("http://1.1.1.5"
);
return
list;
}
模拟生成 5 个服务器地址,用于测试。

✅ 三、核心类 RouterWeightRandom 详细解析
Java
深色版本


import
org.apache.commons.lang3.tuple.Pair;
import
java.util.Arrays;
import
java.util.List;
import
java.util.concurrent.ThreadLocalRandom;
import
java.util.stream.Collectors;

/**
* 加权随机,nacos
*/
public class RouterWeightRandom {
导入所需类,定义主类 RouterWeightRandom,用于实现加权随机算法。

🔹 方法一:route() —— 主路由方法
Java
深色版本

    
public String route(List<Pair<String,Double>> list) {
入口方法,输入是一个地址与权重的配对列表,返回选中的地址。

Java
深色版本


List<String> addresses = convert2Addresses(list);
调用 convert2Addresses() 方法,提取所有地址,生成一个纯地址列表。
例如: ["http://1.1.1.1", "http://1.1.1.2", ...]
✅ 用于后续通过索引返回最终选中的地址。

Java
深色版本

        
double[] cumulativeWeights = calcCumulativeWeight(list);
调用 calcCumulativeWeight() 方法,计算累计权重比例数组。
例如: 如果原始权重是 [1, 3, 6],则:
• 总权重 = 10
• 比例 = [0.1, 0.3, 0.6]
• 累计比例 = [0.1, 0.4, 1.0]
✅ 这个数组用于“轮盘赌”式随机选择。

Java
深色版本

        
double random = ThreadLocalRandom.current().nextDouble(0, 1);
生成一个 [0, 1) 区间内的随机数,作为“命中值”。
例如: random = 0.45
✅ 这个随机数将在累计权重数组中查找它“落在哪个区间”。

Java
深色版本

        
int index = Arrays.binarySearch(cumulativeWeights, random);
使用 二分查找 在 cumulativeWeights 数组中查找 random 的插入位置。
• 如果 random 正好等于某个元素,返回该元素的索引(≥0)。
• 如果 random 不在数组中,返回 -(插入点) - 1(负数)。
例如:
• cumulativeWeights = [0.1, 0.4, 1.0]
• random = 0.45 → 二分查找返回 -3(因为应插入在索引 2 前)
• random = 0.4 → 返回 1
• random = 0.05 → 返回 -1

Java
深色版本

        
if (index < 0
) {
index = -index - 1
;
} else
{
return
addresses.get(index);
}
处理二分查找的结果:
• 如果 index < 0:说明 random 不在数组中,需要转换为应插入的位置。
• index = -index - 1 就是它应该插入的位置,也就是它“落在”的区间。
• 例如:-3 → 2,说明 random=0.45 落在第 2 个区间(即第三个地址)。
• 如果 index >= 0:说明 random 正好等于某个累计值,直接返回该索引对应的地址。
✅ 这一步完成了“轮盘赌”选择:根据随机数找到对应的地址索引。

Java
深色版本

        
if
(index < cumulativeWeights.length && random < cumulativeWeights[index]) {
return
addresses.get(index);
}
增强校验:确保 index 合法,并且 random 确实小于等于该区间的上限。
• 虽然前面已经通过二分查找确定了位置,但这里再次确认。
• 防止边界问题或浮点误差。
✅ 如果满足条件,返回 index 对应的地址。

Java
深色版本

        
return null;
如果以上条件都不满足(理论上不应该发生),返回 null。
⚠️ 这是一个兜底逻辑,表示选择失败。

🔹 方法二:calcCumulativeWeight() —— 计算累计权重
Java
深色版本

    
private double[] calcCumulativeWeight(List<Pair<String, Double>> list) {
输入权重列表,返回累计权重比例数组。

Java
深色版本

        
double originWeightSum = 0D
;
for
(Pair<String,Double> item : list) {
double
weight = item.getRight();
originWeightSum += weight;
}
计算所有权重的总和。
例如: [1, 3, 6] → originWeightSum = 10

Java
深色版本

        
double[] weightRatios = new double
[list.size()];
int index = 0
;
for
(Pair<String,Double> item : list) {
double
singleWeight = item.getRight();
weightRatios[index++] = singleWeight / originWeightSum;
}
计算每个权重占总权重的比例。
例如: [1/10, 3/10, 6/10] = [0.1, 0.3, 0.6]

Java
深色版本

        
double[] cumulativeWeights = new double
[list.size()];
double randomRange = 0D
;
for (int i = 0
; i < index; i++) {
cumulativeWeights[i] = randomRange + weightRatios[i];
randomRange += weightRatios[i];
}
构建累计权重数组:
• cumulativeWeights[0] = 0.1
• cumulativeWeights[1] = 0.1 + 0.3 = 0.4
• cumulativeWeights[2] = 0.4 + 0.6 = 1.0
✅ 最终得到 [0.1, 0.4, 1.0],表示三个区间的边界。

Java
深色版本

        
double doublePrecisionDelta = 0.0001
;
if (index == 0 || (Math.abs(cumulativeWeights[index - 1] - 1
) < doublePrecisionDelta)) {
return
cumulativeWeights;
}
throw new RuntimeException("累加权重计算出错,因为几率和不等于1");
校验累计权重是否接近 1.0(允许浮点误差)。
• 如果最后一个元素不是 1.0(在误差范围内),抛出异常。
• 防止权重计算错误导致概率偏差。

🔹 方法三:convert2Addresses() —— 提取地址
Java
深色版本

    
private List<String> convert2Addresses(List<Pair<String, Double>> list) 
{
return
list.stream().map(Pair::getLeft).collect(Collectors.toList());
}
使用 Java 8 Stream 流,从 Pair<String, Double> 列表中提取所有 String 地址,返回一个纯地址列表。
✅ 用于后续通过索引返回地址。

✅ 四、算法核心思想:轮盘赌(Roulette Wheel Selection)
• 权重归一化:将权重转换为 [0,1] 区间内的比例。
• 构建累计区间:如 [0.1, 0.4, 1.0] 表示三个区间:
• [0, 0.1) → 地址 A
• [0.1, 0.4) → 地址 B
• [0.4, 1.0] → 地址 C
• 生成随机数:在 [0,1) 内随机。
• 查找区间:使用二分查找确定随机数落在哪个区间,返回对应地址。
✅ 权重越高,区间越宽,被选中的概率越大。

✅ 五、总结
组件
作用
Pair<String, Double>
存储地址和权重
calcCumulativeWeight()
计算累计权重比例数组
convert2Addresses()
提取地址列表
Arrays.binarySearch()
快速查找随机数落在哪个区间
ThreadLocalRandom
生成随机数

✅ 六、运行流程图(简化)
深色版本


输入: [(A,1), (B,3), (C,6)]

计算总权重 = 10

计算比例: [0.1, 0.3, 0.6]

累计比例: [0.1, 0.4, 1.0]

生成 random = 0.45

二分查找 → 落在 [0.4, 1.0] 区间 → 选中 C

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI时代SEO关键词优化策略

内容概要 在人工智能&#xff08;AI&#xff09;技术深度融入数字营销的背景下&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;的关键词优化策略正经历一场智能变革&#xff0c;这不仅重塑了传统研究方式&#xff0c;还为企业带来了全新的竞争机遇。本文将从AI时代SEO的变…

复矩阵与共轭转置矩阵乘积及其平方根矩阵

设 是一个 的复数矩阵&#xff0c;其共轭转置矩阵&#xff08;Hermitian 共轭&#xff09;记为 &#xff08;即 &#xff09;&#xff0c;则矩阵 &#xff08; &#xff09;和 &#xff08; &#xff09;的性质如下文所述。1. Hermitian 性&#xff08;自共轭性&#x…

Vue 框架 学习笔记

作为初学者对于Vue框架的学习笔记 总结了Vue框架的核心知识点&#xff0c;包括&#xff1a;1. 基础概念&#xff1a;渐进式框架、两种使用方式、Vue实例创建流程、模板语法和响应式特性。2. 常用指令&#xff1a;详细介绍了v-html、v-show/v-if、v-for、v-on、v-bind、v-model等…

飞牛系统安装DataEase自定义Docker包

飞牛系统安装DataEase自定义Docker包背景构造DataEase Docker包1.在Linux 系统中&#xff08;比如我这里选麒麟V10&#xff09;安装Docker2.准备打包文件3.执行打包4.验证打好的包上传DataEase Docker包1.把本地docker 容器导出1.1查看镜像列表命令&#xff1a;docker images1.…

可配置的PWM外设模块

&#x1f527; 可配置的PWM外设模块 基于FPGA的PWM信号发生器&#xff0c;支持 动态周期与占空比配置&#xff0c;无需外部控制信号&#xff0c;适用于 LED 呼吸灯、舵机控制、电机驱动等场景。 仿真波形 参数修改后会晚一个pwm周期才生效&#x1f4cc; 模块功能 &#x1f9ee;…

从零到一:我是如何用深度学习打造高性能书籍推荐系统的

作者&#xff1a;笙囧同学 | 发布时间&#xff1a;2025年7月28日 | 阅读时长&#xff1a;15分钟 &#x1f3af; 前言&#xff1a;为什么要做这个项目&#xff1f; 大家好&#xff0c;我是笙囧同学&#xff01;最近在学习《机器学习基础》课程时&#xff0c;被推荐系统的魅力深…

OpenRLHF:面向超大语言模型的高性能RLHF训练框架

“四模型协同调度破资源壁垒&#xff0c;让70B模型RLHF训练触手可及” OpenRLHF 是由 OpenLLMAI 团队于2024年推出的开源强化学习人类反馈&#xff08;RLHF&#xff09;框架&#xff0c;旨在解决大语言模型&#xff08;LLM&#xff09;对齐训练中的多模型协调瓶颈与超大规模扩展…

DMETL安装流程及简单使用

目录 安装调度器 安装执行器 安装管理器 启动服务 进入web管理端 创建数据源 ​编辑 添加表 添加影子表增量 节点监控 DMETL工程流搭建实践 创建表/视图 添加sql脚本 添加数据清洗与转换模块 添加排序模块 创建输出表 连接各模块并启动 查看验证结果 监控管理 …

如何通过代码操作文件?

1. 为什么使用文件不使用文件&#xff0c;我们所写的程序存在电脑内存中&#xff0c;程序结束&#xff0c;内存回收&#xff0c;数据就丢失了。再次运行程序也是看不到上次运行时的数据的&#xff0c;如果想要将数据进行持久化保存&#xff0c;就需要使用文件。2. 文件分类&…

unbuntn 22.04 coreutils文件系统故障

文章目录核心思路具体操作步骤&#xff08;需借助 Ubuntu Live USB&#xff09;1. 准备 Ubuntu Live USB2. 从 Live USB 启动并挂载系统分区3. 从安装包中提取完好的 /bin/dir 文件并替换4. 重启系统并验证总结前提说明具体操作步骤&#xff08;分阶段执行&#xff09;阶段1&am…

若依【(前后端分离版)SpringBoot+Vue3】

文章目录什么是若依使用若依验证码的前端实现&#x1f4cc; 前后端验证码流程说明文档1、前端初始化验证码2、前端界面显示3、后端生成验证码接口&#xff08;GET /captchaImage&#xff09;4、用户提交登录信息5、后端验证验证码逻辑&#xff08;POST /login&#xff09;6、登…

Ubuntu24安装MariaDB/MySQL后不知道root密码如何解决

Ubuntu 24.04 安装 MariaDB 后 root 密码未知&#xff1f;解决方案在此在 Ubuntu 24.04 上新安装 MariaDB 后&#xff0c;许多用户会发现自己不知道 root 用户的密码&#xff0c;甚至在安装过程中也没有提示设置密码。这是因为在较新的 MariaDB 版本中&#xff0c;默认情况下 r…

Cloudflare CDN 中设置地域限制并返回特定界面

文章目录 什么是CDN 什么是Cloudflare 注册Cloudflare 账号,添加域名、修改DNS并激活邮箱 阻止或允许特定国家或地区访问 常见规则表达式 WAF自定义规则 + 自定义错误页面 使用Workers脚本 什么是CDN CDN 是一种优化网站请求处理的机制。它是在用户访问网站 (服务器) 时用户与…

Ubuntu高频实用命令大全

Ubuntu系统中高频实用命令 以下为Ubuntu系统中高频实用命令的分类整理,涵盖系统管理、文件操作、网络配置等场景,每个命令附带简要说明: 系统信息与管理 uname -a 显示系统内核版本、主机名等详细信息。 lsb_release -a 查看Ubuntu发行版版本信息。 uptime 显示系统运行时…

关于C#的编程基础:数据类型与变量全解析

一.基本的数据类型 1.什么是数据类型 在编程语言中&#xff0c;数据类型&#xff08;Data Type&#xff09; 是对变量存储的 “数据的种类” 的定义&#xff0c;它决定了&#xff1a; 变量可以存储哪些值&#xff08;例如整数、文本、布尔值&#xff09;。这些值在内存中如何…

深入解析 Spring 获取 XML 验证模式的过程

关键要点Spring 的 XML 验证模式&#xff1a;Spring 框架在加载 XML 配置文件时&#xff0c;会根据文件内容判断使用 DTD&#xff08;文档类型定义&#xff09;或 XSD&#xff08;XML 模式定义&#xff09;进行验证。自动检测机制&#xff1a;Spring 默认使用自动检测&#xff…

复现《Local GDP Estimates Around the World》论文的完整指南

复现《Local GDP Estimates Around the World》论文的完整指南 1. 引言 1.1 论文概述 《Local GDP Estimates Around the World》是一篇重要的经济地理学研究论文&#xff0c;作者提出了一种创新的方法来估计全球范围内次国家层面的GDP数据。这项工作填补了全球经济发展研究中子…

Sql注入 之sqlmap使用教程

一、安装sqlmap 浏览器访问SQLmap官网 即可下载工具&#xff1b;需要说明的是&#xff0c;SQLmap运行依赖于python环境&#xff0c;所以在下载使用前务必在电脑及终端上安装好python环境。 通过网盘分享的文件&#xff1a;sqlmap-master.zip链接: https://pan.baidu.com/s/1YZi…

安宝特案例丨户外通信机房施工革新:AR+作业流技术破解行业难题

在数字化浪潮席卷各行各业的今天&#xff0c;传统户外通信机房建设领域正经历一场静悄悄的变革。作为信息社会的“神经枢纽”&#xff0c;户外机房的质量直接关系到通信网络的稳定性&#xff0c;但长期以来&#xff0c;这一领域却深受施工标准化不足、质量管控难、验收追溯复杂…

在 CentOS 中安装 MySQL 的过程与问题解决方案

MySQL 是一款广泛使用的开源关系型数据库管理系统&#xff0c;在 CentOS 系统中安装 MySQL 是很多开发者和运维人员常做的工作。下面将详细介绍安装过程以及可能遇到的问题和解决方案。 一、安装前的准备工作 在安装 MySQL 之前&#xff0c;需要做好一些准备工作&#xff0c;…