定制网站LoadBalancer替代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>
  • 1
  • 2
  • 3
  • 4
 <dependency>      <groupId>org.springframework.cloud</groupId>      <artifactId>spring-cloud-starter-loadbalancer</artifactId>      <version>3.1.0</version>      <scope>compile</scope></dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

使用

使用之前,定制网站先了解一下微服务之间的调用方式
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;     }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

使用方式二

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

@SpringBootApplicationpublic 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();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
@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;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

配置负载均衡策略

以前的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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

切换随机模式示例

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);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

依据源码: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");        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

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

@Configuration@LoadBalancerClient(name = "provider-one", configuration = LoadBalanceConfig.class)public class RestemplateConfig {    @Bean    @LoadBalanced    public RestTemplate templateOne() {        return new RestTemplate();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

需要注意:

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

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发