Spring Cloud Nacos 2021使用LoadBalancer + Nacos做負(fù)載均衡

一、微服務(wù)之間的調(diào)用方式

Spring Cloud中微服務(wù)調(diào)用默認(rèn)是用http請(qǐng)求,主要通過一下三種 API

  • RestTemplate:同步 http API
  • WebClient:異步響應(yīng)式 http API
  • 第三方封裝:如 openfeign

二、LoadBalancer替代了Ribbon

Ribbon目前已經(jīng)停止維護(hù),新版SpringCloud(2021.x.x)LoadBalancer替代了Ribbon。Spring Cloud全家桶在Spring Cloud Commons項(xiàng)目中,添加了Spring cloud Loadbalancer作為新的負(fù)載均衡器,并且做了兼容

Nacos 2021版本已經(jīng)沒有自帶ribbon的整合,所以無法通過修改Ribbon負(fù)載均衡的模式來實(shí)現(xiàn)nacos提供的負(fù)載均衡模式,需要引入另一個(gè)支持的jar包loadbalancer。

三、使用nacos 2021.1版本實(shí)現(xiàn)負(fù)載均衡

nacos最新版 2021.1版本中

<dependency>
 <groupId>com.alibaba.cloud</groupId>
 <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 <version>2021.1</version>
</dependency>

這個(gè)包已經(jīng)不提供ribbon支持,需要引入另一個(gè)jar包

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

使用這個(gè)包來實(shí)現(xiàn)負(fù)載均衡,具體實(shí)現(xiàn)方式和先前版本一樣。

@Bean
@LoadBalanced //負(fù)載均衡注解
public RestTemplate restTemplate(){
    return new RestTemplate();
}

注冊(cè)bean的同時(shí),添加LoadBalanced負(fù)載均衡注解,到這一步為止,可以實(shí)現(xiàn)基本的負(fù)載均衡功能,負(fù)載均衡默認(rèn)配置為輪詢配置

四、配置負(fù)載均衡策略

4.1 Ribbon載均衡策略

Ribbon有多種負(fù)載均衡策略

  • 隨機(jī) RandomRule
  • 輪詢 RoundRobinRule
  • 重試 RetryRule
  • 最低并發(fā) BestAvailableRule
  • 可用過濾 AvailabilityFilteringRule
  • 響應(yīng)時(shí)間加權(quán)重 ResponseTimeWeightedRule
  • 區(qū)域權(quán)重 ZoneAvoidanceRule
4.2 LoadBalancer載均衡策略

LoadBalancer貌似只提供了兩種負(fù)載均衡器,不指定的時(shí)候默認(rèn)用的是輪詢

  • RandomLoadBalancer 隨機(jī)
  • RoundRobinLoadBalancer 輪詢

五、自定義loadbalancer負(fù)載均衡

5.1 實(shí)現(xiàn)loadbalancer自定義負(fù)載均衡模式進(jìn)行注入

一共有兩種寫法,可以直接在Spring配置文件中注入Bean,但是這樣的話,在 LoadBalancerClients 提供的類里需要寫為Spring的配置文件類

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
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.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.client.RestTemplate;

@Configuration
@LoadBalancerClients(defaultConfiguration = {SpringBeanConfiguration.class})
public class SpringBeanConfiguration {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                                            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

        return new NacosSameClusterWeightedRule(loadBalancerClientFactory
                .getLazyProvider(name, ServiceInstanceListSupplier.class),
                name);
    }
}

或者新建一個(gè)類

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.core.env.Environment;

//這里不用寫Configuration
public class NacosSameClusterConfiguration{
    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                                            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

        // 返回內(nèi)容為自定義負(fù)載均衡的配置類
        return new NacosSameClusterWeightedRule(loadBalancerClientFactory
                .getLazyProvider(name, ServiceInstanceListSupplier.class),
                name);
    }
}


import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
// 在這里配置我們自定義的LoadBalancer策略,注:這里的類為注入Bean的類,而非負(fù)載均衡的實(shí)現(xiàn)類
@LoadBalancerClients(defaultConfiguration = {NacosSameClusterConfiguration.class})
public class SpringBeanConfiguration {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}
5.2 實(shí)現(xiàn)自定義的負(fù)載均衡,基于nacos的同集群優(yōu)先調(diào)用以及基于權(quán)重調(diào)用思想實(shí)現(xiàn)
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.utils.StringUtils;
import com.alibaba.nacos.client.naming.core.Balancer;
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 reactor.core.publisher.Mono;

import javax.annotation.Resource;

