SpringCloud自定义loadbalancer实现标签路由

news/2025/2/21 7:21:38/文章来源:https://www.cnblogs.com/2YSP/p/18716104

一、背景

  最近前端反应开发环境有时候调接口会很慢,原因是有开发图方便将本地服务注册到开发环境,请求路由到开发本地导致,

为了解决该问题想到可以通过标签路由的方式避免该问题,实现前端联调和开发自测互不干扰。

  该方案除了用于本地调试,还可以用于用户灰度发布。

二、实现方案

  关于负载均衡,低版本的SpringCloud用的是Spring Cloud Ribbon,高版本用Spring Cloud LoadBalancer替代了,

Ribbon可以通过实现IRlue接口实现,这里只介绍高版本的实现方案。

实现方案:

  1. idea在环境变量中设置tag,本地服务启动时读取环境变量将tag注册到nacos的元数据

  2. 重写网关的负载均衡算法,从请求头中获取到的request-tag和服务实例的元数据进行匹配,如果匹配到则返回对应的

    服务实例,否则提示服务未找到。

三、编码实现

3.1 order服务

新建一个SpringCloud服务order-service,注册元数据很简单,只需要排除掉NacosDiscoveryClientConfiguration,再写一个自己的NacosDiscoveryClientConfiguration配置类即可。

创建MyNacosDiscoveryClientConfiguration

/*** @Author: Ship* @Description:* @Date: Created in 2025/2/12*/
@Configuration(proxyBeanMethods = false
)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class})
@AutoConfigureAfter({NacosDiscoveryAutoConfiguration.class})
public class MyNacosDiscoveryClientConfiguration {@Beanpublic DiscoveryClient nacosDiscoveryClient(NacosServiceDiscovery nacosServiceDiscovery) {return new NacosDiscoveryClient(nacosServiceDiscovery);}@Bean@ConditionalOnProperty(value = {"spring.cloud.nacos.discovery.watch.enabled"},matchIfMissing = true)public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties,ObjectProvider<ThreadPoolTaskScheduler> taskExecutorObjectProvider, Environment environment) {// 环境变量读取标签String tag = environment.getProperty("tag");nacosDiscoveryProperties.getMetadata().put("request-tag", tag);return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties, taskExecutorObjectProvider);}
}

这里代码基本与NacosDiscoveryClientConfiguration一致,只是加上了设置元数据的逻辑。

@SpringBootApplication(exclude = NacosDiscoveryClientConfiguration.class)
public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);}}

启动类上需要排除默认的NacosDiscoveryClientConfiguration,不然启动会报bean重复注册的错误,或者配置添加spring.main.allow-bean-definition-overriding=true允许重复注册也行。

写一个测试接口,方便后面测试

/*** @Author: Ship* @Description:* @Date: Created in 2025/2/12*/
@RequestMapping("test")
@RestController
public class TestController {@GetMapping("")public String sayHello(){return "hello";}
}

3.2 gateway服务

新建一个网关服务,pom文件如下:

 <properties><java.version>1.8</java.version><spring-cloud.version>2020.0.3</spring-cloud.version><spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version><spring-boot.version>2.5.1</spring-boot.version><maven-compiler-plugin.version>3.1</maven-compiler-plugin.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>${spring-boot.version}</version><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>${spring-boot.version}</version></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>${spring-cloud-alibaba.version}</version></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>${spring-cloud-alibaba.version}</version></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency></dependencies></dependencyManagement><build><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><version>${maven-compiler-plugin.version}</version><configuration><source>${java.version}</source><target>${java.version}</target></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>

Spring-Cloud-loadBalancer默认使用轮询的算法,即org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer类实现,因此可以参考RoundRobinLoadBalancer实现一个TagLoadBalancer,代码如下:

