Spring Cloud LoadBalancer(负载均衡)

简介

了解过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
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值