Spring Cloud Alibaba之負(fù)載均衡組件 - Ribbon

[TOC]


負(fù)載均衡

我們都知道在微服務(wù)架構(gòu)中吁朦,微服務(wù)之間總是需要互相調(diào)用,以此來實(shí)現(xiàn)一些組合業(yè)務(wù)的需求加勤。例如組裝訂單詳情數(shù)據(jù)联四,由于訂單詳情里有用戶信息境蜕,所以訂單服務(wù)就得調(diào)用用戶服務(wù)來獲取用戶信息。要實(shí)現(xiàn)遠(yuǎn)程調(diào)用就需要發(fā)送網(wǎng)絡(luò)請(qǐng)求汽馋,而每個(gè)微服務(wù)都可能會(huì)存在有多個(gè)實(shí)例分布在不同的機(jī)器上侮东,那么當(dāng)一個(gè)微服務(wù)調(diào)用另一個(gè)微服務(wù)的時(shí)候就需要將請(qǐng)求均勻的分發(fā)到各個(gè)實(shí)例上圈盔,以此避免某些實(shí)例負(fù)載過高,某些實(shí)例又太空閑悄雅,所以在這種場景必須要有負(fù)載均衡器驱敲。

目前實(shí)現(xiàn)負(fù)載均衡主要的兩種方式:

1、服務(wù)端負(fù)載均衡宽闲;例如最經(jīng)典的使用Nginx做負(fù)載均衡器癌佩。用戶的請(qǐng)求先發(fā)送到Nginx,然后再由Nginx通過配置好的負(fù)載均衡算法將請(qǐng)求分發(fā)到各個(gè)實(shí)例上便锨,由于需要作為一個(gè)服務(wù)部署在服務(wù)端围辙,所以該種方式稱為服務(wù)端負(fù)載均衡。如圖:

image.png

2放案、客戶端側(cè)負(fù)載均衡姚建;之所以稱為客戶端側(cè)負(fù)載均衡,是因?yàn)檫@種負(fù)載均衡方式是由發(fā)送請(qǐng)求的客戶端來實(shí)現(xiàn)的吱殉,也是目前微服務(wù)架構(gòu)中用于均衡服務(wù)之間調(diào)用請(qǐng)求的常用負(fù)載均衡方式掸冤。因?yàn)椴捎眠@種方式的話服務(wù)之間可以直接進(jìn)行調(diào)用,無需再通過一個(gè)專門的負(fù)載均衡器友雳,這樣能夠提高一定的性能以及高可用性稿湿。以微服務(wù)A調(diào)用微服務(wù)B舉例,簡單來說就是微服務(wù)A先通過服務(wù)發(fā)現(xiàn)組件獲取微服務(wù)B所有實(shí)例的調(diào)用地址押赊,然后通過本地實(shí)現(xiàn)的負(fù)載均衡算法選取出其中一個(gè)調(diào)用地址進(jìn)行請(qǐng)求饺藤。如圖:

image.png

我們來通過Spring Cloud提供的DiscoveryClient寫一個(gè)非常簡單的客戶端側(cè)負(fù)載均衡器,借此直觀的了解一下該種負(fù)載均衡器的工作流程流礁,該示例中采用的負(fù)載均衡策略為隨機(jī)涕俗,代碼如下:

package com.zj.node.contentcenter.discovery;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

/**
 * 客戶端側(cè)負(fù)載均衡器
 *
 * @author 01
 * @date 2019-07-26
 **/
public class LoadBalance {

    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * 隨機(jī)獲取目標(biāo)微服務(wù)的請(qǐng)求地址
     *
     * @return 請(qǐng)求地址
     */
    public String randomTakeUri(String serviceId) {
        // 獲取目標(biāo)微服務(wù)的所有實(shí)例的請(qǐng)求地址
        List<String> targetUris = discoveryClient.getInstances(serviceId).stream()
                .map(i -> i.getUri().toString())
                .collect(Collectors.toList());
        // 隨機(jī)獲取列表中的uri
        int i = ThreadLocalRandom.current().nextInt(targetUris.size());

        return targetUris.get(i);
    }
}

使用Ribbon實(shí)現(xiàn)負(fù)載均衡

什么是Ribbon:

  • Ribbon是Netflix開源的客戶端側(cè)負(fù)載均衡器
  • Ribbon內(nèi)置了非常豐富的負(fù)載均衡策略算法

