Nacos 自定义负载均衡,优先使用同IP服务
在微服务开发过程中,随着微服务数量越来越多,不可能将所有的微服务都在本地启用然后进行调试。最好的方式是需要调试那个服务就启动那个服务,所有的服务都使用开发Nacos,本地需要搭建nacos。
使用nacos在开发微服务的过程中,如果多人同时开发使用同一台服务就会导致服务调用错乱。也不知道自己的请求到达了那个服务。
如果在开发过程中,每个人都可以只调用自己的服务,那样调试代码就会很舒服。
鉴于以上问题,现有两种解决方案
方案一、使用nacos不同集群
每个开发都在配置文件中配置属于自己的集群,那么在进行调试代码时nacos会优先使用相同集群中的服务,如果同集群中服务找不到会返回其他集群中所有实例;这样每个人都只调用自己的服务,也能解决问题。但是会有多分配置文件。也不是太完美;
spring:cloud:loadbalancer.nacos.enabled:truenacos:discovery:server-addr: 127.0.0.1:8848group: dev
# 指定集群的名称,每个人都有自己一份cluster-name: local
方案二、自定义nacos负载均衡机制
1、自定义nacos负载均衡器
自定义nacos服务加载机制,优先使用与本地同IP的服务。本地找不到则使用同集群,然后在使用其他集群服务
自定义负载均衡代码如下:
importcn.hutool.core.net.NetUtil;importcom.alibaba.cloud.commons.lang.StringUtils;importcom.alibaba.cloud.nacos.NacosDiscoveryProperties;importcom.alibaba.cloud.nacos.balancer.NacosBalancer;importcom.alibaba.nacos.client.naming.utils.CollectionUtils;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.ObjectProvider;importorg.springframework.cloud.client.ServiceInstance;importorg.springframework.cloud.client.loadbalancer.DefaultResponse;importorg.springframework.cloud.client.loadbalancer.EmptyResponse;importorg.springframework.cloud.client.loadbalancer.Request;importorg.springframework.cloud.client.loadbalancer.Response;importorg.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;importorg.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;importorg.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;importreactor.core.publisher.Mono;importjava.util.List;importjava.util.Set;importjava.util.stream.Collectors;/**
* nacos 本地服务优先负载均衡器
*/publicclassNacosLocalFirstLoadBalancerimplementsReactorServiceInstanceLoadBalancer{privatestaticfinalLogger log =LoggerFactory.getLogger(NacosLocalFirstLoadBalancer.class);privatefinalString serviceId;privateObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;privatefinalNacosDiscoveryProperties nacosDiscoveryProperties;privateSet<String> localIps;publicNacosLocalFirstLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,String serviceId,NacosDiscoveryProperties nacosDiscoveryProperties){this.serviceId = serviceId;this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;this.nacosDiscoveryProperties = nacosDiscoveryProperties;// 使用hutool 工具获取本机IP地址this.localIps =NetUtil.localIpv4s();}@OverridepublicMono<Response<ServiceInstance>>choose(Request request){ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);return supplier.get().next().map(this::getInstanceResponse);}/**
* 优先获取与本地IP一致的服务,否则获取同一集群服务
*
* @param serviceInstances
* @return
*/privateResponse<ServiceInstance>getInstanceResponse(List<ServiceInstance> serviceInstances){if(serviceInstances.isEmpty()){
log.warn("No servers available for service: "+this.serviceId);returnnewEmptyResponse();}// 过滤与本机IP地址一样的服务实例if(!CollectionUtils.isEmpty(this.localIps)){for(ServiceInstance instance : serviceInstances){String host = instance.getHost();if(this.localIps.contains(host)){returnnewDefaultResponse(instance);}}}returnthis.getClusterInstanceResponse(serviceInstances);}/**
* 同一集群下优先获取
*
* @param serviceInstances
* @return
*/privateResponse<ServiceInstance>getClusterInstanceResponse(List<ServiceInstance> serviceInstances){if(serviceInstances.isEmpty()){
log.warn("No servers available for service: "+this.serviceId);returnnewEmptyResponse();}try{String clusterName =this.nacosDiscoveryProperties.getClusterName();List<ServiceInstance> instancesToChoose = serviceInstances;if(StringUtils.isNotBlank(clusterName)){List<ServiceInstance> sameClusterInstances = serviceInstances.stream().filter(serviceInstance ->{String cluster = serviceInstance.getMetadata().get("nacos.cluster");returnStringUtils.equals(cluster, clusterName);}).collect(Collectors.toList());if(!CollectionUtils.isEmpty(sameClusterInstances)){
instancesToChoose = sameClusterInstances;}}else{
log.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}",
serviceId, clusterName, serviceInstances);}ServiceInstance instance =NacosBalancer.getHostByRandomWeight3(instancesToChoose);returnnewDefaultResponse(instance);}catch(Exception e){
log.warn("NacosLoadBalancer error", e);returnnull;}}}
以上代码大部分来源于 com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer 添加了本地优先过滤规则
2、注册自定义负载均衡器到LoadBalancerClients中
使用自定义负载均衡器覆盖默认配置
importcom.alibaba.cloud.nacos.NacosDiscoveryProperties;importcom.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancerClientConfiguration;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.boot.autoconfigure.condition.ConditionalOnProperty;importorg.springframework.cloud.client.ConditionalOnDiscoveryEnabled;importorg.springframework.cloud.client.ServiceInstance;importorg.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;importorg.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;importorg.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Import;importorg.springframework.core.env.Environment;/**
* nacos 负载均衡同IP 同区域有限
*/@Configuration(proxyBeanMethods =false)@ConditionalOnDiscoveryEnabled// 这里引入 nacos 默认客户端配置,否则的话需要添加 配置 spring.cloud.loadbalancer.nacos.enabled = true@Import(NacosLoadBalancerClientConfiguration.class)publicclassNacosLocalFirstLoadBalancerClientConfiguration{privatestaticfinalLogger log =LoggerFactory.getLogger(NacosLocalFirstLoadBalancerClientConfiguration.class);/**
* 本地优先策略
* @param environment 环境变量
* @param loadBalancerClientFactory 工厂
* @param nacosDiscoveryProperties 属性
* @return ReactorLoadBalancer
*/@Bean@ConditionalOnProperty(value ="spring.cloud.loadbalancer.local-first", havingValue ="true")publicReactorLoadBalancer<ServiceInstance>nacosLocalFirstLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory,NacosDiscoveryProperties nacosDiscoveryProperties){String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);if(log.isDebugEnabled()){
log.debug("Use nacos local first load balancer for {} service", name);}returnnewNacosLocalFirstLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class),
name, nacosDiscoveryProperties);}}
3、添加自动发现类
前两部代码不能直接添加到spring bean 中,否则在引用时获取不到请求服务名称,容易报空指针异常;
需要通过@LoadBalancerClients 注解注入自定义的负载均衡器
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} that sets up LoadBalancer for Nacos.
*/@Configuration(proxyBeanMethods =false)@EnableConfigurationProperties@ConditionalOnNacosDiscoveryEnabled@LoadBalancerClients(defaultConfiguration =NacosLocalFirstLoadBalancerClientConfiguration.class)publicclassLocalFirstLoadBalancerNacosAutoConfiguration{}
4、如果需要自动装配
resources/META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.commmon.LocalFirstLoadBalancerNacosAutoConfiguration
经过上述配置之后,就可以在开发微服务中与同事一起调试代码了,也不会出现调用串了问题。
版权归原作者 java爱好者 所有, 如有侵权,请联系我们删除。