[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ù)載均衡。如圖:
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)求饺藤。如圖:
我們來通過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還是有許多的接口組件的再姑。如下表:
Ribbon默認(rèn)內(nèi)置了八種負(fù)載均衡策略,若想自定義負(fù)載均衡策略則實(shí)現(xiàn)上表中提到的IRule接口或AbstractLoadBalancerRule抽象類即可找御。內(nèi)置的負(fù)載均衡策略如下:
- 默認(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)類之外:
關(guān)于這個(gè)問題可以參考官方文檔的描述:
2明刷、使用配置文件進(jìn)行配置就更簡單了婴栽,不需要寫代碼還不會(huì)有父子上下文掃描重疊的坑,只需在配置文件中增加如下一段配置就可以實(shí)現(xiàn)以上使用代碼配置等價(jià)的效果:
user-center:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
兩種配置方式對(duì)比:
最佳實(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)用:
權(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);
}
}