Ribbon雖然是個(gè)主要用于負(fù)載均衡的小組件,但是麻雀雖小五臟俱全神帅,Ribbon還是有許多的接口組件的再姑。如下表:


image.png

Ribbon默認(rèn)內(nèi)置了八種負(fù)載均衡策略,若想自定義負(fù)載均衡策略則實(shí)現(xiàn)上表中提到的IRule接口或AbstractLoadBalancerRule抽象類即可找御。內(nèi)置的負(fù)載均衡策略如下:


image.png
  • 默認(rèn)的策略規(guī)則為ZoneAvoidanceRule

Ribbon主要有兩種使用方式元镀,一是使用Feign,F(xiàn)eign內(nèi)部已經(jīng)整合了Ribbon霎桅,因此如果只是普通使用的話都感知不到Ribbon的存在栖疑;二是配合RestTemplate使用,這種方式則需要添加Ribbon依賴和@LoadBalanced注解哆档。

這里主要演示一下第二種使用方式蔽挠,由于項(xiàng)目中添加的Nacos依賴已包含了Ribbon所以不需要另外添加依賴,首先定義一個(gè)RestTemplate,代碼如下:

package com.zj.node.contentcenter.configuration;

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

/**
 * bean 配置類
 *
 * @author 01
 * @date 2019-07-25
 **/
@Configuration
public class BeanConfig {

    @Bean
    @LoadBalanced  // 加上這個(gè)注解表示使用Ribbon
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

然后使用RestTemplate調(diào)用其他服務(wù)的時(shí)候澳淑,只需要寫服務(wù)名即可比原,不需要再寫ip地址和端口號(hào)。如下示例:

public ShareDTO findById(Integer id) {
    // 獲取分享詳情
    Share share = shareMapper.selectByPrimaryKey(id);
    // 發(fā)布人id
    Integer userId = share.getUserId();
    // 調(diào)用用戶中心獲取用戶信息
    UserDTO userDTO = restTemplate.getForObject(
            "http://user-center/users/{id}",  // 只需要寫服務(wù)名
            UserDTO.class, userId
    );

    ShareDTO shareDTO = objectConvert.toShareDTO(share);
    shareDTO.setWxNickname(userDTO.getWxNickname());

    return shareDTO;
}

如果不太清楚RestTemplate的使用杠巡,可以參考如下文章:


自定義Ribbon負(fù)載均衡配置

在實(shí)際開發(fā)中量窘,我們可能會(huì)遇到默認(rèn)的負(fù)載均衡策略無法滿足需求,從而需要更換其他的負(fù)載均衡策略氢拥。關(guān)于Ribbon負(fù)載均衡的配置方式主要有兩種蚌铜,在代碼中配置或在配置文件中配置。

Ribbon支持細(xì)粒度的配置嫩海,例如我希望微服務(wù)A在調(diào)用微服務(wù)B的時(shí)候采用隨機(jī)的負(fù)載均衡策略冬殃,而在調(diào)用微服務(wù)C的時(shí)候采用默認(rèn)策略,下面我們就來實(shí)現(xiàn)一下這種細(xì)粒度的配置叁怪。

1审葬、首先是通過代碼進(jìn)行配置,編寫一個(gè)配置類用于實(shí)例化指定的負(fù)載均衡策略對(duì)象:

@Configuration
public class RibbonConfig {

    @Bean
    public IRule ribbonRule(){
        // 隨機(jī)的負(fù)載均衡策略對(duì)象
        return new RandomRule();
    }
}

然后再編寫一個(gè)用于配置Ribbon客戶端的配置類奕谭,該配置類的目的是指定在調(diào)用user-center時(shí)采用RibbonConfig里配置的負(fù)載均衡策略涣觉,這樣就可以達(dá)到細(xì)粒度配置的效果:

@Configuration
// 該注解用于自定義Ribbon客戶端配置,這里聲明為屬于user-center的配置
@RibbonClient(name = "user-center", configuration = RibbonConfig.class)
public class UserCenterRibbonConfig {
}

需要注意的是RibbonConfig應(yīng)該定義在主啟動(dòng)類之外血柳,避免被Spring掃描到官册,不然會(huì)產(chǎn)生父子上下文掃描重疊的問題,從而導(dǎo)致各種奇葩的問題难捌。而在Ribbon這里就會(huì)導(dǎo)致該配置類被所有的Ribbon客戶端共享膝宁,即不管調(diào)用user-center還是其他微服務(wù)都會(huì)采用該配置類里定義的負(fù)載均衡策略,這樣就會(huì)變成了一個(gè)全局配置了栖榨,違背了我們需要細(xì)粒度配置的目的昆汹。所以需要將其定義在主啟動(dòng)類之外:


image.png

關(guān)于這個(gè)問題可以參考官方文檔的描述:

https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#_customizing_the_ribbon_client

2明刷、使用配置文件進(jìn)行配置就更簡單了婴栽,不需要寫代碼還不會(huì)有父子上下文掃描重疊的坑,只需在配置文件中增加如下一段配置就可以實(shí)現(xiàn)以上使用代碼配置等價(jià)的效果:

user-center:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

兩種配置方式對(duì)比:


image.png

最佳實(shí)踐總結(jié):

