阅读完需:约 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响应式编程,这里我们不展开,两者的实现原理思想相同,都是通过客户端添加拦截器,在拦截器中实现负载均衡。
测试目录结构:
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-2.png)
这里测试的版本是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
服务时从中选择。
客户端负载均衡原理
![](http://www.enmalvi.com/wp-content/uploads/2023/01/v2-5dd9961f01d21283b622b6ef08270f08_r.png)
![](http://www.enmalvi.com/wp-content/uploads/2023/01/v2-1236854d34e23c942329bf04da6fe4da_1440w.png)
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
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-3.png)
当满足上述的条件时(@Conditional为条件注解),将自动创建LoadBalancerInterceptor
并注入到RestTemplate
中。
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-5.png)
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-4.png)
LoadBalancerInterceptor
LoadBalancerInterceptor
实现了ClientHttpRequestInterceptor
接口,因此也实现intercept
方法,用于实现负载均衡的拦截处理。注册到RestTemplate
中。
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-6.png)
查看LoadBalancerInterceptor
的intercept
方法,调用了loadBalancer
的execute
方法,执行具体的请求逻辑,查看execute
方法,位于BlockingLoadBalancerClient
中
LoadBalancerClient
LoadBalancerClient
用于进行负载均衡逻辑,继承自ServiceInstanceChooser
接口,从服务列表中选择出一个服务地址进行调用。在LoadBalancerClient
种存在两个execute()
方法,均是用来执行请求的,reconstructURI()
是用来重构URL。
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-7.png)
对于LoadBalancerClient
接口Spring Cloud LoadBalancer
的提供默认实现为BlockingLoadBalancerClient
BlockingLoadBalancerClient
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-8.png)
LoadBalancerClientFactory
BlockingLoadBalancerClient
中持有LoadBalancerClientFactory
通过调用其getInstance
方法获取具体的负载均衡客户端。通过工厂类LoadBalancerClientFactory
获取具体的负载均衡器实例,后面的loadBalancer.choose(request)
调用其接口choose()
方法实现根据负载均衡算法选择下一个服务器完成负载均衡,而ReactiveLoadBalancer getInstance(String serviceId)
有默认实现
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-9.png)
这里的LoadBalancerClientFactory
从哪里来?
LoadBalancerAutoConfiguration
将会注入LoadBalancerClientFactory
,BlockingLoadBalancerClientAutoConfiguration
将会注入LoadBalancerClient
,符合了上面的流程图。
(注意,这里的LoadBalancerAutoConfiguration有两个相同的类,分别位于config包和loadbalancer包,这两个都会执行,只是先后顺序不一定,注意区分开来看)
LoadBalancerClientFactory
客户端实现了不同的负载均衡算法,比如轮询、随机等。LoadBalancerClientFactory
继承自NamedContextFactory,NamedContextFactory
继承ApplicationContextAware
,实现Spring ApplicationContext
容器操作。
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-10.png)
ReactiveLoadBalancer
ReactiveLoadBalancer
负载均衡器实现服务选择,Spring Cloud Balancer
中实现了轮询RoundRobinLoadBalancer
、随机RandomLoadBalancer
算法。
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-11.png)
LoadBalancerClientConfiguration
如果没有显式指定负载均衡算法,默认缺省值为RoundRobinLoadBalancer
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-12.png)
LoadBalancerRequestFactory
前面提到的LoadBalancerInterceptor
还有一个属性LoadBalancerRequestFactory
LoadBalancerRequest
工厂类调用createRequest
方法用于创建LoadBalancerRequest
。其内部持有LoadBalancerClient
对象也即持有BlockingLoadBalancerClient
。
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-13.png)
到这基本可以结束了,当类都加载完成后,按流程可以回到BlockingLoadBalancerClient
的execute
方法中
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-14.png)
BlockingLoadBalancerClient
的choose
方法继续调用
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-15.png)
可以看到,调用了loadBalancerClientFactory
的getInstance
方法,根据服务ID获取到了负载均衡策略,又根据负载均衡策略获取到了具体的请求服务实例
ReactorLoadBalancerClientAutoConfiguration
基于WebClient
的@Loadbalanced
的流程的引入,首先声明负载均衡过滤器ReactorLoadBalancerClientAutoConfiguration
是一个自动装配器类,在项目中引入了 WebClient
和 ReactiveLoadBalancer
类之后,自动装配流程就开始运行,它会初始化一个实现了 ExchangeFilterFunction
的实例,在后面该实例将作为过滤器被注入到WebClient
。
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-16.png)
在日常项目中,一般负载均衡都是结合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
的配置实例的工厂的接口。
![](http://www.enmalvi.com/wp-content/uploads/2023/01/image-17-1024x594.png)
其实还发现无论哪一种方式本质都是调用了ObjectProvider<ServiceInstanceListSupplier>
最开始的例子上是直接通过ServiceInstanceListSupplier
硬编码了
在load-balance机制中添加了ReactiveLoadBalancer
接口,并且为其提供了Round-Robin-based
和Random
实现。
为了从反应式服务中选择服务实例,使用了ServiceInstanceListSupplier
接口,Spring Cloud使用ServiceInstanceListSupplier
的service-discovery-based
的实现来从使用类路径中可用的DiscoveryClient
的ServiceDiscovery
中检索可用服务实例。