关于和LoadBalancer
本次试验spring boot
版本2.6.1
配合SpringCloud
版本为Jubilee(2021.0.0)
本来想用Ribbon
做,定制网站偶然间发现不导入ribbon
也能通过RestTemplate
+@LoadBalance
定制网站实现负载均衡,心生好奇
@LoadBalance
定制网站注解在之前的springcloud
定制网站版本中属于spring-cloud-starter-ribbon
包
但在jubilee
定制网站版本好像改成了org.springframework.cloud.client.loadbalancer
定制网站后面去查了一下,原来是Ribbon
定制网站目前已经停止维护,新版SpringCloud(2021.x.x)
用LoadBalancer
替代了Ribbon
;Spring Cloud
全家桶在Spring Cloud Commons
项目中,添加了Spring cloud Loadbalancer
定制网站作为新的负载均衡器,定制网站并且做了兼容- 在导入
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('生产服务名')
选择一个服务方
根据服务方的host
和port
信息拼接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
需要注意:
@LoadBalancerClient
中的name
属性是指服务提供方的服务名(即:spring.application.name),eureka是通过服务名去找对应的微服务的- configuration 则是指定负载均衡器配置类