  • 盡量使用配置文件配置辈末,配置文件滿足不了需求的情況下再考慮使用代碼配置
  • 在同一個(gè)微服務(wù)內(nèi)盡量保持單一性愚争,例如統(tǒng)一使用配置文件配置,盡量不要兩種方式混用挤聘,以免增加定位問題的復(fù)雜度

以上介紹的是細(xì)粒度地針對(duì)某個(gè)特定Ribbon客戶端的配置轰枝,下面我們?cè)傺菔疽幌氯绾螌?shí)現(xiàn)全局配置。很簡單组去,只需要把注解改為@RibbonClients即可鞍陨,代碼如下:

@Configuration
// 該注解用于全局配置
@RibbonClients(defaultConfiguration = RibbonConfig.class)
public class GlobalRibbonConfig {
}

Ribbon默認(rèn)是懶加載的,所以在第一次發(fā)生請(qǐng)求的時(shí)候會(huì)顯得比較慢,我們可以通過在配置文件中添加如下配置開啟饑餓加載:

ribbon:
  eager-load:
    enabled: true
    # 為哪些客戶端開啟饑餓加載诚撵,多個(gè)客戶端使用逗號(hào)分隔(非必須)
    clients: user-center

支持Nacos權(quán)重

以上小節(jié)基本介紹完了負(fù)載均衡及Ribbon的基礎(chǔ)使用缭裆,接下來的內(nèi)容需要配合Nacos,若沒有了解過Nacos的話可以參考以下文章:

在Nacos Server的控制臺(tái)頁面可以編輯每個(gè)微服務(wù)實(shí)例的權(quán)重寿烟,服務(wù)列表 -> 詳情 -> 編輯澈驼;默認(rèn)權(quán)重都為1,權(quán)重值越大就越優(yōu)先被調(diào)用:


image.png

權(quán)重在很多場景下非常有用筛武,例如一個(gè)微服務(wù)有很多的實(shí)例缝其,它們被部署在不同配置的機(jī)器上,這時(shí)候就可以將配置較差的機(jī)器上所部署的實(shí)例權(quán)重設(shè)置得比較低徘六,而部署在配置較好的機(jī)器上的實(shí)例權(quán)重設(shè)置得高一些内边,這樣就可以將較大一部分的請(qǐng)求都分發(fā)到性能較高的機(jī)器上。

但是Ribbon內(nèi)置的負(fù)載均衡策略都不支持Nacos的權(quán)重待锈,所以我們就需要自定義實(shí)現(xiàn)一個(gè)支持Nacos權(quán)重配置的負(fù)載均衡策略假残。好在Nacos Client已經(jīng)內(nèi)置了負(fù)載均衡的能力,所以實(shí)現(xiàn)起來也比較簡單炉擅,代碼如下:

package com.zj.node.contentcenter.configuration;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties;
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;

/**
 * 支持Nacos權(quán)重配置的負(fù)載均衡策略
 *
 * @author 01
 * @date 2019-07-27
 **/
@Slf4j
public class NacosWeightedRule extends AbstractLoadBalancerRule {

    @Autowired
    private  NacosDiscoveryProperties discoveryProperties;

    /**
     * 讀取配置文件辉懒,并初始化NacosWeightedRule
     *
     * @param iClientConfig iClientConfig
     */
    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
        // do nothing
    }

    @Override
    public Server choose(Object key) {
        BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
        log.debug("lb = {}", loadBalancer);

        // 需要請(qǐng)求的微服務(wù)名稱
        String name = loadBalancer.getName();
        // 獲取服務(wù)發(fā)現(xiàn)的相關(guān)API
        NamingService namingService = discoveryProperties.namingServiceInstance();

        try {
            // 調(diào)用該方法時(shí)nacos client會(huì)自動(dòng)通過基于權(quán)重的負(fù)載均衡算法選取一個(gè)實(shí)例
            Instance instance = namingService.selectOneHealthyInstance(name);
            log.info("選擇的實(shí)例是:instance = {}", instance);

            return new NacosServer(instance);
        } catch (NacosException e) {
            return null;
        }
    }
}

