在微服务架构中,服务通常会部署在不同的集群中。每个集群可能位于不同的物理位置或数据中心,以提高可用性和容错性。同一服务的多个实例可能分布在不同的集群中。在负载均衡调用时,需要减少跨集群的网络延迟和资源开销,提高服务之间的通信效率。还要考虑到不同的服务实例可能具有不同的性能和资源配置,有些实例可能更强大,可以处理更多的请求,而有些可能资源有限。要考虑这些差异,以便更合理地分配负载。

本文就是基于Nacos实现了两个主要特性:同集群优先调用基于权重的负载均衡 实现的自定义算法。

同集群优先调用是指在负载均衡时,优先选择在相同集群中注册的服务实例。这样做的目的是减少跨集群的网络延迟和资源开销,提高服务之间的通信效率。应该通过筛选出属于相同集群的服务实例,然后再进行负载均衡选择,以确保首选选择来自同一集群的实例。

基于权重的负载均衡考虑了服务器配置的差异,以便更合理地分配负载。每个服务实例都分配了一个权重,表示其处理能力的相对大小。在负载均衡过程中,具有较高权重的实例将获得更多的请求,而具有较低权重的实例将获得较少的请求。

这个特性主要用于在多个集群中进行负载均衡和实例选择,以减少跨集群的网络延迟。如果只有一个集群,那么选择集群的问题就不存在了。在这种情况下,只用考虑权重负载均衡。

微服务实例注册进Nacos时自定义元数据(instanceId)

import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration;
import com.alibaba.cloud.nacos.discovery.NacosWatch;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled;
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({SimpleDiscoveryClientAutoConfiguration.class})
@AutoConfigureAfter({NacosDiscoveryAutoConfiguration.class})
public class NacosDiscoveryClientConfig {
    public NacosDiscoveryClientConfig() {
    }

    @Value("${server.port}")
    private String port;

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(
            value = {"spring.cloud.nacos.discovery.watch.enabled"},
            matchIfMissing = true
    )
    public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties) {
        // 在服务注册Nacos时增加元数据 instanceId格式:ip#port#cluster#group@@service
        Map<String, String> metadata = nacosDiscoveryProperties.getMetadata();
        metadata.put("nacos.instanceId", getNacosInstanceId(nacosDiscoveryProperties));
        return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties);
    }

    public String getNacosInstanceId(NacosDiscoveryProperties nacosDiscoveryProperties) {
        StringBuilder builder = new StringBuilder();
        String separator1 = "#";
        String separator2 = "@";
        return builder.append(nacosDiscoveryProperties.getIp())
                .append(separator1)
                .append(port)
                .append(separator1)
                .append(nacosDiscoveryProperties.getClusterName())
                .append(separator1)
                .append(nacosDiscoveryProperties.getGroup())
                .append(separator2)
                .append(separator2)
                .append(nacosDiscoveryProperties.getService())
                .toString();
    }
}

自定义负载均衡策略

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.core.Balancer;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
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.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
// 自定义负载均衡需要实现 ReactorServiceInstanceLoadBalancer 接口
public class NacosSameClusterWeightedRule implements ReactorServiceInstanceLoadBalancer {

    // Nacos配置属性
    @Resource
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    // 当前服务名称
    private String serviceId;

    // 服务实例列表
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceList;

    public NacosSameClusterWeightedRule(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceList, String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceList = serviceInstanceList;
    }


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

    /**
     * 处理所选的服务实例并执行必要的回调
     * @param supplier
     * @param serviceInstances
     * @return
     */
    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {

        Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback)supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
        }
        return serviceInstanceResponse;
    }

    /**
     * 根据自定义的逻辑选择适当的服务实例
     * @param serviceInstances
     * @return
     */
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances) {

        if (CollectionUtils.isEmpty(serviceInstances)) {
            return new EmptyResponse();
        }
        // 获取当前服务所在的集群名称
        String clusterName = nacosDiscoveryProperties.getClusterName();
        // 过滤在同一集群下注册的服务
        List<ServiceInstance> instanceList = serviceInstances.stream().filter(instance -> clusterName.equals(instance.getMetadata().get("nacos.cluster"))).collect(Collectors.toList());
        ServiceInstance sameClusterNameInst = CollectionUtils.isEmpty(instanceList) ? getHostByRandomWeight(serviceInstances) : getHostByRandomWeight(instanceList);
        return new DefaultResponse(sameClusterNameInst);
    }

    /**
     * 基于自定义权重计算选择适当的服务实例
     * @param serviceInstances
     * @return
     */
    private ServiceInstance getHostByRandomWeight(List<ServiceInstance> serviceInstances) {

        List<Instance> instances = Lists.newArrayList();
        Map<String, ServiceInstance> dataMap = Maps.newHashMap();

        serviceInstances.forEach(serviceInstance -> {
            Instance instance = new Instance();
            Map<String, String> metadata = serviceInstance.getMetadata();
            instance.setInstanceId(metadata.get("nacos.instanceId"));
            instance.setWeight(Double.parseDouble(metadata.get("nacos.weight")));
            instance.setClusterName(metadata.get("nacos.cluster"));
            instance.setEphemeral(Boolean.parseBoolean(metadata.get("nacos.ephemeral")));
            instance.setHealthy(Boolean.parseBoolean(metadata.get("nacos.healthy")));
            instance.setPort(serviceInstance.getPort());
            instance.setIp(serviceInstance.getHost());
            instance.setServiceName(serviceInstance.getServiceId());

            instance.setMetadata(metadata);
            instances.add(instance);
            // key为服务ID,值为服务对象
            dataMap.put(instance.getInstanceId(), serviceInstance);
        });

        // 调用Nacos官方提供的负载均衡权重算法
        return dataMap.get(ExtendBalancer.getHostByRandomWeightCopy(instances).getInstanceId());
    }
}

class ExtendBalancer extends Balancer {
    public static Instance getHostByRandomWeightCopy(List<Instance> instances) {
        return getHostByRandomWeight(instances);
    }
}

注入自定义负载均衡算法

@Configuration
public class CustomLoadBalancerConfiguration {
    
    @Bean
    public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new NacosSameClusterWeightedRule(
                loadBalancerClientFactory
                        .getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

启动类

import com.demo.common.ApplicationConst;
import com.demo.config.CustomLoadBalancerConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@LoadBalancerClients(
        @LoadBalancerClient(value = ApplicationConst.PROVIDER_SERVER_NAME, configuration = CustomLoadBalancerConfiguration.class)
)
@EnableFeignClients(basePackages = {"com.demo.remote"})
public class ConsumerServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerServerApplication.class, args);
    }
}