一、微服務(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);
}
}