然后在配置文件中配置一下就可以使用該負(fù)載均衡策略了:

user-center:
  ribbon:
    NFLoadBalancerRuleClassName: com.zj.node.contentcenter.configuration.NacosWeightedRule

思考:既然Nacos Client已經(jīng)有負(fù)載均衡的能力,Spring Cloud Alibaba為什么還要去整合Ribbon呢谍失?

個(gè)人認(rèn)為眶俩,這主要是為了符合Spring Cloud標(biāo)準(zhǔn)。Spring Cloud Commons有個(gè)子項(xiàng)目 spring-cloud-loadbalancer 快鱼,該項(xiàng)目制定了標(biāo)準(zhǔn)颠印,用來適配各種客戶端負(fù)載均衡器(雖然目前實(shí)現(xiàn)只有Ribbon,但Hoxton就會(huì)有替代的實(shí)現(xiàn)了)抹竹。

Spring Cloud Alibaba遵循了這一標(biāo)準(zhǔn)线罕,所以整合了Ribbon,而沒有去使用Nacos Client提供的負(fù)載均衡能力窃判。

其他實(shí)現(xiàn)方式的參考文章:


同一集群優(yōu)先調(diào)用

Spring Cloud Alibaba之服務(wù)發(fā)現(xiàn)組件 - Nacos一文中已經(jīng)介紹過集群的概念以及作用钞楼,這里就不再贅述,加上上一小節(jié)中已經(jīng)介紹過如何自定義負(fù)載均衡策略了袄琳,所以這里不再啰嗦而是直接上代碼询件,實(shí)現(xiàn)代碼如下:

package com.zj.node.contentcenter.configuration;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.core.Balancer;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties;
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 實(shí)現(xiàn)同一集群優(yōu)先調(diào)用并基于隨機(jī)權(quán)重的負(fù)載均衡策略
 *
 * @author 01
 * @date 2019-07-27
 **/
@Slf4j
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties discoveryProperties;

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
        // do nothing
    }

    @Override
    public Server choose(Object key) {
        // 獲取配置文件中所配置的集群名稱
        String clusterName = discoveryProperties.getClusterName();
        BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
        // 獲取需要請(qǐng)求的微服務(wù)名稱
        String serviceId = loadBalancer.getName();
        // 獲取服務(wù)發(fā)現(xiàn)的相關(guān)API
        NamingService namingService = discoveryProperties.namingServiceInstance();

        try {
            // 獲取該微服務(wù)的所有健康實(shí)例
            List<Instance> instances = namingService.selectInstances(serviceId, true);
            // 過濾出相同集群下的所有實(shí)例
            List<Instance> sameClusterInstances = instances.stream()
                    .filter(i -> Objects.equals(i.getClusterName(), clusterName))
                    .collect(Collectors.toList());

            // 相同集群下沒有實(shí)例則需要使用其他集群下的實(shí)例
            List<Instance> instancesToBeChosen;
            if (CollectionUtils.isEmpty(sameClusterInstances)) {
                instancesToBeChosen = instances;
                log.warn("發(fā)生跨集群調(diào)用,name = {}, clusterName = {}, instances = {}",
                        serviceId, clusterName, instances);
            } else {
                instancesToBeChosen = sameClusterInstances;
            }

            // 基于隨機(jī)權(quán)重的負(fù)載均衡算法唆樊,從實(shí)例列表中選取一個(gè)實(shí)例
            Instance instance = ExtendBalancer.getHost(instancesToBeChosen);
            log.info("選擇的實(shí)例是:port = {}, instance = {}", instance.getPort(), instance);

            return new NacosServer(instance);
        } catch (NacosException e) {
            log.error("獲取實(shí)例發(fā)生異常", e);
            return null;
        }
    }
}

class ExtendBalancer extends Balancer {

    /**
     * 由于Balancer類里的getHostByRandomWeight方法是protected的宛琅,
     * 所以通過這種繼承的方式來實(shí)現(xiàn)調(diào)用,該方法基于隨機(jī)權(quán)重的負(fù)載均衡算法逗旁,選取一個(gè)實(shí)例
     */
    static Instance getHost(List<Instance> hosts) {
        return getHostByRandomWeight(hosts);
    }
}