/*** @Author: Ship* @Description:* @Date: Created in 2025/2/12*/
public class TagLoadBalancer implements ReactorServiceInstanceLoadBalancer {private static final String TAG_HEADER = "request-tag";private static final Log log = LogFactory.getLog(TagLoadBalancer.class);final AtomicInteger position;final String serviceId;ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;public TagLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {this(serviceInstanceListSupplierProvider, serviceId, (new Random()).nextInt(1000));}public TagLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, int seedPosition) {this.serviceId = serviceId;this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;this.position = new AtomicInteger(seedPosition);}@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier) this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);return supplier.get(request).next().map((serviceInstances) -> {return this.processInstanceResponse(supplier, serviceInstances, request);});}private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances, Request request) {Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances, request);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback) supplier).selectedServiceInstance((ServiceInstance) serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, Request request) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + this.serviceId);}return new EmptyResponse();}if (request instanceof DefaultRequest) {DefaultRequest<RequestDataContext> defaultRequest = (DefaultRequest) request;// 上下文获取请求头HttpHeaders headers = defaultRequest.getContext().getClientRequest().getHeaders();List<String> list = headers.get(TAG_HEADER);if (!CollectionUtils.isEmpty(list)) {String requestTag = list.get(0);for (ServiceInstance instance : instances) {String str = instance.getMetadata().getOrDefault(TAG_HEADER, "");if (requestTag.equals(str)) {return new DefaultResponse(instance);}}log.error(String.format("No servers available for service:%s,tag:%s ", this.serviceId, requestTag));return new EmptyResponse();}}int pos = Math.abs(this.position.incrementAndGet());ServiceInstance instance = instances.get(pos % instances.size());return new DefaultResponse(instance);}
}

这里需要实现ReactorServiceInstanceLoadBalancer接口,如果请求头带有标签则根据标签路由,否则使用默认的轮询算法。

还要把TagLoadBalancer用起来,所以需要定义一个配置类TagLoadBalancerConfig,并通过@LoadBalancerClients注解添加默认配置,代码如下:

/*** @Author: Ship* @Description:* @Date: Created in 2025/2/12*/
public class TagLoadBalancerConfig {@Beanpublic ReactorLoadBalancer reactorTagLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new TagLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);}
}@LoadBalancerClients(defaultConfiguration = {TagLoadBalancerConfig.class})
@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}}

最后在application.yml文件添加网关路由配置

spring:application:name: gatewaycloud:nacos:config:server-addr: 127.0.0.1:8848namespace: devgroup: DEFAULT_GROUPdiscovery:server-addr: 127.0.0.1:8848namespace: devgateway:routes:- id: order-serviceuri: lb://order-servicepredicates:- Path=/order/**filters:- StripPrefix=1
server:port: 9000

3.3 代码测试

  • 本地启动nacos后启动order(注意需要在idea设置环境变量tag=ship)和gateway服务,可以看到order服务已经成功注册了元数据

    image

  • 然后用Postman请求网关http://localhost:9000/order/test

image

可以看到请求成功路由到了order服务,说明根据tag路由成功了。

  • 去掉环境变量tag后重新启动Order服务,再次请求响应报文如下:

    {"timestamp": "2025-02-14T12:10:44.294+00:00","path": "/order/test","status": 503,"error": "Service Unavailable","requestId": "41651188-4"
    }
    

说明根据requst-tag找不到对应的服务实例,代码逻辑生效了。

四、总结

  聪明的人已经发现了,本文只实现了网关路由到下游服务这部分的标签路由,下游服务A调服务B的标签路由并未实现,其实现方案也不难,只需要通过上下文传递+feign拦截器就可以做到全链路的标签路由,有兴趣的可以自己试试。

  本文代码已上传github,顺便推广下前段时间写的idea插件CodeFaster(快速生成常用流操作的代码,Marketplace搜索下载即可体验)😂。

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

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

相关文章

2025.2.14鲜花

ln将卷积转为加法。推歌 (看fengwu博客时候看见的) 《堕》 星河挂在天上 保护璀璨月亮 而你在我心中宛如月光 为你痴为你狂 为你笑为你闯 为你悲为你伤 为你扬 她是踏碎星河落入我梦境的幻想 环遍星系为你寻找的力量 神明给我在最难熬的时光 留下唯一的星光 堕入日月星辉之中…

解密prompt系列48. DeepSeek R1 Kimi 1.5长思维链 - RL Scaling

春节前DeepSeek R1和Kimi1.5炸翻天了,之前大家推测的O1的实现路径,多数都集中在MCTS推理优化,以及STaR等样本自优化方案等等,结果DeepSeek和Kiim直接出手揭示了reasoning的新路线不一定在SFT和Inference Scaling,也可以在RL。也算是Post Train阶段新的Scaling方向,几个核…

