Spring Cloud Alibaba学习笔记(3) - Ribbon
1.手写一个客户端负载均衡器
在了解什么是Ribbon之前,首先通过代码的方式手写一个负载均衡器
RestTemplate restTemplate = new RestTemplate(); // 获取请求示例
List<ServiceInstance> instances = discoveryClient.getInstances("study02");
List<String> collect = instances.stream()
.map(instance -> instance.getUri().toString() + "/find")
.collect(Collectors.toList()); // 随机算法
int i = ThreadLocalRandom.current().nextInt(collect.size());
String targetURL = collect.get(i); log.info("请求的目标地址: {}", targetURL); DemoComment forObject = restTemplate.getForObject(targetURL, DemoComment.class, 1);
2.Ribbon是什么
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。
简单来说,Ribbon就是简化了上面代码的组件,其中提供了更多的负载均衡算法。
3.整合Ribbon
依赖:因为spring-cloud-starter-alibaba-nacos-discovery中已经包含了Ribbon,所以不需要加依赖
注解:
在注册RestTemplate的地方添加注解
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
配置:没有配置
配置完成,此时,章节1中的手写负载均衡器代码可以简化为:
DemoComment forObject = restTemplate.getForObject("http://study02/find", DemoComment.class, 1);
当restTemplate组织请求的时候,Ribbon会自动把“study02”转换为该服务在Nacos上面的地址,并且进行负载均衡
PS:默认情况下Ribbon是懒加载的。当服务起动好之后,第一次请求是非常慢的,第二次之后就快很多。
解决方法:开启饥饿加载
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: server-,server-,server- #为哪些服务的名称开启饥饿加载,多个用逗号分隔
4.Ribbon的组成
接口 | 作用 | 默认值 |
IclientConfig | 读取配置 | DefaultClientConfigImpl |
IRule | 负载均衡规则,选择实例 | ZoneAvoidanceRule |
IPing | 筛选掉ping不通的实例 | DumyPing(该类什么不干,认为每个实例都可用,都能ping通) |
ServerList | 交给Ribbon的实例列表 | Ribbon:ConfigurationBasedServerList Spring Cloud Alibaba:NacosServerList |
ServerListFilter | 过滤掉不符合条件的实例 | ZonePreferenceServerListFilter |
ILoadBalancer | Ribbon的入口 | ZoneAwareLoadBalancer |
ServerListUpdater | 更新交给Ribbon的List的策略 | PollingServerListUpdater |
4.1 Ribbon内置的负载均衡规则
规则名称 | 特点 |
AvailabilityFilteringRule | 过滤掉一直连接失败的被标记为circuit tripped(电路跳闸)的后端Service,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤Server的逻辑,其实就是检查status的记录的各个Server的运行状态 |
BestAvailableRule | 选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过 |
RandomRule | 随机选择一个Server |
ResponseTimeWeightedRule | 已废弃,作用同WeightedResponseTimeRule |
RetryRule | 对选定的负责均衡策略机上充值机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的Server |
RoundRobinRule | 轮询选择,轮询index,选择index对应位置Server |
WeightedResponseTimeRule | 根据相应时间加权,相应时间越长,权重越小,被选中的可能性越低 |
ZoneAvoidanceRule | (默认是这个)负责判断Server所Zone的性能和Server的可用性选择Server,在没有Zone的环境下,类似于轮询(RoundRobinRule) |
4.2 细粒度配置
示例:调用服务A采用默认的负载均衡规则ZoneAvoidanceRule,调用服务B采用随即RandomRule规则
4.2.1 Java代码
新建一个ribbonConfiguration包(一定要在启动类所在的包以外),ribbonConfiguration包内新建RibbonConfiguration类
ps:为什么ribbonConfiguration包一定要在启动类所在的包以外,RibbonConfiguration类引用了@Configuration注解,这个注解组合了@Component注解,可以理解为@Configuration是一个特殊的@Component。
而在启动类中有一个@SpringBootApplication注解,其中组合了@ComponentScan注解,这个注解是用来扫猫@Component的,包括@Configuration注解,扫猫的分为当前启动类所在的包以及启动类所在包下面的所有的@Component。
而Spring的上下文是树状的上下文,@SpringBootApplication所扫猫的上下文是主上下文;而ribbon也会有一个上下文,是子上下文。而父子上下文扫猫的包一旦重叠,会导致很多问题【在本文中会导致配置被共享】,所以ribbon的配置类一定不能被启动类扫猫到,ribbonConfiguration包一定要在启动类所在的包以外。
package ribbonConfiguration; import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* java代码实现细粒度配置
* 注意是单独的包
* 父子上下文重复扫猫问题
*/
@Configuration
public class RibbonConfiguration {
@Bean
public IRule ribbonRule() {
return new RandomRule();
}
}
新建DemoRibbonConfiguration类,为服务B指定负载均衡算法
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Configuration;
import ribbonConfiguration.RibbonConfiguration; /**
* 自定义负载均衡方式
*/
@Configuration
// 单个配置
@RibbonClient(name = "B", configuration = RibbonConfiguration.class)
public class DemoRibbonConfiguration { }
4.2.2 配置属性方式
# ribbon 配置方式实现细粒度配置
demo-center:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
其他参数:
NFLoadBalancerClassName: #ILoadBalancer该接口实现类
NFLoadBalancerRuleClassName: #IRule该接口实现类
NFLoadBalancerPingClassName: #Iping该接口实现类
NIWSServerListClassName: #ServerList该接口实现类
NIWSServerListFilterClassName: #ServiceListFilter该接口实现类
优先级:属性配置 > 代码配置
尽量使用属性配置,属性配置解决不了问题的时候在考虑使用代码配置
在同一个微服务内尽量保证配置单一性
4.3 全局配置
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Configuration;
import ribbonConfiguration.RibbonConfiguration; @Configuration
// 全局配置
@RibbonClients(defaultConfiguration = RibbonConfiguration.class)
public class DemoRibbonConfiguration { }
还有一种方法,4.2.1中提到的,让ComponentScan上下文重叠,从而实现全局配置,不建议使用这种方法。
5.Ribbon扩展
5.1 支持Nacos权重
在Nacos的控制台,可以为每一个实例配置权重,取值在0~1之间,值越大,表示这个实例被调用的几率越大。而Ribbon内置的负载均衡的规则不支持权重,我们可以通过代码的方式让ribbon支持Nacos权重。
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
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 org.springframework.beans.factory.annotation.Autowired; /**
* 基于Nacos 权重的负载均衡算法
*/
public class NacosWeightRule extends AbstractLoadBalancerRule { @Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties; @Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
// 读取配置文件,并初始化NacosWeightRule
} @Override
public Server choose(Object o) {
try {
// ribbon入口
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); //想要请求的微服务的名称
String name = loadBalancer.getName(); // 实现负载均衡算法
// 拿到服务发现的相关api
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// nacos client自动通过基于权重的负载均衡算法,给我们一个示例
Instance instance = namingService.selectOneHealthyInstance(name); return new NacosServer(instance);
} catch (NacosException e) {
return null;
}
}
}
5.2 同一集群优先调用
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
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.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.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 {
// 拿到配置文件中的集群名称
String clusterName = nacosDiscoveryProperties.getClusterName(); BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
String name = loadBalancer.getName();
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// 找到指定服务的所有示例 A
List<Instance> instances = namingService.selectInstances(name, true);
// 过滤出相同集群的所有示例 B
List<Instance> sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
.collect(Collectors.toList());
// 如果B是空,就用A
List<Instance> instancesToBeanChoose = new ArrayList<>();
if (CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToBeanChoose = instances;
} else {
instancesToBeanChoose = sameClusterInstances;
}
// 基于权重的负载均衡算法返回示例
Instance hostByRandomWeightExtend = ExtendBalancer.getHostByRandomWeightExtend(instancesToBeanChoose); return new NacosServer(hostByRandomWeightExtend);
} catch (NacosException e) {
log.error("发生异常了", e);
return null;
}
}
} class ExtendBalancer extends Balancer {
public static Instance getHostByRandomWeightExtend(List<Instance> hosts) {
return getHostByRandomWeight(hosts);
}
}
5.3 基于元数据的版本控制
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
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.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 org.springframework.beans.factory.annotation.Autowired; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; /**
* 基于nacos元数据的版本控制
*/
@Slf4j
public class NacosFinalRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties; @Override
public void initWithNiwsConfig(IClientConfig iClientConfig) { } @Override
public Server choose(Object o) {
try {
// 负载均衡规则:优先选择同集群下,符合metadata的实例
// 如果没有,就选择所有集群下,符合metadata的实例 // 1. 查询所有实例 A
String clusterName = nacosDiscoveryProperties.getClusterName();
String targetVersion = this.nacosDiscoveryProperties.getMetadata().get("target-version"); BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
String name = loadBalancer.getName();
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
List<Instance> instances = namingService.selectInstances(name, true); // 2. 筛选元数据匹配的实例 B
List<Instance> metadataMatchInstances = instances;
// 如果配置了版本映射,那么只调用元数据匹配的实例
if (StringUtils.isNotBlank(targetVersion)) {
metadataMatchInstances = instances.stream()
.filter(instance -> Objects.equals(targetVersion, instance.getMetadata().get("version")))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(metadataMatchInstances)) {
log.warn("未找到元数据匹配的目标实例!请检查配置。targetVersion = {}, instance = {}", targetVersion, instances);
return null;
}
} // 3. 筛选出同cluster下元数据匹配的实例 C
// 4. 如果C为空,就用B
List<Instance> clusterMetadataMatchInstances = metadataMatchInstances;
// 如果配置了集群名称,需筛选同集群下元数据匹配的实例
if (StringUtils.isNotBlank(clusterName)) {
clusterMetadataMatchInstances = metadataMatchInstances.stream()
.filter(instance -> Objects.equals(clusterName, instance.getClusterName()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(clusterMetadataMatchInstances)) {
clusterMetadataMatchInstances = metadataMatchInstances;
log.warn("发生跨集群调用。clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, targetVersion, clusterMetadataMatchInstances);
}
} // 5. 随机选择实例
Instance instance = ExtendBalancer.getHostByRandomWeightExtend(clusterMetadataMatchInstances);
return new NacosServer(instance);
} catch (Exception e) {
return null;
}
}
}
Spring Cloud Alibaba学习笔记(3) - Ribbon的更多相关文章
- Spring Cloud Alibaba学习笔记(1) - 整合Spring Cloud Alibaba
Spring Cloud Alibaba从孵化器版本毕业:https://github.com/alibaba/spring-cloud-alibaba,记录一下自己学习Spring Cloud Al ...
- Spring Cloud Alibaba学习笔记(15) - 整合Spring Cloud Gateway
Spring Cloud Gateway 概述 Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于Netty.Reactor以及WEbFlux构建,它 ...
- Spring Cloud Alibaba学习笔记
引自B站楠哥:https://space.bilibili.com/434617924 一.创建父工程 创建父工程hello-spring-cloud-alibaba Spring Cloud Ali ...
- Spring Cloud Alibaba学习笔记(22) - Nacos配置管理
目前业界流行的统一配置管理中心组件有Spring Cloud Config.Spring Cloud Alibaba的Nacos及携程开源的Apollo,本文将介绍Nacos作为统一配置管理中心的使用 ...
- Spring Cloud Alibaba学习笔记(2) - Nacos服务发现
1.什么是Nacos Nacos的官网对这一问题进行了详细的介绍,通俗的来说: Nacos是一个服务发现组件,同时也是一个配置服务器,它解决了两个问题: 1.服务A如何发现服务B 2.管理微服务的配置 ...
- Spring Cloud Alibaba学习笔记(23) - 调用链监控工具Spring Cloud Sleuth + Zipkin
随着业务发展,系统拆分导致系统调用链路愈发复杂一个前端请求可能最终需要调用很多次后端服务才能完成,当整个请求陷入性能瓶颈或不可用时,我们是无法得知该请求是由某个或某些后端服务引起的,这时就需要解决如何 ...
- Spring Cloud Alibaba学习笔记(20) - Spring Cloud Gateway 内置的全局过滤器
参考:https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#_global_filter ...
- Spring Cloud Alibaba学习笔记(18) - Spring Cloud Gateway 内置的过滤器工厂
参考:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.0.RELEASE/single/spring-clou ...
- Spring Cloud Alibaba学习笔记(14) - Spring Cloud Stream + RocketMQ实现分布式事务
发送消息 在Spring消息编程模型下,使用RocketMQ收发消息 一文中,发送消息使用的是RocketMQTemplate类. 在集成了Spring Cloud Stream之后,我们可以使用So ...
随机推荐
- 每天学会一点点(JAVA基础)
1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”? 虚拟机是一个可以执行Java字节码的虚拟机进程.Java源文件被编译成能被Java虚拟机执行的字节码文件. Java被设计成允 ...
- 前端自动化部署linux centOs + Jenkins + nignx + 单页面应用
Jenkins是什么? Jenkins 是一款业界流行的开源持续集成工具,广泛用于项目开发,具有自动化构建.测试和部署等功能. 准备工作 Linux centOS系统阿里云服务器一个 码云一个存放vu ...
- Appium+python自动化(三十七)- 士兵突击许三多 - 多个appium服务启动,多个设备启动,多进程并发启动设备-并发测试 - 下(超详解)
简介 接着上一篇继续看一下如何并发测试以及并发测试的过程中,可能遇到的问题,在这里宏哥把宏哥遇到的和小伙伴或者童鞋们,一起分享一下. Appium端口检测 问题思考 经过前面学习,我们已经能够使用py ...
- a149: 乘乘樂
题目: 你拿到一个整数,却忍不住想把每个位数都乘在一起.例如看到356就会想要知道3 * 5 * 6的值为何.快写个程序帮帮为了乘数字而快发疯的自己吧! 思路:把这个数每一位%10,并且再将它每次/1 ...
- Web之-----弹出确认框控件应用
引用文件!-------- <link rel="stylesheet" type="text/css" href="@Url.FrontUrl ...
- 新手学习FFmpeg - 调用API完成两个视频的任意合并
本次尝试在视频A中的任意位置插入视频B. 在上一篇中,我们通过调整PTS可以实现视频的加减速.这只是对同一个视频的调转,本次我们尝试对多个视频进行合并处理. Concat如何运行 ffmpeg提供了一 ...
- .netCore部署在IIS上遇到的问题(500.19,500.21错误)
1.确保IIS功能都安装上了. 2.确保.netcore 的最新sdk已安装. 3.应用程序池改成无托管代码 4.500.19错误 错误原因,没有安装 DotNetCore.2.0.5-Windows ...
- PyCharm设置自己的默认模板
1.File-Settings 2.Editor- Code Style - File and Code Templates - Python Script 需要设置什么内容,现在就可以写入了,相关变 ...
- javascript:history.go(-1)的使用
1.问题描述 在微信项目开发中,比如常用联系人的增删改查操作中,比如跳入常用联系人管理页面,选中一个联系人修改它,就会跳入修改页面,修改完成后跳转到常用联系人管理页面,此时如果修改成功跳转采用的是页面 ...
- Android开发——Kotlin开发APP使用笔记
之前一直使用java来开发Android项目,学了新的kotlin语言,前来试一试,并说一下kotlin对Android的一些功能增强 创建项目 我使用的是Android Studio3.0+,所以默 ...