阅读完需:约 17 分钟
目前从2018年开始SpringCloud中的Netflix项目逐渐进入维护阶段了。很多的组件Spring官方都有相应的组件来代替,阻断式升级(不向下兼容),比如负载均衡器,之前用的Ribbon
使用Spring Cloud Loadbalancer
来代替。
Spring Cloud Loadbalancer
,它一度只是Spring Cloud 孵化器里的一个小项目,并且一度搁浅。后再经过重启,发展,现行使其伟大使命,正式用于完全替换 Ribbon,成为Spring Cloud负载均衡器唯一实现。
值得注意的是:Spring Cloud LoadBalancer首次引入是在Spring Cloud Commons 2.2.0时,也就是Hoxton发布时就引入了,只不过那会还只是备胎/备选,默认依旧是Ribbon挑大梁。
负载均衡抽象LoadBalancerClient
接口有两个实现,而到了Spring Cloud 2020.0版本后,BlockingLoadBalancerClient
就是唯一实现了。
从2020开始的已经不兼容Ribbon了,必须使用Spring Cloud LoadBalancer,相比Ribbon而言从使用测并没有太大的改变。
Spring Cloud LoadBalancer还支持Spring Web Flux响应式编程,这里我们不展开,两者的实现原理思想相同,都是通过客户端添加拦截器,在拦截器中实现负载均衡。
测试目录结构:
这里测试的版本是SpringCloud-2022版本,使用的是SpringBoot3,JDK17
首先是创建总的pom文件作为父类依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.enmalvi</groupId>
<artifactId>spring-cloud-2022-boot3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cloud-3.x</name>
<description>spring-cloud-3.x</description>
<packaging>pom</packaging>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>netflix-candidates</id>
<name>Netflix Candidates</name>
<url>https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
1、创建eureka-server
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.enmalvi</groupId>
<artifactId>spring-cloud-2022-boot3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>eureka-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka</name>
<description>eureka</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>netflix-candidates</id>
<name>Netflix Candidates</name>
<url>https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
在启动类加上@EnableEurekaServer
注解,在application.yml
文件里写入配置信息
#给当前的服务取一个名字
spring:
application:
name: eureka
#设置端口号
server:
port: 1111
#默认情况下,Eureka也是一个普通的微服务,所以当它还是一个注册中心的时候,它会有两层的身份:1.注册中心,2.普通服务
#即当前服务会自己把自己注册到上面来
eureka:
client:
register-with-eureka: true
fetch-registry: true #表示是否从Eureka Server上获取信息
service-url:
defaultZone: http://127.0.0.1:1111/eureka
2、创建loadBalancer-server
同样的创建一个服务,pom依赖写入
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.enmalvi</groupId>
<artifactId>spring-cloud-2022-boot3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.enmalvi</groupId>
<artifactId>loadBalancer-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>loadBalancer-server</name>
<description>loadBalancer-server</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>netflix-candidates</id>
<name>Netflix Candidates</name>
<url>https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
启动类上添加@EnableDiscoveryClient
,application.yml
写入配置信息
#给当前的服务取一个名字
spring:
application:
name: loadBalancer-server
#设置端口号
server:
port: 1112
#默认情况下,Eureka也是一个普通的微服务,所以当它还是一个注册中心的时候,它会有两层的身份:1.注册中心,2.普通服务
#即当前服务会自己把自己注册到上面来
eureka:
client:
register-with-eureka: true
fetch-registry: true #表示是否从Eureka Server上获取信息
service-url:
defaultZone: http://127.0.0.1:1111/eureka
创建几个需要的接口
package com.example.loadbalancerserver;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author xujiahui
*/
@RestController
public class Controller {
private static final Logger log = LoggerFactory.getLogger(Controller.class);
@GetMapping("/greeting")
public String greet() {
log.info("Access /greeting");
List<String> greetings = Arrays.asList("Hi there", "Greetings", "Salutations");
Random rand = new Random();
int randomNum = rand.nextInt(greetings.size());
return greetings.get(randomNum);
}
@GetMapping("/")
public String home() {
log.info("Access /");
return "Hi!";
}
}
server算是简单的创建完成
3、创建loadbalancer-client
同样创建项目,pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.enmalvi</groupId>
<artifactId>spring-cloud-2022-boot3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.enmalvi</groupId>
<artifactId>loadbalancer-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>loadbalancer-client</name>
<description>loadbalancer-client</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>netflix-candidates</id>
<name>Netflix Candidates</name>
<url>https://artifactory-oss.prod.netflix.net/artifactory/maven-oss-candidates</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
启动类添加@EnableDiscoveryClient
,application.yml
配置添加
spring:
application:
name: loadBalancer-client
server:
port: 1113
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://127.0.0.1:1111/eureka
客户端创建接口来调用服务端的接口
package com.enmalvi.loadbalancerclient;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
/**
* 控制器
*
* @author xujiahui
* @date 2023/01/30
*/
@RestController
public class Controller {
private final WebClient.Builder loadBalancedWebClientBuilder;
private final ReactorLoadBalancerExchangeFilterFunction lbFunction;
/**
* 控制器
*
* @param webClientBuilder web客户机构建器
* @param lbFunction 磅函数
*/
public Controller(WebClient.Builder webClientBuilder,
ReactorLoadBalancerExchangeFilterFunction lbFunction) {
this.loadBalancedWebClientBuilder = webClientBuilder;
this.lbFunction = lbFunction;
}
/**
* 嗨
*
* @param name 名字
* @return {@link Mono}<{@link String}>
*/
@RequestMapping("/hi")
public Mono<String> hi(@RequestParam(value = "name", defaultValue = "Mary") String name) {
return loadBalancedWebClientBuilder.build().get().uri("http://loadBalancer-server/greeting")
.retrieve().bodyToMono(String.class)
.map(greeting -> String.format("%s, %s!", greeting, name));
}
/**
* 你好
*
* @param name 名字
* @return {@link Mono}<{@link String}>
*/
@RequestMapping("/hello")
public Mono<String> hello(@RequestParam(value = "name", defaultValue = "John") String name) {
return WebClient.builder()
.filter(lbFunction)
.build().get().uri("http://loadBalancer-server/greeting")
.retrieve().bodyToMono(String.class)
.map(greeting -> String.format("%s, %s!", greeting, name));
}
}
我们还需要一个@Configuration
类来设置负载均衡WebClient.Builder
实例,这里没用RestTemplate
,两者都是可以的
/*
SpringCloud 从 2020.0.1 版本开始,从 eureka 中移除了 ribbon
使用 SpringCloud LoadBalance 替代 ribbon 进行客户端负载均衡
目前 SpringCloud LoadBalance 仅支持两种负载均衡策略:【轮询策略】和【随机策略】
*/
//使用 @LoadBalancerClient 或 @LoadBalancerClients 注解,针对具体服务配置具体的客户端负载均衡算法策略
@LoadBalancerClient(name = "PROVIDER-APP", configuration = RandomLoadBalancerConfig.class)
@Configuration
public class RestTemplateConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Configuration
@LoadBalancerClient(name = "loadBalancer-server", configuration = ControllerConfiguration.class)
public class WebClientConfig {
@LoadBalanced
@Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}
配置提供了一个@LoadBalanced WebClient.Builder
实例,我们在有人点击 的hi
端点时使用它Controller.java
。命中端点后hi
,我们使用此构建器创建一个WebClient
实例,该实例向服务的 URL 发出 HTTPGET
请求loadBalancer-server
并将结果作为String
.
在Controller.java
中,我们还添加了一个/hello
执行相同操作的端点。但是,我们不使用@LoadBalanced
注释,而是使用@Autowired
负载平衡器交换过滤器函数 ( lbFunction
),我们通过使用该filter()
方法将其传递给WebClient
我们以编程方式构建的实例。
尽管我们WebClient
为两个端点设置的负载均衡实例略有不同,但两者的最终行为完全相同。Spring Cloud LoadBalancer
用于选择合适的loadBalancer-server
服务实例。
跨服务器实例的负载平衡
在WebClientConfig.java
中,我们使用@LoadBalancerClient
注释为 LoadBalancer 传递自定义配置:
@LoadBalancerClient(name = "loadBalancer-server", configuration = ControllerConfiguration.class)
这意味着,每当loadBalancer-server
联系名为的服务时,Spring Cloud LoadBalancer 不会使用默认设置运行,而是使用中提供的配置ControllerConfiguration.java
。
package com.enmalvi.loadbalancerclient;
import java.util.Arrays;
import java.util.List;
import reactor.core.publisher.Flux;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
/**
* @author xujiahui
*/
public class ControllerConfiguration {
@Bean
@Primary
ServiceInstanceListSupplier serviceInstanceListSupplier() {
return new DemoServiceInstanceListSuppler("loadBalancer-server");
}
}
class DemoServiceInstanceListSuppler implements ServiceInstanceListSupplier {
private final String serviceId;
DemoServiceInstanceListSuppler(String serviceId) {
this.serviceId = serviceId;
}
@Override
public String getServiceId() {
return serviceId;
}
@Override
public Flux<List<ServiceInstance>> get() {
return Flux.just(Arrays
.asList(new DefaultServiceInstance(serviceId + "1", serviceId, "localhost", 1112, false),
new DefaultServiceInstance(serviceId + "2", serviceId, "localhost", 9092, false),
new DefaultServiceInstance(serviceId + "3", serviceId, "localhost", 9999, false)));
}
}
在那个类中,我们提供了一个自定义ServiceInstanceListSupplier
的三个硬编码实例,Spring Cloud LoadBalancer 在调用loadBalancer-server
服务时从中选择。
客户端负载均衡原理
Loadbalancer负载均衡组件注册流程图是SpringBoot中使用@Loadbalanced
注解RestTemplate开启赋值均衡的组件依赖图
在RestTemplate上打上@Loadbalanced
注解
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
从官网可以知道Spring Cloud LoadBalancer放在spring-cloud-commons
,因此也作为其核心的@LoadBalanced
注解也就是由spring-cloud-commons
来实现,依据SpringBoot自动装配的原理先查看依赖包的实现逻辑,不难发现spring-cloud-commons引入了自动配置类LoadBalancerAutoConfiguration
和ReactorLoadBalancerClientAutoConfiguration
。
LoadBalancerAutoConfiguration
是RestTemplate
,ReactorLoadBalancerClientAutoConfiguration
是WebClient
当满足上述的条件时(@Conditional为条件注解),将自动创建LoadBalancerInterceptor
并注入到RestTemplate
中。
LoadBalancerInterceptor
LoadBalancerInterceptor
实现了ClientHttpRequestInterceptor
接口,因此也实现intercept
方法,用于实现负载均衡的拦截处理。注册到RestTemplate
中。
查看LoadBalancerInterceptor
的intercept
方法,调用了loadBalancer
的execute
方法,执行具体的请求逻辑,查看execute
方法,位于BlockingLoadBalancerClient
中
LoadBalancerClient
LoadBalancerClient
用于进行负载均衡逻辑,继承自ServiceInstanceChooser
接口,从服务列表中选择出一个服务地址进行调用。在LoadBalancerClient
种存在两个execute()
方法,均是用来执行请求的,reconstructURI()
是用来重构URL。
对于LoadBalancerClient
接口Spring Cloud LoadBalancer
的提供默认实现为BlockingLoadBalancerClient
BlockingLoadBalancerClient
LoadBalancerClientFactory
BlockingLoadBalancerClient
中持有LoadBalancerClientFactory
通过调用其getInstance
方法获取具体的负载均衡客户端。通过工厂类LoadBalancerClientFactory
获取具体的负载均衡器实例,后面的loadBalancer.choose(request)
调用其接口choose()
方法实现根据负载均衡算法选择下一个服务器完成负载均衡,而ReactiveLoadBalancer getInstance(String serviceId)
有默认实现
这里的LoadBalancerClientFactory
从哪里来?
LoadBalancerAutoConfiguration
将会注入LoadBalancerClientFactory
,BlockingLoadBalancerClientAutoConfiguration
将会注入LoadBalancerClient
,符合了上面的流程图。
(注意,这里的LoadBalancerAutoConfiguration有两个相同的类,分别位于config包和loadbalancer包,这两个都会执行,只是先后顺序不一定,注意区分开来看)
LoadBalancerClientFactory
客户端实现了不同的负载均衡算法,比如轮询、随机等。LoadBalancerClientFactory
继承自NamedContextFactory,NamedContextFactory
继承ApplicationContextAware
,实现Spring ApplicationContext
容器操作。
ReactiveLoadBalancer
ReactiveLoadBalancer
负载均衡器实现服务选择,Spring Cloud Balancer
中实现了轮询RoundRobinLoadBalancer
、随机RandomLoadBalancer
算法。
LoadBalancerClientConfiguration
如果没有显式指定负载均衡算法,默认缺省值为RoundRobinLoadBalancer
LoadBalancerRequestFactory
前面提到的LoadBalancerInterceptor
还有一个属性LoadBalancerRequestFactory
LoadBalancerRequest
工厂类调用createRequest
方法用于创建LoadBalancerRequest
。其内部持有LoadBalancerClient
对象也即持有BlockingLoadBalancerClient
。
到这基本可以结束了,当类都加载完成后,按流程可以回到BlockingLoadBalancerClient
的execute
方法中
BlockingLoadBalancerClient
的choose
方法继续调用
可以看到,调用了loadBalancerClientFactory
的getInstance
方法,根据服务ID获取到了负载均衡策略,又根据负载均衡策略获取到了具体的请求服务实例
ReactorLoadBalancerClientAutoConfiguration
基于WebClient
的@Loadbalanced
的流程的引入,首先声明负载均衡过滤器ReactorLoadBalancerClientAutoConfiguration
是一个自动装配器类,在项目中引入了 WebClient
和 ReactiveLoadBalancer
类之后,自动装配流程就开始运行,它会初始化一个实现了 ExchangeFilterFunction
的实例,在后面该实例将作为过滤器被注入到WebClient
。
在日常项目中,一般负载均衡都是结合Feign使用,整合LoadBalancer
的自动配置类FeignLoadBalancerAutoConfiguration
的实现
自定义负载均衡器
从上面可以知道LoadBalancerClientFactory
是创建客户机、负载均衡器和客户机配置实例的工厂。它根据客户端名称创建一个Spring ApplicationContext
,并从中提取所需的bean。因此进入到LoadBalancerClientFactory
类中,需要去实现它的子接口ReactorServiceInstanceLoadBalancer
,因为去获取负载均衡器实例的时候,是通过去容器中查找ReactorServiceInstanceLoadBalancer
类型的bean来实现的,可以参照RandomLoadBalancer
实现代码
package cn.itxs.ecom.order.config;
import java.util.List;
import java.util.Random;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
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 reactor.core.publisher.Mono;
public class ItxsRandomLoadBalancerClient implements ReactorServiceInstanceLoadBalancer {
// 服务列表
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
public ItxsRandomLoadBalancerClient(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable();
return supplier.get().next().map(this::getInstanceResponse);
}
/**
* 使用随机数获取服务
* @param instances
* @return
*/
private Response<ServiceInstance> getInstanceResponse(
List<ServiceInstance> instances) {
System.out.println("ItxsRandomLoadBalancerClient start");
if (instances.isEmpty()) {
return new EmptyResponse();
}
System.out.println("ItxsRandomLoadBalancerClient random");
// 随机算法
int size = instances.size();
Random random = new Random();
ServiceInstance instance = instances.get(random.nextInt(size));
return new DefaultResponse(instance);
}
}
package cn.itxs.ecom.order.config;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.context.annotation.Bean;
public class CustomLoadBalancerConfiguration {
@Bean
public ReactorServiceInstanceLoadBalancer customLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
return new ItxsRandomLoadBalancerClient(serviceInstanceListSupplierProvider);
}
}
同样的也可以直接这么写
@Configuration
public class WeightLoadBalancerClientConfiguration {
@Bean
public ReactorLoadBalancer<ServiceInstance> weightLoadBalancer(Environment environment, ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
String name = environment.getProperty("loadbalancer.client.name");
return new WeightLoadBalancer(serviceInstanceListSupplierProvider, name);
}
// 参数 serviceInstanceListSupplierProvider 会自动注入
// @Bean
// public ReactorServiceInstanceLoadBalancer customLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
// return new WeightLoadBalancer(serviceInstanceListSupplierProvider,null);
// }
}
然后,在启动类上加上@LoadBalancerClient(name =
"client1"
,configuration = WeightLoadBalancerClientConfiguration.
class
)
,name是需要调用的服务名
@LoadBalancerClient
的作用就是用来区分不同服务使用不同的负载均衡策略
其实发现无论是实现它的子接口ReactorServiceInstanceLoadBalancer
还是注册一个ReactorLoadBalancer
的Bean,这几个接口都是有继承关系的,父类都是ReactiveLoadBalancer
,也就是LoadBalancerClientFactory
的配置实例的工厂的接口。
其实还发现无论哪一种方式本质都是调用了ObjectProvider<ServiceInstanceListSupplier>
最开始的例子上是直接通过ServiceInstanceListSupplier
硬编码了
在load-balance机制中添加了ReactiveLoadBalancer
接口,并且为其提供了Round-Robin-based
和Random
实现。
为了从反应式服务中选择服务实例,使用了ServiceInstanceListSupplier
接口,Spring Cloud使用ServiceInstanceListSupplier
的service-discovery-based
的实现来从使用类路径中可用的DiscoveryClient
的ServiceDiscovery
中检索可用服务实例。