思维导图概念总结,地址:everything will be fine | ProcessOn
Ribbon使用
1.当引入了nacos服务发现就可以不用再引入ribbon依赖,nacos中用到了ribbon做负载均衡。
2.RestTemplate 添加@LoadBalanced注解,让RestTemplate在请求时拥有客户端负载均衡的能力
@Configuration
public class RestConfig {
@Bean
@LoadBalanced //开启负载均衡,加了@LoadBalanced注解,让RestTemplate在请求时拥有客户端负载均衡的能力,
public RestTemplate restTemplate() {
return new RestTemplate();
}
3.使用微服务名发起调用
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/findOrderByUserId/{id}")
public R findOrderByUserId(@PathVariable("id") Integer id) {
//如果restTemplate不添加@LoadBalanced注解则就不能解析mall-order
String url = "http://mall-order/order/findOrderByUserId/"+id;
R result = restTemplate.getForObject(url,R.class);
return result;
}
LoadBalanced原理解析
看源码的思路,很多框架代码时间都会遵循开闭原则,它会提供接口支持你对他的功能扩展,但不支持你修改,因此有时候看源码只需要记住核心接口自顶向下的看,对看源码有很大的帮助,尽量的避免打断电debug的方式,因为在响应式编程中很容易被一些回调函数扰乱思路,或者也有能在一些框架中会有一些定时任务不知道从哪就触发你的断点,例如nacos框架中就有很多定时任务检查心跳,健康状态。
1.@LoadBalanced会在restTemplate发起调用的时候,通过LoadBalancedInterceptor拦截器讲nacos获取到的实例列表中mall-order(spring.applicationa.name)替换成 对应的ip地址。在通过LoadBalanceCliet调用http接口
LoadBalanced.java
/**
* Annotation to mark a RestTemplate or WebClient bean to be configured to use a
* LoadBalancerClient.
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
@LoadBalanced使用了@Qualifier,@Qualifier用于筛选限定注入Bean。@LoadBalanced利用@Qualifier作为restTemplates注入的筛选条件,筛选出具有负载均衡标识的RestTemplate。
LoadBalancerAutoConfiguration.java
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced //自动配置,要想注入到 restTemplates中,必须要有 @LoadBalanced属性才行
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
//只展示重点方法,其他方法忽略
@Bean
//SmartInitializingSingleton这个是spring的一个扩展点,是在所有的bean初始化完成之后调用的,所以使用restTemplates需要在bean初始化完成之后在进行调。假设在@PostCpnstruct中调用则restTemplate不会具有负载均衡功能
//假设在@PostCpnstruct中调用则restTemplate不会具有负载均衡功能,例如提前的获取token,提前对数据预热或者缓存
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor loadBalancerInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
被@LoadBalanced注解的restTemplate会被定制,添加LoadBalancerInterceptor拦截器,SmartInitializingSingleton这个是spring的一个扩展点,是在所有的bean初始化完成之后调用的,所以使用restTemplates需要在bean初始化完成之后在进行调。假设在@PostCpnstruct中调用则restTemplate不会具有负载均衡功能。假设在@PostCpnstruct中调用则restTemplate不会具有负载均衡功能,例如提前的获取token,提前对数据预热
LoadBalancerCliet实现了ServiceInstanceChooser,ServiceInstanceChooser遵循了SpringCloud的规范,服务的选择器,对于负载均衡来说就是在服务的列表中选择出一个服务出来。
如果不使用@LoadBalanced注解,也可以通过添加LoadBalancerInterceptor拦截器让restTemplate起到负载均衡器的作用。
@Bean
public RestTemplate restTemplate(LoadBalancerInterceptor loadBalancerInterceptor) {
RestTemplate restTemplate = new RestTemplate();
//注入loadBalancerInterceptor拦截器
restTemplate.setInterceptors(Arrays.asList(loadBalancerInterceptor));
return restTemplate;
}
Ribbon扩展功能
@Configuration(
proxyBeanMethods = false
)
@EnableConfigurationProperties
@Import({HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {
//只展示重点方法,其他方法忽略
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
if (this.propertiesFactory.isSet(IRule.class, this.name)) {
return (IRule)this.propertiesFactory.get(IRule.class, config, this.name);
} else {
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;
}
}
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(
IClientConfig config, //IClientConfig:Ribbon的客户端配置,默认采用DefaultClientConfigImpl实现。
ServerList<Server> serverList, //ServerList:服务实例清单的维护机制,默认采用ConfigurationBasedServerList实现。
ServerListFilter<Server> serverListFilter, //ServerListFilter:服务实例清单过滤机制,默认采ZonePreferenceServerListFilter,该策略能够优先过滤出与请求方处于同区域的服务实例。
IRule rule, //IRule:Ribbon的负载均衡策略,默认采用ZoneAvoidanceRule实现,该策略能够在多区域环境下选出最佳区域的实例进行访问。
IPing ping, //IPing:Ribbon的实例检查策略,默认采用DummyPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认认为所有服务实例都是可用的。
ServerListUpdater serverListUpdater) {
//ILoadBalancer:负载均衡器,默认采用ZoneAwareLoadBalancer实现,它具备了区域感知的能力。
return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name) : new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater));
}
}
IClientConfig:Ribbon的客户端配置,默认采用DefaultClientConfigImpl实现。
IRule:Ribbon的负载均衡策略,默认采用ZoneAvoidanceRule实现,该策略能够在多区域环境下选出最佳区域的实例进行访问。
IPing:Ribbon的实例检查策略,默认采用DummyPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认认为所有服务实例都是可用的。一般不会去使用,还是会影响性能的,因为请求之前还是要再调用一次。
ServerList:服务实例清单的维护机制,默认采用ConfigurationBasedServerList实现。
ServerListFilter:服务实例清单过滤机制,默认采ZonePreferenceServerListFilter,该策略能够优先过滤出与请求方处于同区域的服务实例。(这个功能在弱化,尽量使用IRule)
ILoadBalancer:负载均衡器,默认采用ZoneAwareLoadBalancer实现,它具备了区域感知的能力。
IRule详解
- 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,在没有区域的环境下,类似于轮询(Zone---华北华中华南这样的区域,region---亚洲南美北美这样比较打的区域)
- NacosRule: 优先调用同一集群的实例,基于随机权重
全局配置:
调用的微服务,一律使用指定的负载均衡策略
@Configuration
public class RibbonConfig {
/**
* 全局配置
* 指定负载均衡策略
* @return
*/
@Bean
public IRule ribbonRule() {
// 指定使用Nacos提供的负载均衡策略(优先调用同一集群的实例,基于随机权重)
return new NacosRule();
}
局部配置:
调用指定微服务时,使用对应的负载均衡策略
修改application.yml
# 被调用的微服务名
mall-order:
ribbon:
# 指定使用Nacos提供的负载均衡策略(优先调用同一集群的实例,基于随机&权重)
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
自定义负载均衡策略
通过实现 IRule 接口可以自定义负载策略,主要的选择服务逻辑在 choose 方法中。
实现基于Nacos权重的负载均衡策略
@Slf4j
public class NacosRandomWithWeightRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public Server choose(Object key) {
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String serviceName = loadBalancer.getName();
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
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 clientConfig) {
}
配置自定义的策略
1)全局配置
@Bean
public IRule ribbonRule() {
return new NacosRandomWithWeightRule();
}
2)局部配置:
修改application.yml
# 被调用的微服务名
mall-order:
ribbon:
# 自定义的负载均衡策略(基于随机&权重)
NFLoadBalancerRuleClassName: com.tuling.mall.ribbondemo.rule.NacosRandomWithWeightRule
饥饿加载
在进行服务调用的时候,如果网络情况不好,第一次调用会超时。Ribbon默认懒加载,意味着只有在发起调用的时候才会创建客户端。
开启饥饿加载,解决第一次调用慢的问题
ribbon:
eager-load:
enabled: true
clients: mall-order,mall-other
参数说明:
- ribbon.eager-load.enabled:开启ribbon的饥饿加载模式
- ribbon.eager-load.clients:指定需要饥饿加载的服务名,也就是你需要调用的服务,如果有多个服务,则用逗号隔开
LoadBalancer
Spring Cloud LoadBalancer是Spring Cloud官方自己提供的客户端负载均衡器, 用来替代Ribbon。
Spring官方提供了两种客户端都可以使用loadbalancer:
RestTemplate
RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。默认情况下,RestTemplate默认依赖jdk的HTTP连接工具。
WebClient
WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor的。WebClient中提供了标准Http请求方式对应的get、post、put、delete等方法,可以用来发起相应的请求。
3.1 RestTemplate整合LoadBalancer
1)引入依赖
<!-- LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 提供了RestTemplate支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos服务注册与发现 移除ribbon支持-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
注意: nacos-discovery中引入了ribbon,需要移除ribbon的包
如果不移除,也可以在yml中配置不使用ribbon。默认情况下,如果同时拥有RibbonLoadBalancerClient和BlockingLoadBalancerClient,为了保持向后兼容性,将使用RibbonLoadBalancerClient。要覆盖它,可以设置spring.cloud.loadbalancer.ribbon.enabled属性为false。
spring:
application:
name: mall-user-loadbalancer-demo
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# 不使用ribbon,使用loadbalancer
loadbalancer:
ribbon:
enabled: false
2)使用@LoadBalanced注解修饰RestTemplate,开启客户端负载均衡功能
@Configuration public class RestConfig { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
3) 使用
@RestController @RequestMapping("/user") public class UserController { @Autowired private RestTemplate restTemplate; @RequestMapping(value = "/findOrderByUserId/{id}") public R findOrderByUserId(@PathVariable("id") Integer id) { String url = "http://mall-order/order/findOrderByUserId/"+id; R result = restTemplate.getForObject(url,R.class); return result; } }
3.2 WebClient整合LoadBalancer
1)引入依赖
<!-- LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- webflux -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- nacos服务注册与发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
2) 配置WebClient作为负载均衡器的client
@Configuration
public class WebClientConfig {
@LoadBalanced
@Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
@Bean
WebClient webClient() {
return webClientBuilder().build();
}
}
3) 使用
@Autowired
private WebClient webClient;
@RequestMapping(value = "/findOrderByUserId2/{id}")
public Mono<R> findOrderByUserIdWithWebClient(@PathVariable("id") Integer id) {
String url = "http://mall-order/order/findOrderByUserId/"+id;
//基于WebClient
Mono<R> result = webClient.get().uri(url)
.retrieve().bodyToMono(R.class);
return result;
}
原理: 底层会使用ReactiveLoadBalancer
引入webFlux
@Autowired
private ReactorLoadBalancerExchangeFilterFunction lbFunction;
@RequestMapping(value = "/findOrderByUserId3/{id}")
public Mono<R> findOrderByUserIdWithWebFlux(@PathVariable("id") Integer id) {
String url = "http://mall-order/order/findOrderByUserId/"+id;
//基于WebClient+webFlux
Mono<R> result = WebClient.builder()
.filter(lbFunction)
.build()
.get()
.uri(url)
.retrieve()
.bodyToMono(R.class);
return result;
}