同樣的嘿辟,想要使用該負(fù)載均衡策略的話,在配置文件中配置一下即可:

user-center:
  ribbon:
    NFLoadBalancerRuleClassName: com.zj.node.contentcenter.configuration.NacosSameClusterWeightedRule

基于元數(shù)據(jù)的版本控制

在以上兩個(gè)小節(jié)我們實(shí)現(xiàn)了基于Nacos權(quán)重的負(fù)載均衡策略及同一集群下優(yōu)先調(diào)用的負(fù)載均衡策略,但在實(shí)際項(xiàng)目中红伦,可能會(huì)面臨多版本共存的問題介陶,即一個(gè)微服務(wù)擁有不同版本的實(shí)例,并且這些不同版本的實(shí)例之間可能是互不兼容的色建。例如微服務(wù)A的v1版本實(shí)例無法調(diào)用微服務(wù)B的v2版本實(shí)例哺呜,只能夠調(diào)用微服務(wù)B的v1版本實(shí)例。

而Nacos中的元數(shù)據(jù)就比較適合解決這種版本控制的問題箕戳,至于元數(shù)據(jù)的概念及配置方式已經(jīng)在Spring Cloud Alibaba之服務(wù)發(fā)現(xiàn)組件 - Nacos一文中介紹過某残,這里主要介紹一下如何通過Ribbon去實(shí)現(xiàn)基于元數(shù)據(jù)的版本控制。

舉個(gè)例子陵吸,線上有兩個(gè)微服務(wù)玻墅,一個(gè)作為服務(wù)提供者一個(gè)作為服務(wù)消費(fèi)者,它們都有不同版本的實(shí)例壮虫,如下:

  • 服務(wù)提供者有兩個(gè)版本:v1澳厢、v2
  • 服務(wù)消費(fèi)者也有兩個(gè)版本:v1、v2

v1和v2是不兼容的囚似。服務(wù)消費(fèi)者v1只能調(diào)用服務(wù)提供者v1剩拢;消費(fèi)者v2只能調(diào)用提供者v2。如何實(shí)現(xiàn)呢饶唤?下面我們來圍繞該場景徐伐,實(shí)現(xiàn)微服務(wù)之間的版本控制。

綜上募狂,我們需要實(shí)現(xiàn)的主要有兩點(diǎn):

  • 優(yōu)先選擇同集群下办素,符合metadata的實(shí)例
  • 如果同集群下沒有符合metadata的實(shí)例,就選擇其他集群下符合metadata的實(shí)例

首先我們得在配置文件中配置元數(shù)據(jù)祸穷,元數(shù)據(jù)就是一堆的描述信息性穿,以k - v形式進(jìn)行配置,如下:

spring:
  cloud:
    nacos:
      discovery:
        # 指定nacos server的地址
        server-addr: 127.0.0.1:8848
        # 配置元數(shù)據(jù)
        metadata: 
          # 當(dāng)前實(shí)例版本
          version: v1
          # 允許調(diào)用的提供者實(shí)例的版本
          target-version: v1

然后就可以寫代碼了雷滚,和之前一樣需曾,也是通過負(fù)載均衡策略實(shí)現(xiàn),具體代碼如下:

package com.zj.node.contentcenter.configuration;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import com.alibaba.nacos.client.utils.StringUtils;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties;
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 基于元數(shù)據(jù)的版本控制負(fù)載均衡策略
 *
 * @author 01
 * @date 2019-07-27
 **/
