阅读完需:约 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中检索可用服务实例。
