💡 Spring Cloud LoadBalancer 实现自定义负载均衡策略(基于服务元数据筛选)

在微服务架构中,我们常常希望对服务实例进行更精细的路由控制,例如:

  • 灰度发布:不同环境访问不同版本
  • 操作系统差异:Windows/Mac/Linux 客户端访问对应的服务
  • 多机房、多网段:按区域划分访问

本文将基于 Spring Cloud LoadBalancer 实现一个自定义策略,按服务实例 metadata 中的 env(环境)和 os(系统)字段进行智能路由。


🧩 配置目的和背景

我们假设服务实例在注册到 Nacos 时会带上 metadata 元信息:

# application.yml
spring:cloud:nacos:discovery:metadata:env: ${spring.profiles.active}os: ${os.name}

📌 上述配置的作用:

  • env: 当前应用运行环境(dev/test/prod)
  • os: 当前操作系统(如 macOS、Windows)

这样注册到 Nacos 的服务实例就带上了标签,可以作为路由依据。


⚙️ 自定义负载均衡策略核心代码

1️⃣ 负载均衡器实现类 CustomFilteredLoadBalancer.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;/*** 自定义负载均衡器,实现 ReactorServiceInstanceLoadBalancer 接口。* 功能:* - 根据服务ID获取服务实例列表(由 ServiceInstanceListSupplier 提供)* - 过滤出满足当前环境(env)和操作系统(os)条件的实例* - 返回符合条件的实例封装成 Response,否则返回空响应*/
@Slf4j
public class CustomFilteredLoadBalancer implements ReactorServiceInstanceLoadBalancer {final AtomicInteger position;private final String serviceId;// 延迟获取 ServiceInstanceListSupplier,避免循环依赖private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;private final String currentEnv;  // 当前环境,如 "dev"private final String currentOs;   // 当前操作系统,如 "mac"public CustomFilteredLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,String serviceId,String currentEnv,String currentOs) {this(serviceInstanceListSupplierProvider, serviceId, currentEnv, currentOs,new Random().nextInt(1000));}public CustomFilteredLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,String serviceId,String currentEnv,String currentOs,int seedPosition) {this.serviceId = serviceId;this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;this.currentEnv = currentEnv.toLowerCase();this.currentOs = currentOs.toLowerCase();this.position = new AtomicInteger(seedPosition);}/*** 选择符合条件的服务实例** @param request 当前请求对象(通常不使用)* @return Mono<Response<ServiceInstance>> 响应封装选中的实例或空实例*/@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);return supplier.get(request).next().map(this::getInstanceResponse);}/*** 根据 env 和 os 过滤服务实例列表** @param serviceInstances 服务实例列表* @return Response<ServiceInstance> 包含选中实例或 EmptyResponse*/private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances) {if (serviceInstances.isEmpty()) {log.warn("没有可供服务的服务器: " + this.serviceId);return new EmptyResponse();}log.info("原始实例数量:" + serviceInstances.size());// 自定义扩展过滤逻辑List<ServiceInstance> instances = serviceInstances.stream().filter(instance -> {String instanceEnv = instance.getMetadata().getOrDefault("env", "").toLowerCase();boolean envMatch = instanceEnv.equals(currentEnv);if (!envMatch) {log.debug("过滤实例 [" + instance.getUri() + "],环境不匹配,实例env=" + instanceEnv + ", 当前env=" + currentEnv);}return envMatch;}).filter(instance -> {String instanceOs = instance.getMetadata().getOrDefault("os", "").toLowerCase();boolean osMatch;if (currentOs.contains("mac")) {osMatch = instanceOs.contains("mac");} else if (currentOs.contains("windows")) {osMatch = instanceOs.contains("windows");} else {osMatch = instanceOs.equals(currentOs);}if (!osMatch) {log.debug("过滤实例 [" + instance.getUri() + "],系统不匹配,实例os=" + instanceOs + ", 当前os=" + currentOs);}return osMatch;}).collect(Collectors.toList());log.info("过滤后实例数量:" + instances.size());if (instances.isEmpty()) {log.warn("没有符合条件的实例,返回空实例");return new EmptyResponse();}// 如果只有一个实例,则直接返回if (instances.size() == 1) {return new DefaultResponse(instances.get(0));}// 如果有多个实例,可以采用随机或轮询int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;ServiceInstance instance = instances.get(pos % instances.size());log.info("选中实例:" + instance.getUri());return new DefaultResponse(instance);}}

此处过滤后的服务实例选择逻辑:

