LoadBalancer替代Ribbon实现负载均衡

发布于:2022-11-28 ⋅ 阅读:(362) ⋅ 点赞:(0)

关于Ribbon和LoadBalancer

本次试验spring boot版本2.6.1
配合SpringCloud版本为Jubilee(2021.0.0)
本来想用Ribbon做负载均衡,偶然间发现不导入ribbon也能通过RestTemplate+@LoadBalance实现负载均衡,心生好奇

  1. @LoadBalance注解在之前的springcloud版本中属于spring-cloud-starter-ribbon
    但在jubilee版本好像改成了org.springframework.cloud.client.loadbalancer
    后面去查了一下,原来是Ribbon目前已经停止维护,新版SpringCloud(2021.x.x)LoadBalancer替代了RibbonSpring Cloud全家桶在Spring Cloud Commons项目中,添加了Spring cloud Loadbalancer作为新的负载均衡器,并且做了兼容
  2. 在导入spring-cloud-starter-netflix-eureka-client这个包的时候,就已经包含了spring-cloud-starter-loadbalancer,因此不需要另外导入
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
 <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
      <version>3.1.0</version>
      <scope>compile</scope>
</dependency>

使用

使用之前,先了解一下微服务之间的调用方式
Spring Cloud 中微服务调用默认是用 http 请求,主要通过一下三种 API:

API 描述
RestTemplate 同步 http API
WebClient 异步响应式 http API
第三方封装 如 openfeign
当项目中导入了 spring-cloud-starter-loadbalancer依赖,会自动在相关的Bean中加入负载均衡,对于以上三种请求方式加入负载均衡的方式如下:
  • 对于 RestTemplate,会自动对 @LoadBalanced 注解修饰的 RestTemplate Bean 增加 Interceptor 拦截器,从而加上了负载均衡的特性。
  • 对于 WebClient,通过加入 ReactorLoadBalancerExchangeFilterFunction 的方式加上负载均衡的特性。
  • 对于第三方封装,见百度

使用方式一

手动注入LoadBalanceClient,通过choose('生产服务名')选择一个服务方
根据服务方的hostport信息拼接url,手动new RestTemplate()发送请求获取响应

@RestController
@RequestMapping("/consumer")
public class ConsumeController {
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @RequestMapping("/interfaceOne")
    public String consumeOne(String msg) {
 		// 第二种调用方式: 通过loadBalancerClient按照一定规则选择服务方(默认为轮询模式)
		// 根据服务名(EurekaClient的服务名)获取服务列表,根据算法选取某个服务,并获得这个服务的网络信息。
		ServiceInstance serviceInstance = loadBalancerClient.choose("ServiceClient");
		String result = new RestTemplate().getForObject("http://" + serviceInstance.getHost()
                + ":" + serviceInstance.getPort() 
                + "/clientOne/interfaceOne?msg=" + msg, String.class);
        return result; 
    }
}

使用方式二

方式二是第一种方式的注解简化版.原理相同
因为SpringBoot不会自动注入RestTemplate对象,因此需要手动注入
SpringBoot自动注入了RestTempalateBuilder,所以可以用build()的方式创建restTemplate对象
同时在restTemplate对象加上@LoadBalance注解,叠加自动负载均衡
其实就是用@LoadBalance注解代替了手动注入loadBalanceClient对象去实现服务选择

@SpringBootApplication
public class ConsumerApplication {
    @Autowired
    private RestTemplateBuilder templateBuilder;

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

    @Bean
    @LoadBalanced
    public RestTemplate getTemplate() {
        return templateBuilder.build();
    }
}
@RestController
@RequestMapping("/consumer")
public class ConsumeController {
    @Autowired()
    private RestTemplate restTemplate;

    @RequestMapping("/interfaceOne")
    public String consumeOne(String msg) {
		String result = restTemplate.getForObject(
			"http://ServiceClient/clientOne/interfaceOne?msg=" + msg, String.class
		);
		return result;
    }
}

配置负载均衡策略

以前的Ribbon有多种负载均衡策略