@Slf4j
// 自定義負(fù)載均衡實(shí)現(xiàn)需要實(shí)現(xiàn) ReactorServiceInstanceLoadBalancer 接口 以及重寫choose方法
public class NacosSameClusterWeightedRule implements ReactorServiceInstanceLoadBalancer {

    // 注入當(dāng)前服務(wù)的nacos的配置信息
    @Resource
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    // loadbalancer 提供的訪問當(dāng)前服務(wù)的名稱
    final String serviceId;

    // loadbalancer 提供的訪問的服務(wù)列表
    ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

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

/**
     * 服務(wù)器調(diào)用負(fù)載均衡時(shí)調(diào)的放啊
     * 此處代碼內(nèi)容與 RandomLoadBalancer 一致
     */
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next().map((serviceInstances) -> {
            return this.processInstanceResponse(supplier, serviceInstances);
        });
    }

    /**
     * 對(duì)負(fù)載均衡的服務(wù)進(jìn)行篩選的方法
     * 此處代碼內(nèi)容與 RandomLoadBalancer 一致
     */
    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
        Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
            ((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());
        }

        return serviceInstanceResponse;
    }

    /**
     * 對(duì)負(fù)載均衡的服務(wù)進(jìn)行篩選的方法
     * 自定義
     * 此處的 instances 實(shí)例列表  只會(huì)提供健康的實(shí)例  所以不需要擔(dān)心如果實(shí)例無法訪問的情況
     */
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            return new EmptyResponse();
        }
        // 獲取當(dāng)前服務(wù)所在的集群名稱
        String currentClusterName = nacosDiscoveryProperties.getClusterName();
        // 過濾在同一集群下注冊(cè)的服務(wù) 根據(jù)集群名稱篩選的集合
        List<ServiceInstance> sameClusterNameInstList  = instances.stream().filter(i-> StringUtils.equals(i.getMetadata().get("nacos.cluster"),currentClusterName)).collect(Collectors.toList());
        ServiceInstance sameClusterNameInst;
        if (sameClusterNameInstList.isEmpty()) {
            // 如果為空,則根據(jù)權(quán)重直接過濾所有服務(wù)列表
            sameClusterNameInst = getHostByRandomWeight(instances);
        } else {
            // 如果不為空,則根據(jù)權(quán)重直接過濾所在集群下的服務(wù)列表
            sameClusterNameInst = getHostByRandomWeight(sameClusterNameInstList);
        }
        return new DefaultResponse(sameClusterNameInst);
    }

    private ServiceInstance getHostByRandomWeight(List<ServiceInstance> sameClusterNameInstList){

        List<Instance> list = new ArrayList<>();
        Map<String,ServiceInstance> dataMap = new HashMap<>();
        // 此處將 ServiceInstance 轉(zhuǎn)化為 Instance 是為了接下來調(diào)用nacos中的權(quán)重算法,由于入?yún)⒉煌?,所以需要轉(zhuǎn)換,此處建議打斷電進(jìn)行參數(shù)調(diào)試,以下是我目前為止所用到的參數(shù),轉(zhuǎn)化為map是為了最終方便獲取取值到的服務(wù)對(duì)象
        sameClusterNameInstList.forEach(i->{
            Instance ins = new Instance();
            Map<String, String> metadata = i.getMetadata();

            ins.setInstanceId(metadata.get("nacos.instanceId"));
            ins.setWeight(new BigDecimal(metadata.get("nacos.weight")).doubleValue());
            ins.setClusterName(metadata.get("nacos.cluster"));
            ins.setEphemeral(Boolean.parseBoolean(metadata.get("nacos.ephemeral")));
            ins.setHealthy(Boolean.parseBoolean(metadata.get("nacos.healthy")));
            ins.setPort(i.getPort());
            ins.setIp(i.getHost());
            ins.setServiceName(i.getServiceId());

            ins.setMetadata(metadata);

            list.add(ins);
            // key為服務(wù)ID,值為服務(wù)對(duì)象
            dataMap.put(metadata.get("nacos.instanceId"),i);
        });
        // 調(diào)用nacos官方提供的負(fù)載均衡權(quán)重算法
        Instance hostByRandomWeightCopy = ExtendBalancer.getHostByRandomWeightCopy(list);

        // 根據(jù)最終ID獲取需要返回的實(shí)例對(duì)象
        return dataMap.get(hostByRandomWeightCopy.getInstanceId());
    }

}

class ExtendBalancer extends Balancer {
    /**
     * 根據(jù)權(quán)重選擇隨機(jī)選擇一個(gè)
     */
    public static Instance getHostByRandomWeightCopy(List<Instance> hosts) {
        return getHostByRandomWeight(hosts);
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容