求勾股数

基本概念 众所周知(3,4,5)和(6,8,10)是两组勾股数,区别是前一组三个数的公因数是一而后一组的不是。像第一组勾股数一样三个数之间两两互质的就叫做 本原勾股数。而且将一组本原勾股数里的三个数同时扩大相同的倍数得到的一组数还是勾股数(只不过不是本原勾股数)。所…

腾讯元宝接入 DeepSeek R1 模型,支持深度思考 + 联网搜索,好用不卡机!

前言 腾讯元宝AI产品于2025年2月13日在应用商店发布更新,正式接入了DeepSeek R1模型,并宣布该模型已联网、满血上线,DeepSeek+腾讯混元,好用不卡机。腾讯元宝介绍 腾讯元宝是依托于腾讯混元、DeepSeek等大模型,基于跨知识领域和自然语言理解能力的大模型AI产品。元宝期望通…

QFileSystemWatcher+QTableView监控目录下文件变化

目标监控目录下的指定类型的文件,显示文件的序号、名称以及大小。 目录下的文件变化时(删除文件,新增文件后),程序界面可以实时显示目录下的状态。效果图监控目录下的 .h 文件完整源码 #include <QApplication> #include <QTableView> #include <QFileSyst…

服务器安装Nginx

环境CentOS 8 使用编译安装 NginxNginx使用Docker安装确实有点麻烦,需要将很多前端文件映射到容器内部,不推荐 正式环境 1、安装环境需要的依赖包 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel2、在你的linux服务器上创建目录:nginx cd /root &…

WPF的DataGrid简单使用

简单做个记录。有时候会忘记某个样式怎么调整; 首先写一个DataGrid,AlternatingRowBackground表示隔行更改背景色;AlternationCount表示隔的行数;:1 <DataGrid Grid.Row="1" 2 AlternatingRowBackground="Snow" 3 Alternation…

报名丨Computer useVoice Agent :使用 TEN 搭建你的 Mac Assistant

与 TEN 相聚在「LET’S VISION 2025」大会,欢迎来展位上跟我们交流。这次我们还准备了一场聚焦「computer use」的工作坊,功能新鲜上线,线下首波体验!📅 TEN 展位:2025年3月1日-2日TEN workshop:2025年3月2日 13:30开始📍 上海浦东鲜花港TEN Framework 是一个主流对话…

本地AI搭建

搭建本地博客AI 目录搭建本地博客AI环境下载ollama选择模型选择embedding模型查看性能测试选择合适的嵌入模型(Embedder)估算内存选择模型量化类型介绍Q5_0 vs Q5_KQ5_K 变体(Q5_K_S、Q5_K_M、Q5_K_L)选择LLM模型下载模型下载LLM下载Embedder下载AnythingLLM配置向量数据库测试…

无需编码5分钟免费部署云上调用满血版DeepSeek

大家好,我是 V 哥。如何自己部署DeepSeek调用满血版。首先,如果你遇到了使用公共服务器时的延迟或限制,想要本地部署以获得更好的性能和稳定性。你是不是也想自己来部署DeepSeek呢,其实除了自己部署本地DeepSeek,还可以在云上免费部署满血版DeepSeek,接下来,V 哥来介绍这…

svm(support vector machine)之svr(support vector regression)学习笔记(?)

1.SVR和SVC的区分:SVR:构建函数拟合数据;SVC:二向数据点的划分(分类)注:SVR的是输入时给出的实际值 \(y_{i}\),SVC的 \(y_{i}\)是输入时给出的类别,即+1,-1。 2.SVR的目的:找到一个函数\(f(x)\),使之与训练数据给出的实际目标\(y_{i}\) 的偏差几乎不超过\(ε\),同时…

电脑必学基础操作,任何设备不白买,免费实现远程办公

随着科技的发展、时代的进步,手机、电脑、平板设备还有越来越多的软件应用已成为人们学习、工作、娱乐、社交中必不可少的工具。然而,固然这些智能化的产品益处颇多,若想要更好的掌握它们的妙用,更高效的进行使用,那可以好好了解一下一些基础操作!这不仅能够帮助我们获得…