策略类型 类名
随机 RandomRule
轮询 RoundRobinRule
重试 RetryRule
最低并发 BestAvailableRule
可用过滤 AvailabilityFilteringRule
响应时间加权重 ResponseTimeWeightedRule
区域权重 ZoneAvoidanceRule

但LoadBalancer貌似只提供了两种负载均衡器

  • RandomLoadBalancer 随机
  • RoundRobinLoadBalancer 轮询

不指定的时候默认用的是轮询

依据源码:LoadBalancerClientConfiguration类中的默认构造器

@Bean
@ConditionalOnMissingBean // 空Bean(未指定任何负载均衡器)时的默认情况
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty("loadbalancer.client.name");
// 构造器返回的对象是轮询负载均衡器
return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}

如果要切换使用随机或者自定义,需要手动配置一下

切换随机模式示例

1.先创建一个LoadBalancerConfig配置类
注意不加@Configuration注解(只针对单个微服务调用,而不是全局配置)

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.*;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

public class LoadBalanceConfig {
    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                LoadBalancerClientFactory loadBalancerClientFactory) {
        // name取自@LoadBalancerClient中的name属性指定的服务提供方的服务名
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

依据源码:LoadBalancerClientConfigurationRegistrar类

// 这是个Bean定义器,猜测其作用就是和@LoadBalancerClient注解配合,在注册RestTemplate对象并给其配置负载均衡器的时候,定义负载均衡器的核心属性
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		// 上来简单明了地获取@LoadBalancerClients的name属性,说明之前没有猜错
        Map<String, Object> attrs = metadata.getAnnotationAttributes(LoadBalancerClients.class.getName(), true);
        // 这里说如果取name属性没取到,就取value属性,那说明除了name属性外,也可以用过value属性来指定服务提供方的服务名(有待试验)
        if (attrs != null && attrs.containsKey("value")) {
            AnnotationAttributes[] clients = (AnnotationAttributes[])((AnnotationAttributes[])attrs.get("value"));
            AnnotationAttributes[] var5 = clients;
            int var6 = clients.length;
            for(int var7 = 0; var7 < var6; ++var7) {
                AnnotationAttributes client = var5[var7];
                // 这里应该是用name和指定的configuration配置去注册一个负载均衡器,先不深究
                registerClientConfiguration(registry, getClientName(client), client.get("configuration"));
            }
        }
        // 没有拿到name和configuration等属性的时候,用 default+类名 作为name,加上默认的配置defaultConfiguration去注册负载均衡器
        if (attrs != null && attrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            } else {
                name = "default." + metadata.getClassName();
            }
            registerClientConfiguration(registry, name, attrs.get("defaultConfiguration"));
        }
		// 这里为啥又重走一遍?不太明白
        Map<String, Object> client = metadata.getAnnotationAttributes(LoadBalancerClient.class.getName(), true);
        String name = getClientName(client);
        if (name != null) {
            registerClientConfiguration(registry, name, client.get("configuration"));
        }
    }
// 这个是上面那个方法调用的取值方法~看看就好
private static String getClientName(Map<String, Object> client) {
    if (client == null) {
        return null;
    } else {
        String value = (String)client.get("value");
        if (!StringUtils.hasText(value)) {
            value = (String)client.get("name");
        }
        if (StringUtils.hasText(value)) {
            return value;
        } else {
            throw new IllegalStateException("Either 'name' or 'value' must be provided in @LoadBalancerClient");
        }
    }
}

2.再创建一个RestTemplateConfig配置类(直接写在启动类也可以)
通过@LoadBalancerClient注解为当前的restTemplate对象指定负载均衡配置

@Configuration
@LoadBalancerClient(name = "provider-one", configuration = LoadBalanceConfig.class)
public class RestemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate templateOne() {
        return new RestTemplate();
    }
}

需要注意:

  1. @LoadBalancerClient中的name属性是指服务提供方的服务名(即:spring.application.name),eureka是通过服务名去找对应的微服务的
  2. configuration 则是指定负载均衡器配置类

在这里插入图片描述

本文含有隐藏内容,请 开通VIP 后查看