  • 如果只有一个实例,则直接返回
  • 如果有多个实例,可以采用随机或轮询 ,这里我采用了org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer官方的轮循负载均衡器里面的源码

这个自定义的负载均衡策略过滤逻辑可以用一句话概括:

根据当前环境变量(env)和操作系统类型(os),从服务实例中筛选出最匹配的实例来使用。

👇 简单白话解释:

当你访问某个服务时,这个策略会先从注册中心获取到所有可用的服务实例,然后一步步筛选:

  1. 先看环境变量(env)是否匹配
    比如你本地运行的是 dev 环境,那就只选出那些注册时标记了 env=dev 的实例。

  2. 再看操作系统(os)是否匹配
    继续在第一步筛出来的基础上,再选出和你当前电脑或服务器系统一致的实例。

    • 你用的是 Mac,就只选出标记为 os=mac 的实例;

    • Windows,就选 os=windows

    • Linux,就严格匹配 os=linux

  3. 最终选中一个实例返回
    如果匹配到多个符合的实例,就默认选第一个(你也可以改成随机、轮询等方式);
    如果一个都没有匹配上,就返回空结果,意味着没有服务可用。

📌 这个策略适合什么场景?
  • 多套部署环境并存(如 dev/test/prod);

  • 某些服务只适用于特定操作系统(比如调用原生接口或依赖特定底层库);

  • 希望本地测试时访问本地服务实例,线上访问线上服务实例。


2️⃣ 配置类 CustomLoadBalancerConfiguration.java

/*** 自定义负载均衡器配置类,用于将自定义实现注册为 Spring Bean。* 系统会通过该配置在启动时注入我们的自定义负载均衡逻辑。*/
public class CustomLoadBalancerConfiguration {@Beanpublic ReactorLoadBalancer<?> reactorServiceInstanceLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {// 从环境中获取当前调用的服务名(被调用方)String serviceId = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);// 获取当前环境配置(如 dev、test、prod)String currentEnv = SpringUtils.getActiveProfile();// 获取当前操作系统名(如 Windows、Mac OS X)String currentOs = System.getProperty("os.name", "unknown");return new CustomFilteredLoadBalancer(loadBalancerClientFactory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class),serviceId,currentEnv,currentOs);}
}

3️⃣ 启动类中启用配置 Application.java

/*** 启动类* 使用 @LoadBalancerClients 注册我们的自定义负载均衡策略配置*/
@EnableFeignClients
@SpringBootApplication
@LoadBalancerClients(defaultConfiguration = {CustomLoadBalancerConfiguration.class})
public class GsApplication {public static void main(String[] args) {SpringApplication.run(GsApplication.class, args);}
}

📋 核心逻辑解析

步骤动作说明
1注入元数据在服务注册时注入 envos 字段
2实现 LoadBalancer自定义 choose 方法,按需过滤实例
3注册 Bean使用 ReactorLoadBalancer 接口注册负载均衡器
4全局启用启动类中用 @LoadBalancerClients 激活策略

✅ 优点

  • ✔️ 支持灰度环境隔离(按环境路由)
  • ✔️ 支持客户端系统识别(按 OS 分流)
  • ✔️ 可扩展为版本控制、机房标签等智能路由

❗ 注意事项

  • 💡 元数据必须在注册服务时注入
  • 💡 所有调用方都必须配置正确的 spring.profiles.activeos.name
  • 💡 默认只取第一个匹配的实例,如需其他策略请自行修改

📝 小结与建议

该方案通过 Spring Cloud LoadBalancer 提供的 SPI 接口扩展机制,灵活实现了按元数据(环境+系统)进行服务调用的策略。非常适用于以下场景:

  • 多环境隔离(dev/test/prod)
  • 灰度发布和分阶段上线
  • 跨平台客户端(Windows/Mac/Linux)路由控制

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

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

相关文章

Javaweb(1)html、css、js

注:图来自黑马 一、HTML(超文本标记语言) HTML 是网页的 “骨架”,负责定义页面的结构和内容,通过标签(tag)描述文本、图片、链接等元素。 1. 基础结构 文档声明:<!DOCTYPE html>(告诉浏览器这是 HTML5 文档)。 根标签:<html> 包裹整个文档,包含 &l…

MQTT:Dashboard数据集成(待补充)

目录一、工作原理二、基本使用三、连接器基本使用一、工作原理 数据集成使用sink和source组件与外部数据系统对接。 sink&#xff1a;用于将消息发送到外部数据系统&#xff0c;例如MySQL、Kafka或Http服务等。source&#xff1a;用于从外部数据系统接收消息&#xff0c;例如…

VisionMoE本地部署的创新设计:从架构演进到高效实现

本地部署VisionMoE的时代需求 在人工智能技术飞速发展的今天&#xff0c;视觉语言模型(Vision-Language Models, VLMs)已成为多模态理解的核心工具。然而&#xff0c;传统的大型视觉语言模型主要依赖云端GPU集群进行部署和推理&#xff0c;这不仅带来了高昂的运营成本&#xf…

机试备考笔记 8/31

2025年8月8日 小结&#xff1a;省流&#xff0c;写了俩道巨简单的&#xff08;被卡好久的传参指针和指针的引用的区别&#xff09;&#xff0c;一题递归&#xff08;意满&#xff09;&#xff1b;这笔记还是0809写的&#xff0c;啧&#xff0c;今天可能不写了&#xff0c;明天也…

java9学习笔记-part2

进程 API在 Java 9 之前&#xff0c;Process API 仍然缺乏对使用本地进程的基本支持&#xff0c;例如获取进程的 PID 和所有者&#xff0c;进程的开始时间&#xff0c;进程使用了多少 CPU 时间&#xff0c;多少本地进程正在运行等。Java 9 向 Process API 添加了一个名为 Proce…

AI智能编程工具汇总

AI智能编程工具汇总 以下是一份关于主流大模型开发工具的综合介绍&#xff0c;涵盖 Gemini CLI、Qwen-Code、Kimi K2 等关键工具的功能特性、安装方式与使用建议。 &#x1f31f; Gemini CLI 开发者&#xff1a;Google DeepMind 简介&#xff1a;命令行工具&#xff0c;用于调…

算法_python_牛客华为机试笔记_01

刷题是必须的&#xff0c;通过刷题以及别人对题目的解析&#xff0c;可以快速理解&#xff0c;提高效率。 00_题库与参考视频 华为机试_在线编程_牛客网 HJ3 明明的随机数_哔哩哔哩_bilibili 这套华为机试是华为笔试面试机考在线练习&#xff0c;共138道题&#xff0c;目前…

Java基础-完成局域网内沟通软件的开发

目录 案例要求&#xff1a; 实现思路&#xff1a; itheima-chat-server包 src com.itheima Constant类&#xff1a; Server类: ServerReaderThread类: itheima-chat-system包 src com.itheima.ui ChatEntryFrame类&#xff1a; ClientChatFrame类: ClientReaderTh…

windows内核研究(内存管理-线性地址的管理)

内存管理线性地址的管理 进程空间的地址划分分区x86 32位Windows空指针赋值区0x00000000 - 0x0000FFFF用户模式区0x00010000 - 0x7FFEFFFF64KB禁入区0x7FFF0000 - 0x7FFFFFFF内核0x80000000 - 0xFFFFFFFF线性地址有4GB&#xff0c;但是并不是所有的地方都能访问&#xff08;这里…

【问题解决】使用patch-package修改node-models中的源码

文章目录一、应用场景二、patch-package 和 postinstallpatch-packagepostinstall三、操作步骤1、使用yarn安装patch-package和postinstall-postinstall2、修改package.json3、修改node-model中源码、保存。4、找到修改文件对应的包名5、使用git将新增的patches文件同步到仓库6…

当配置项只支持传入数字,即无法指定单位为rem,需要rem转px

您好&#xff01;针对您 Vue 3 Element Plus 的技术栈&#xff0c;要优雅且符合大厂规范地解决这个问题&#xff0c;最佳实践是创建一个响应式的 Composition API (组合式函数)。 这个方法完全遵循 Vue 3 的设计哲学&#xff0c;具有高内聚、低耦合、可复用、类型安全&#xf…

谷歌搜索 sg_ss 逆向分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;部分python代码sg_ss cp.call(get_sg_…

一个“加锁无效“的诡异现象

加锁了还出问题&#xff1f;从"点击过快"到"状态可控"&#xff1a;多线程共享变量的并发陷阱与实战对策详情如下&#xff1a;在服务端开发中&#xff0c;多线程并发处理客户端请求是提升系统吞吐量的常见手段。最近有位开发者朋友遇到了一个令人费解的问题…

液体泄漏识别误报率↓76%:陌讯多模态融合算法实战解析

原创声明本文为原创技术解析&#xff0c;核心技术参数与架构设计引用自《陌讯技术白皮书》&#xff0c;禁止未经授权的转载与篡改。一、行业痛点&#xff1a;液体泄漏识别的现实挑战在化工生产、食品加工、仓储物流等场景中&#xff0c;液体泄漏的实时监测是保障安全生产的关键…

Y9000P跑开源模型(未完成)

环境信息 1、Y9000笔记本 2、1T空白硬盘 3、ubunut24.04桌面版 一、环境初始化 第一部分&#xff1a;系统初始化 1、安装基础软件 apt-get update apt-get -y install openssh-server openssh-client apt-utils freeipmi ipmitool sshpass ethtool zip unzip nano less git ne…

ARM体系结构

ARM体系结构 编程原理 从源代码到CPU执行过程 #mermaid-svg-M4xemCxDjIQVNNnW {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:14px;fill:#333;}#mermaid-svg-M4xemCxDjIQVNNnW .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mer…

基于SpringBoot的高校社团管理系统的设计与实现(代码+LW文档+远程运行)

&#x1f4af;博主&#xff1a;✌全网拥有50W粉丝、博客专家、全栈领域优质创作者、平台优质Java创作者、专注于Java技术领域和毕业项目实战✌&#x1f4af; &#x1f497;开发技术&#xff1a;SpringBoot、Vue、SSM、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、…

F5发布业界首创集成式应用交付与安全平台,开启ADC 3.0新时代

在数字化转型加速与AI技术蓬勃发展的今天&#xff0c;企业对应用性能与安全的需求正经历革命性变革。传统应用架构已难以满足现代混合多云环境与AI驱动型业务场景的严苛要求。全球领先的应用安全和交付服务提供商F5&#xff08;NASDAQ: FFIV&#xff09;&#xff0c;持续推动 F…

SELinux 入门指南

SELinux(Security-Enhanced Linux)是 Linux 内核的一个安全模块&#xff0c;它提供了一种强制访问控制&#xff08;Mandatory Access Control, MAC&#xff09;机制。与传统的 Linux 自主访问控制&#xff08;Discretionary Access Control, DAC&#xff09;不同&#xff0c;SE…

ARMv8 MMU页表格式及地址转换过程分析

1.简介 CPU发出的虚拟地址经过MMU转换后得到物理地址&#xff0c;然后使用物理地址访问真实的硬件。虚拟地址和物理地址的映射关系保存在页表中&#xff0c;MMU需要遍历页表&#xff0c;才能将虚拟地址转换成物理地址。ARM64现在有两种大小的页表描述符&#xff0c;分别是ARMv8…