@Slf4j
public class NacosFinalRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties discoveryProperties;

    private static final String TARGET_VERSION = "target-version";
    private static final String VERSION = "version";

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
        // do nothing
    }

    @Override
    public Server choose(Object key) {
        // 獲取配置文件中所配置的集群名稱
        String clusterName = discoveryProperties.getClusterName();
        // 獲取配置文件中所配置的元數(shù)據(jù)
        String targetVersion = discoveryProperties.getMetadata().get(TARGET_VERSION);

        DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
        // 需要請(qǐng)求的微服務(wù)名稱
        String serviceId = loadBalancer.getName();
        // 獲取該微服務(wù)的所有健康實(shí)例
        List<Instance> instances = getInstances(serviceId);

        List<Instance> metadataMatchInstances = instances;
        // 如果配置了版本映射揭措,那么代表只調(diào)用元數(shù)據(jù)匹配的實(shí)例
        if (StringUtils.isNotBlank(targetVersion)) {
            // 過濾與版本元數(shù)據(jù)相匹配的實(shí)例胯舷,以實(shí)現(xiàn)版本控制
            metadataMatchInstances = filter(instances,
                    i -> Objects.equals(targetVersion, i.getMetadata().get(VERSION)));

            if (CollectionUtils.isEmpty(metadataMatchInstances)) {
                log.warn("未找到元數(shù)據(jù)匹配的目標(biāo)實(shí)例!請(qǐng)檢查配置绊含。targetVersion = {}, instance = {}",
                        targetVersion, instances);
                return null;
            }
        }

        List<Instance> clusterMetadataMatchInstances = metadataMatchInstances;
        // 如果配置了集群名稱,需篩選同集群下元數(shù)據(jù)匹配的實(shí)例
        if (StringUtils.isNotBlank(clusterName)) {
            // 過濾出相同集群下的所有實(shí)例
            clusterMetadataMatchInstances = filter(metadataMatchInstances,
                    i -> Objects.equals(clusterName, i.getClusterName()));

            if (CollectionUtils.isEmpty(clusterMetadataMatchInstances)) {
                clusterMetadataMatchInstances = metadataMatchInstances;
                log.warn("發(fā)生跨集群調(diào)用炊汹。clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, targetVersion, clusterMetadataMatchInstances);
            }
        }

        // 基于隨機(jī)權(quán)重的負(fù)載均衡算法躬充,選取其中一個(gè)實(shí)例
        Instance instance = ExtendBalancer.getHost(clusterMetadataMatchInstances);

        return new NacosServer(instance);
    }

    /**
     * 通過過濾規(guī)則過濾實(shí)例列表
     */
    private List<Instance> filter(List<Instance> instances, Predicate<Instance> predicate) {
        return instances.stream()
                .filter(predicate)
                .collect(Collectors.toList());
    }

    private List<Instance> getInstances(String serviceId) {
        // 獲取服務(wù)發(fā)現(xiàn)的相關(guān)API
        NamingService namingService = discoveryProperties.namingServiceInstance();
        try {
            // 獲取該微服務(wù)的所有健康實(shí)例
            return namingService.selectInstances(serviceId, true);
        } catch (NacosException e) {
            log.error("發(fā)生異常", e);
            return Collections.emptyList();
        }
    }
}

class ExtendBalancer extends Balancer {
    /**
     * 由于Balancer類里的getHostByRandomWeight方法是protected的,
     * 所以通過這種繼承的方式來實(shí)現(xiàn)調(diào)用,該方法基于隨機(jī)權(quán)重的負(fù)載均衡算法充甚,選取一個(gè)實(shí)例
     */
    static Instance getHost(List<Instance> hosts) {
        return getHostByRandomWeight(hosts);
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末以政,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子伴找,更是在濱河造成了極大的恐慌盈蛮,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件技矮,死亡現(xiàn)場離奇詭異抖誉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)衰倦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門袒炉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人樊零,你說我怎么就攤上這事我磁。” “怎么了驻襟?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵夺艰,是天一觀的道長。 經(jīng)常有香客問我沉衣,道長劲适,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任厢蒜,我火速辦了婚禮霞势,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘斑鸦。我一直安慰自己愕贡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布巷屿。 她就那樣靜靜地躺著固以,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嘱巾。 梳的紋絲不亂的頭發(fā)上憨琳,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音旬昭,去河邊找鬼篙螟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛问拘,可吹牛的內(nèi)容都是我干的遍略。 我是一名探鬼主播惧所,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绪杏!你這毒婦竟也來了下愈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤蕾久,失蹤者是張志新(化名)和其女友劉穎势似,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體僧著,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡履因,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霹抛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搓逾。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖杯拐,靈堂內(nèi)的尸體忽然破棺而出霞篡,到底是詐尸還是另有隱情,我是刑警寧澤端逼,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布朗兵,位于F島的核電站,受9級(jí)特大地震影響顶滩,放射性物質(zhì)發(fā)生泄漏余掖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一礁鲁、第九天 我趴在偏房一處隱蔽的房頂上張望盐欺。 院中可真熱鬧,春花似錦仅醇、人聲如沸冗美。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粉洼。三九已至,卻和暖如春叶摄,著一層夾襖步出監(jiān)牢的瞬間属韧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工蛤吓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宵喂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓柱衔,卻偏偏與公主長得像樊破,于是被迫代替她去往敵國和親愉棱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子唆铐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容