1.前言
本文基于上一节内容继续讲解,上一节内容传送门:Ribbon的介绍和使用(一)
2.Ribbon 支持Nacos权重
Ribbon内置的负载均衡规则是不支持Nacos权重的,这里我们编码写个扩展规则实现权重。
2.1 编写负载均衡规则扩展Ribbon
package com.ding.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;
@Slf4j
public class NacosWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
// 读取配置文件,并初始化NacosWeightedRule
}
@Override
public Server choose(Object o) {
try {
// ribbon入口,BaseLoadBalancer获取get方法
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
//想要请求的微服务的名称
String name = loadBalancer.getName();
// 拿到服务发现的相关api
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// nacos client 自动通过基于权重的负载均衡算法选择一个实例
Instance instance = namingService.selectOneHealthyInstance(name);
log.info("port = {},instance={}",instance.getPort(),instance);
return new NacosServer(instance);
} catch (NacosException e) {
return null;
}
}
}
2.2 编写配置
package ribbonconfiguration;
import com.ding.contentcenter.configuration.NacosWeightedRule;
import com.netflix.loadbalancer.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new NacosWeightedRule();
}
}
2.3 设置端口实例权重
将端口为8888的实例权重设置为0
2.4 请求用户中心
curl --location --request GET 'http://localhost:8082/shares/1'
查看用户中心2个端口实例的响应情况:
结论:因为端口为8888的实例权重为0,而端口为8080的权重为1,因此所有的请求都分配到了端口8080的实例上了,从而实现了基于权重的负载均衡算法。
注意:Nacos client内置了基于权重的负载均衡算法,而Spring Cloud Alibaba还要去整合Ribbon,这里是为了符合Spring Cloud的标准,其子项目Spring Cloud Commons定义了一些标准,而其子项目Spring Cloud Loadbalancer没有权重,因此Spring Cloud Alibaba为了遵循标准整合了Ribbon。
3.扩张Ribbon同一集群优先调用
为了容灾我们往往会将微服务的集群部署在不同的地方。为了提高性能,相同集群下的服务就会优先相互调用,如果当前集群下的服务无法响应时将会自动调用其他地区的服务。
3.1 编写同一集群优先调用规则
package com.ding.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.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Slf4j
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
try {
// 拿到配置中的集群名称 SuZhou
String clusterName = nacosDiscoveryProperties.getClusterName();
// ribbon入口,BaseLoadBalancer获取get方法
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
//想要请求的微服务的名称
String name = loadBalancer.getName();
// 拿到服务发现的相关api,如:com.alibaba.nacos.client.naming.NacosNamingService@3194e0d0
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// 1.找到指定服务的所有实例A
List<Instance> instances = namingService.selectInstances(name,true);
// 2.筛选出实例A与当前配置集群名称SuZhou相同的实例 B
List<Instance> sameClustInstances = instances.stream()
.filter(instance -> Objects.equals(instance.getClusterName(),clusterName))
.collect(Collectors.toList());
// 3.如果B是是空,就用A
List<Instance> instancesToBeChosen = new ArrayList<>();
if(CollectionUtils.isEmpty(sameClustInstances)){
instancesToBeChosen = instances;
log.warn("跨集群的调用,name={},clusterName={},instances={}",
name,
clusterName,
instances);
}else{
instancesToBeChosen = sameClustInstances;
}
// 4.基于权重的负载均衡算法,返回1个实例
Instance instance = ExtendBalancer.getHostByRandomWeightw(instancesToBeChosen);
log.info("选择的实例是: port{},instance{}",instance.getPort(),instance);
return new NacosServer(instance);
} catch (NacosException e) {
log.error("error",e);
return null;
}
}
}
class ExtendBalancer extends Balancer{
public static Instance getHostByRandomWeightw(List<Instance> hosts) {
return getHostByRandomWeight(hosts);
}
}
3.2 编写配置
package ribbonconfiguration;
import com.ding.contentcenter.configuration.NacosSameClusterWeightedRule;
import com.netflix.loadbalancer.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new NacosSameClusterWeightedRule();
}
}
3.3 编写内容中心的配置
cloud:
nacos:
discovery:
server-addr: localhost:8848
# 当前集群:Suzhou
cluster-name: Suzhou
内容中心的当前集群是:Suzhou
3.4 编写用户中心的配置
cloud:
nacos:
discovery:
server-addr: localhost:8848
cluster-name: SuZhou
application:
# 服务名称尽量用-,不用_和特殊字符
name: user-center
server:
port: 8080
用户中心端口为8080的实例所在集群为Suzhou,端口为8888的实例未设置所属集群。
3.5 内容中心多次请求用户中心
curl --location --request GET 'http://localhost:8082/shares/1'
3.6 响应结果
结论:通过以上编码编码实现了:同一集群的服务优先调用,当前集群的服务按照权重优先调用,当当前集群的服务失去响应会按照权重规则调用其他集群的服务。
4.Nacos的Namespace
4.1 新建命名空间
4.2 用户中心配置
cloud:
nacos:
discovery:
server-addr: localhost:8848
cluster-name: SZ
namespace: 831b9597-fb98-4742-aafb-27986c142d2d
application:
# 服务名称尽量用-,不用_和特殊字符
name: user-center
4.3 内容中心配置
cloud:
nacos:
discovery:
server-addr: localhost:8848
# 当前集群:SZ
cluster-name: SZ
application:
# 服务名称尽量用-,不用_和特殊字符
name: content-center
说明:通过namespace可以实现服务之前的隔离,不同命名空间下的服务不可以相互调用。