文章目录

  • 🌕先说痛点
  • 🍁解决思路(使用自定义负载均衡器)
  • 🍁不知道怎么写?(那就看看源码参考下)
  • 🍁动手时间 (模仿源码写一个)
  • 🍁最终(完美解决)



【Nacos】多人本地开发优先调用同ip服务_spring cloud

🌕先说痛点


  • 问题描述
  • 同一台nacos 服务 多个开发人员在本地开发,
  • 就会出现一个问题:也就是你本来想在本地调试你修改的代码,发现服务调到别的同事的服务上去了,导致调试很麻烦
  • 举个例子:有12两个开发人员,分别启动了A1,A2 B1 B2 服务, 然后 A1就有可能调用调B2服务上,我预期的是A1调用在B1 服务上 优先使用同IP服务(本地服务优先调用)

🍁解决思路(使用自定义负载均衡器)


  • 自定义负载均衡器要怎么写,
    简单看下他已有负载均衡器是怎么写的

1️⃣ RandomLoadBalancer :基于随机访问的负载均衡策略
随机地从候选服务实例中选择一个实例来处理请求。

2️⃣ NacosLoadBalancer:基于Nacos权重的负载均衡策略:
根据服务实例的权重来决定请求的分配比例。权重越高的实例将获得更多的请求。

3️⃣ RoundRobinLoadBalancer:基于轮询的负载均衡策略
按顺序轮询每一个实例

🍁不知道怎么写?(那就看看源码参考下)


我这边是随便选了一个: 下面截图是这个类RoundRobinLoadBalancer

【Nacos】多人本地开发优先调用同ip服务_spring cloud_02

🍁动手时间 (模仿源码写一个)


想要修改负载逻辑只需要修改这个两个方法就可以 getInstanceResponse getClusterInstanceResponse

package com.common.star.base.configuration;


import cn.hutool.core.net.NetUtil;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.excel.util.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
 * nacos 自定义负载均衡器
 *
 * @Author: hrd
 * @CreateTime: 2023/7/1 14:30
 * @Description:
 */
@Slf4j
public class MySameIPRoundRobinLoadBalancer  implements ReactorServiceInstanceLoadBalancer {

    final AtomicInteger position;
    final String serviceId;

    ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private final NacosDiscoveryProperties nacosDiscoveryProperties;
    private final Set<String> localIps;

    public MySameIPRoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
        this.localIps = NetUtil.localIpv4s();
        this.position = new AtomicInteger(new Random().nextInt(1000));
    }

    @SuppressWarnings("rawtypes")
    @Override
    // see original
    // https://github.com/Netflix/ocelli/blob/master/ocelli-core/
    // src/main/java/netflix/ocelli/loadbalancer/RoundRobinLoadBalancer.java
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
                .getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next()
                .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
                                                              List<ServiceInstance> serviceInstances) {
        Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances) {
        if (serviceInstances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + serviceId);
            }
            return new EmptyResponse();
        }
        log.info("--------------------------");
        // 过滤与本机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);
            }

            int pos = Math.abs(this.position.incrementAndGet());

            // 同一个集群下轮询
            ServiceInstance instance = instancesToChoose.get(pos % instancesToChoose.size());
            return new DefaultResponse(instance);
        } catch (Exception e) {
            log.warn("NacosLoadBalancer error", e);
            return null;
        }
    }
}

我这里判断了只有本地才走自定义负载均衡器

package com.common.star.base.configuration;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer;
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.core.env.Environment;

/**
 * 本地才走自定义负载均衡
 *
 * @Author: hrd
 * @CreateTime: 2023/7/1 14:50
 * @Description:
 */
@Slf4j
@Configuration(proxyBeanMethods = false)
public class MyLoadBalancerConfig {

    @Value("${spring.profiles.active}")
    private String active;

    /**
     * 本地优先策略
     *
     * @param environment               环境变量
     * @param loadBalancerClientFactory 工厂
     * @param nacosDiscoveryProperties  属性
     * @return ReactorLoadBalancer
     */
    @Bean
    public ReactorLoadBalancer<ServiceInstance> nacosLocalFirstLoadBalancer(Environment environment,
                                                                            LoadBalancerClientFactory loadBalancerClientFactory,
                                                                            NacosDiscoveryProperties nacosDiscoveryProperties) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        ObjectProvider<ServiceInstanceListSupplier> lazyProvider = loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class);
        if ("dev".equals(active)) {
            log.info("Use nacos local first load balancer for {} service", name);
            return new MySameIPRoundRobinLoadBalancer(lazyProvider, name, nacosDiscoveryProperties);
        }
        return new RoundRobinLoadBalancer(lazyProvider, name);
    }
}

把自定义负载均衡器 设置为默认的

package com.common.star.base.configuration;

import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;

/**
 * @Author: hrd
 * @CreateTime: 2023/7/1 16:47
 * @Description:
 */
@LoadBalancerClients(defaultConfiguration = MyLoadBalancerConfig.class)
public class MyLoadBalancerClients {
}

🍁最终(完美解决)


【Nacos】多人本地开发优先调用同ip服务_spring cloud_03


【Nacos】多人本地开发优先调用同ip服务_中间件_04

  • 可以看到最后返回服务就是我调用服务同一ip的