Ribbon的核心接口
参考:org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
- IClientConfig:Ribbon的客户端配置,默认采用DefaultClientConfigImpl实现。
- IRule:Ribbon的负载均衡策略,默认采用ZoneAvoidanceRule实现,该策略能够在多区域环境下选出最佳区域的实例进行访问。
- IPing:Ribbon的实例检查策略,默认采用DummyPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认认为所有服务实例都是可用的。
- ServerList:服务实例清单的维护机制,默认采用ConfigurationBasedServerList实现。
- ServerListFilter:服务实例清单过滤机制,默认采ZonePreferenceServerListFilter,该策略能够优先过滤出与请求方处于同区域的服务实例。
- ILoadBalancer:负载均衡器,默认采用ZoneAwareLoadBalancer实现,它具备了区域感知的能力。
Ribbon负载均衡策略
- RandomRule:随机选择一个Server。
- RetryRule:对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server。
- RoundRobinRule:轮询选择,轮询index,选择index对应位置的Server。
- AvailabilityFilteringRule:过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就是检查status里记录的各个Server的运行状态。
- BestAvailableRule:选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。
- WeightedResponseTimeRule:根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低。
- ZoneAvoidanceRule:默认的负载均衡策略,即复合判断Server所在区域的性能和Server的可用性选择Server,在没有区域的环境下,类似于轮询
- NacosRule: 优先调用同一集群的实例,基于随机权重
修改默认负载均衡策略
全局配置
全局配置:所有调用的微服务一律使用指定的负载均衡策略,只需要向容器中注入IRule实例即可。
package com.morris.user.config;
import com.alibaba.cloud.nacos.ribbon.NacosRule;
import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RibbonConfig {
@Bean
public IRule ribbonRule() {
// 指定使用Nacos提供的负载均衡策略(优先调用同一集群的实例,基于随机权重)
return new NacosRule();
}
}
局部配置
局部配置:可以在配置文件中调用指定微服务时,使用对应的负载均衡策略。
# 被调用的微服务名
order-service:
ribbon:
# 指定使用Nacos提供的负载均衡策略(优先调用同一集群的实例,基于随机&权重)
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
自定义负载均衡策略
通过实现IRule接口可以自定义负载策略,主要的选择服务逻辑在choose方法中。
实现基于Nacos权重的负载均衡策略:
package com.morris.user.config;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
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.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
@Slf4j
public class NacosRandomWithWeightRule extends AbstractLoadBalancerRule {
@Resource
private NacosServiceManager nacosServiceManager;
@Resource
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public Server choose(Object key) {
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String serviceName = loadBalancer.getName();
NamingService namingService = nacosServiceManager.getNamingService(nacosDiscoveryProperties.getNacosProperties());
try {
//nacos基于权重的算法
Instance instance = namingService.selectOneHealthyInstance(serviceName);
return new NacosServer(instance);
} catch (NacosException e) {
log.error("获取服务实例异常:{}", e.getMessage());
e.printStackTrace();
}
return null;
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
可以将NacosRandomWithWeightRule按照上面的全局配置或者局部配置。
饥饿加载
在进行服务调用的时候,如果网络情况不好,第一次调用会超时。Ribbon默认懒加载,意味着只有在发起调用的时候才会创建客户端。
应用启动后第一次请求会有2s左右的延时,如果此时有大量请求进来就会抛出大量异常。
2023-07-21 16:50:37.718 INFO 20488 --- [nio-8030-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-07-21 16:50:37.718 INFO 20488 --- [nio-8030-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2023-07-21 16:50:37.733 INFO 20488 --- [nio-8030-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 15 ms
2023-07-21 16:50:39.332 INFO 20488 --- [nio-8030-exec-1] c.netflix.loadbalancer.BaseLoadBalancer : Client: order-service instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=order-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2023-07-21 16:50:39.346 INFO 20488 --- [nio-8030-exec-1] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2023-07-21 16:50:39.534 INFO 20488 --- [nio-8030-exec-1] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client order-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=order-service,current list of Servers=[2.0.0.1:8020],Load balancer stats=Zone stats: {unknown=[Zone:unknown; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:2.0.0.1:8020; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:com.alibaba.cloud.nacos.ribbon.NacosServerList@fb090e
开启饥饿加载,解决第一次调用慢的问题:
ribbon:
eager-load:
enabled: true
clients: order-service
参数说明:
- ribbon.eager-load.enabled:开启ribbon的饥饿加载模式
- ribbon.eager-load.clients:指定需要饥饿加载的服务名,也就是你需要调用的服务,如果有多个服务,则用逗号隔开
看下效果,在启动过程中就已经初始化连接了:
2023-07-21 16:54:18.480 INFO 22040 --- [ main] com.morris.user.UserServiceApplication : Started UserServiceApplication in 6.846 seconds (JVM running for 8.265)
2023-07-21 16:54:19.254 INFO 22040 --- [ main] c.netflix.loadbalancer.BaseLoadBalancer : Client: order-service instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=order-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2023-07-21 16:54:19.260 INFO 22040 --- [ main] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2023-07-21 16:54:19.286 INFO 22040 --- [ main] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client order-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=order-service,current list of Servers=[2.0.0.1:8020],Load balancer stats=Zone stats: {unknown=[Zone:unknown; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:2.0.0.1:8020; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:com.alibaba.cloud.nacos.ribbon.NacosServerList@432469
NacosRule源码分析
com.alibaba.cloud.nacos.ribbon.NacosRule#choose
@Override
public Server choose(Object key) {
try {
String clusterName = this.nacosDiscoveryProperties.getClusterName();
String group = this.nacosDiscoveryProperties.getGroup();
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String name = loadBalancer.getName();
// 获取NamingService,在我们的代码中也可以这么使用
NamingService namingService = nacosServiceManager
.getNamingService(nacosDiscoveryProperties.getNacosProperties());
// 筛选出同一个group的实例,不同group之间不能通讯
List<Instance> instances = namingService.selectInstances(name, group, true);
if (CollectionUtils.isEmpty(instances)) {
LOGGER.warn("no instance in service {}", name);
return null;
}
List<Instance> instancesToChoose = instances;
if (StringUtils.isNotBlank(clusterName)) {
// 找出同一个集群的节点
List<Instance> sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(clusterName,
instance.getClusterName()))
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToChoose = sameClusterInstances;
}
else {
LOGGER.warn(
"A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}",
name, clusterName, instances);
}
}
// 带权重的随机选择一个节点
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
return new NacosServer(instance);
}
catch (Exception e) {
LOGGER.warn("NacosRule error", e);
return null;
}
}
NacosRule是AlibabaNacos自己实现的一个负载均衡策略,可以在nacos平台中根据自定义权重进行访问。
基于Nacos元数据的版本控制自定义负载均衡
实际项目,我们可能还会有这样的需求:
一个微服务在线上可能多版本共存,且多个版本的微服务并不兼容。使用Nacos的自定义元数据,可以实现微服务的版本控制。
配置文件的格式: spring.cloud.nacos.discovery.metadata.{key}={value}文章来源:https://www.toymoban.com/news/detail-613769.html
当前配置的版本: spring.cloud.nacos.discovery.metadata.version=V1文章来源地址https://www.toymoban.com/news/detail-613769.html
package com.morris.user.config;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.ribbon.ExtendBalancer;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
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.apache.commons.lang.StringUtils;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
public class ClusterMetaDataRibbonRule extends AbstractLoadBalancerRule {
@Resource
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Resource
private NacosServiceManager nacosServiceManager;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
log.info("-------key: {}", o);
// 获取当前服务的集群名称
String currentClusterName = nacosDiscoveryProperties.getClusterName();
// 获取当前版本
String currentVersion = nacosDiscoveryProperties.getMetadata().get("version");
// 获取被调用的服务的名称
BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) getLoadBalancer();
String serviceName = baseLoadBalancer.getName();
// 获取nacos clinet的服务注册发现组件的api
NamingService namingService = nacosServiceManager.getNamingService(nacosDiscoveryProperties.getNacosProperties());
try {
// 获取所有被调用服务
List<Instance> allInstances = namingService.getAllInstances(serviceName);
// 过滤出相同版本且相同集群下的所有服务
List<Instance> sameVersionAndClusterInstances = allInstances.stream()
.filter(x -> StringUtils.equalsIgnoreCase(x.getMetadata().get("version"), currentVersion)
&& StringUtils.equalsIgnoreCase(x.getClusterName(), currentClusterName)
).collect(Collectors.toList());
Instance chooseInstance;
if(sameVersionAndClusterInstances.isEmpty()) {
// 过滤出所有相同版本的服务
List<Instance> sameVersionInstances = allInstances.stream()
.filter(x -> StringUtils.equalsIgnoreCase(x.getMetadata().get("version"), currentVersion))
.collect(Collectors.toList());
if(sameVersionInstances.isEmpty()) {
log.info("跨集群调用找不到对应合适的版本当前版本为:currentVersion:{}",currentVersion);
throw new RuntimeException("找不到相同版本的微服务实例");
}
else {
// 随机权重
chooseInstance = ExtendBalancer.getHostByRandomWeight2(sameVersionInstances);
log.info("跨集群同版本调用--->当前微服务所在集群:{},被调用微服务所在集群:{},当前微服务的版本:{},被调用微服务版本:{},Host:{},Port:{}",
currentClusterName, chooseInstance.getClusterName(), chooseInstance.getMetadata().get("current-version"),
chooseInstance.getMetadata().get("current-version"), chooseInstance.getIp(), chooseInstance.getPort());
}
}
else {
chooseInstance = ExtendBalancer.getHostByRandomWeight2(sameVersionAndClusterInstances);
log.info("同集群同版本调用--->当前微服务所在集群:{},被调用微服务所在集群:{},当前微服务的版本:{},被调用微服务版本:{},Host:{},Port:{}",
currentClusterName, chooseInstance.getClusterName(), chooseInstance.getMetadata().get("version"),
chooseInstance.getMetadata().get("current-version"), chooseInstance.getIp(), chooseInstance.getPort());
}
return new NacosServer(chooseInstance);
} catch (NacosException e) {
log.error("error,", e);
return null;
}
}
}
到了这里,关于【ribbon】Ribbon的负载均衡和扩展功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!