Nacos 自定义负载均衡,优先使用同IP服务

在微服务开发过程中,随着微服务数量越来越多,不可能将所有的微服务都在本地启用然后进行调试。最好的方式是需要调试那个服务就启动那个服务,所有的服务都使用开发Nacos,本地需要搭建nacos。

使用nacos在开发微服务的过程中,如果多人同时开发使用同一台服务就会导致服务调用错乱。也不知道自己的请求到达了那个服务。

如果在开发过程中,每个人都可以只调用自己的服务,那样调试代码就会很舒服。

鉴于以上问题,现有两种解决方案

方案一、使用nacos不同集群

每个开发都在配置文件中配置属于自己的集群,那么在进行调试代码时nacos会优先使用相同集群中的服务,如果同集群中服务找不到会返回其他集群中所有实例;这样每个人都只调用自己的服务,也能解决问题。但是会有多分配置文件。也不是太完美;

spring:
  cloud:
    loadbalancer.nacos.enabled: true
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        group: dev
        # 指定集群的名称,每个人都有自己一份
        cluster-name: local

方案二、自定义nacos负载均衡机制

1、自定义nacos负载均衡器

自定义nacos服务加载机制,优先使用与本地同IP的服务。本地找不到则使用同集群,然后在使用其他集群服务

自定义负载均衡代码如下:


import cn.hutool.core.net.NetUtil;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.balancer.NacosBalancer;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Set;
import java.util.stream.Collectors;

/**
 * nacos 本地服务优先负载均衡器
 */
public class NacosLocalFirstLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private static final Logger log = LoggerFactory.getLogger(NacosLocalFirstLoadBalancer.class);

    private final String serviceId;

    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    private final NacosDiscoveryProperties nacosDiscoveryProperties;

    private Set<String> localIps;

    public NacosLocalFirstLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
        // 使用hutool 工具获取本机IP地址
        this.localIps = NetUtil.localIpv4s();
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
                .getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get().next().map(this::getInstanceResponse);
    }

    /**
     * 优先获取与本地IP一致的服务,否则获取同一集群服务
     *
     * @param serviceInstances
     * @return
     */
    private Response<ServiceInstance> getInstanceResponse(
            List<ServiceInstance> serviceInstances) {
        if (serviceInstances.isEmpty()) {
            log.warn("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        }
        // 过滤与本机IP地址一样的服务实例
        if (!CollectionUtils.isEmpty(this.localIps)) {
            for (ServiceInstance instance : serviceInstances) {
                String host = instance.getHost();
                if (this.localIps.contains(host)) {
                    return new DefaultResponse(instance);
                }
            }
        }
        return this.getClusterInstanceResponse(serviceInstances);
    }

    /**
     * 同一集群下优先获取
     *
     * @param serviceInstances
     * @return
     */
    private Response<ServiceInstance> getClusterInstanceResponse(
            List<ServiceInstance> serviceInstances) {
        if (serviceInstances.isEmpty()) {
            log.warn("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        }

        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");
                            return StringUtils.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);
            return new DefaultResponse(instance);
        } catch (Exception e) {
            log.warn("NacosLoadBalancer error", e);
            return null;
        }
    }
}


以上代码大部分来源于 com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer 添加了本地优先过滤规则

2、注册自定义负载均衡器到LoadBalancerClients中

使用自定义负载均衡器覆盖默认配置

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancerClientConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;

/**
 * nacos 负载均衡同IP 同区域有限
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
// 这里引入 nacos 默认客户端配置,否则的话需要添加 配置 spring.cloud.loadbalancer.nacos.enabled = true
@Import(NacosLoadBalancerClientConfiguration.class)
public class NacosLocalFirstLoadBalancerClientConfiguration{
    private static final Logger log = LoggerFactory.getLogger(NacosLocalFirstLoadBalancerClientConfiguration.class);


    /**
     * 本地优先策略
     * @param environment 环境变量
     * @param loadBalancerClientFactory 工厂
     * @param nacosDiscoveryProperties 属性
     * @return ReactorLoadBalancer
     */
    @Bean
    @ConditionalOnProperty(value = "spring.cloud.loadbalancer.local-first", havingValue = "true")
    public ReactorLoadBalancer<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);
        }
        return new NacosLocalFirstLoadBalancer(
                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)
public class LocalFirstLoadBalancerNacosAutoConfiguration {

}

4、如果需要自动装配

resources/META-INF/spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.commmon.LocalFirstLoadBalancerNacosAutoConfiguration

经过上述配置之后,就可以在开发微服务中与同事一起调试代码了,也不会出现调用串了问题。

GitHub 加速计划 / na / nacos
29.83 K
12.75 K
下载
Nacos是由阿里巴巴开源的服务治理中间件,集成了动态服务发现、配置管理和服务元数据管理功能,广泛应用于微服务架构中,简化服务治理过程。
最近提交(Master分支:3 个月前 )
4334cd16 * Support custom client configuration timeout.(#12748) * Add UT.(#12748) 10 天前
b04d2266 14 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