简介
了解过Spring Cloud,就知道,之前Spring Cloud中默认的负载均衡组件为ribbon,ribbon是Netflix开源的组件,但是目前已经停止更新了。所以Spring官方推出了Spring Cloud LoadBalancer。而且Spring Cloud LoadBalancer是目前唯一的负载均衡组件。
示例
文件承接前篇的示例,需要的请移步 Spring Cloud alibaba 使用Nacos服务发现
服务端
修改一下TestController
@RequestMapping("getCommonConf")
public String getCommonConf(HttpServletRequest request) {
System.out.println(conf);
return this.conf.toString() + request.getServerPort();
}
就在Controller里面打印了一下当前的端口。
启动
我们这次启动两个Provider的代码,端口分别为8701和8702
idea中同一个项目多次启动配置
勾上它就可以了
注意启动的时候使用不同的端口就行了。
消费端Consumer
我们使用@LoadBalanced的配置示例
package com.yyoo.cloud.conf;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConsumerConf {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
package com.yyoo.cloud.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getMyCloudConf")
public String getMyCloudConf(){
return restTemplate.getForObject("http://myCloud/myCloud/conf/getCommonConf",String.class);
}
}
消费端我们启动端口修改为8703
查看Nacos界面
两个服务端和一个客户端都启动之后,查看Nacos的服务菜单
客户端调用
访问:http://localhost:8703/myCloud/consumer/getMyCloudConf
多刷新几次,你会发现,打印出的端口在8701和8702之间转换,而且是轮流转换。
ReactiveLoadBalancer
Spring Cloud LoadBalancer中使用ReactiveLoadBalancer接口提供负载均衡的实现,目前只有两个实现:
- RoundRobinLoadBalancer:轮询(默认)
- RandomLoadBalancer:随机
在负载均衡策略的实现上,Spring Cloud LoadBalancer确实比ribbon少很多,不知后续官方会不会添加更多的可选策略,当然如果这些策略不够我们使用,我们也可以自己定义的,比如Nacos就自定义了一个实现NacosLoadBalancer。
RoundRobinLoadBalancer
其源码中就一句关键的代码
ServiceInstance instance = instances.get(pos % instances.size());
pos大家可以认为是当前的访问次数,instances.size()是我们的Provider实例的个数,后面的我想不用多解释了。
RandomLoadBalancer
关键代码
int index = ThreadLocalRandom.current().nextInt(instances.size());
ServiceInstance instance = instances.get(index);
也不用过多解释了吧
可以看到,我们的负载均衡的实现就是如此的简单。
修改默认的LoadBalancer
配置RandomLoadBalancer
package com.yyoo.cloud.conf;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
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;
public class MyLoadBalancerConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
注意:我们的配置类上没有添加@Configuration等注解
在RestTemplate配置中修改代码
package com.yyoo.cloud.conf;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
@LoadBalancerClient(value = "myCloud", configuration = MyLoadBalancerConfiguration.class)
public class ConsumerConf {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
其实就是增加了@LoadBalancerClient注解,value属性是我们的Provider的spring.cloud.nacos.discovery.service,spring.cloud.nacos.discovery.service的默认值是spring.application.name。configuration 就是我们的配置类。
再次尝试访问
访问:http://localhost:8703/myCloud/consumer/getMyCloudConf
多次刷新,你会发现,打印出的端口在8701和8702之间不再是轮流转换,而是随机转换了。
@LoadBalancerClients
上面我们提到了@LoadBalancerClient注解,仔细想想,就会发现,@LoadBalancerClient注解有个问题,那就是value属性需要指定,这意味着我们每个@LoadBalancerClient注解能指定某一个Provider使用我们自定义的负载均衡策略,而不能指定默认的策略。
我们再定义一个NacosLoadBalancer
package com.yyoo.cloud.conf;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer;
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;
import javax.annotation.Resource;
public class MyNacosLoadBalancerConfiguration {
@Resource
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Bean
ReactorLoadBalancer<ServiceInstance> myNacosLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new NacosLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name,nacosDiscoveryProperties);
}
}
修改RestTemplate配置类
package com.yyoo.cloud.conf;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
@LoadBalancerClients(value = {
@LoadBalancerClient(value = "myCloud1", configuration = MyLoadBalancerConfiguration.class)
},defaultConfiguration = MyNacosLoadBalancerConfiguration.class)
public class ConsumerConf {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
我们在@LoadBalancerClients中配置了一个@LoadBalancerClient,对应Provider名称为myCloud1,目前我们的示例中没有该Provider,这没有关系,如果有,它将使用我们的随机负载均衡策略。我们还配置了一个defaultConfiguration ,使用的是NacosLoadBalancer。我们目前调用的Provider的名称为myCloud,所以在调用我们之前的接口时,将使用NacosLoadBalancer。
NacosLoadBalancer
调用上面的示例之后你会发现,好像我们的结果跟随机的负载均衡策略差不多,是不是NacosLoadBalancer也是随机的负载均衡呢?
NacosLoadBalancer源码如下
package com.alibaba.cloud.nacos.loadbalancer;
import java.util.List;
import java.util.stream.Collectors;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.balancer.NacosBalancer;
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
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.ServiceInstanceListSupplier;
/**
* see original.
* {@link org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer}
*
* @author XuDaojie
* @since 2021.1
*/
public class NacosLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Logger log = LoggerFactory.getLogger(NacosLoadBalancer.class);
private final String serviceId;
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private final NacosDiscoveryProperties nacosDiscoveryProperties;
public NacosLoadBalancer(
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get().next().map(this::getInstanceResponse);
}
private Response<ServiceInstance> getInstanceResponse(
List<ServiceInstance> serviceInstances) {
if (serviceInstances.isEmpty()) {
log.warn("No servers available for service: " + this.serviceId);
return new EmptyResponse();
}
try {
String clusterName = this.nacosDiscoveryProperties.getClusterName();
List<ServiceInstance> instancesToChoose = serviceInstances;
if (StringUtils.isNotBlank(clusterName)) {
List<ServiceInstance> sameClusterInstances = serviceInstances.stream()
.filter(serviceInstance -> {
String cluster = serviceInstance.getMetadata()
.get("nacos.cluster");
return StringUtils.equals(cluster, clusterName);
}).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToChoose = sameClusterInstances;
}
}
else {
log.warn(
"A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}",
serviceId, clusterName, serviceInstances);
}
ServiceInstance instance = NacosBalancer
.getHostByRandomWeight3(instancesToChoose);
return new DefaultResponse(instance);
}
catch (Exception e) {
log.warn("NacosLoadBalancer error", e);
return null;
}
}
}
其最重要的方法就是getInstanceResponse
实际上NacosLoadBalancer的负载均衡策略为:取同一个集群(相同的clusterName)下Provider实例,然后根据实例的weight(权重,默认1.0)配置进行负载均衡。
Provider的权重配置为:spring.cloud.nacos.discovery.weight。
修改权重再验证
比如我们修改8701的Provider权重为10,再次测试
多试几次,8701出现的次数一定比8702的多
既然我们使用Nacos做注册发现,那么没有特殊要求的情况下,建议使用NacosLoadBalancer。
LoadBalancer的缓存设置
Spring Cloud LoadBalancer提供了两种缓存实现
- 默认
- caffeine支持的实现
要使用caffeine支持的实现,需要将其相关依赖添 com.github.ben-manes.caffeine:caffeine 加到项目中即可。当然相应的配置也需要使用caffeine的配置。
修改默认缓存配置
# 缓存过期时间,默认是35s
spring.cloud.loadbalancer.cache.ttl=35s
# 缓存的容量,默认是256
spring.cloud.loadbalancer.cache.capacity=256