SpringCloud 负载均衡 spring-cloud-starter-loadbalancer

简述

spring-cloud-starter-loadbalancer 是 Spring Cloud 中的一个组件,它提供了客户端负载均衡的功能。在 Spring Cloud 的早期版本中,Netflix Ribbon 被广泛用作客户端负载均衡器,但随着时间推移和 Netflix Ribbon 进入维护模式,Spring Cloud 社区开始转向更灵活、更易于维护的替代方案。

spring-cloud-starter-loadbalancer 是基于 Spring 5 的 WebClient 构建的,并使用了 Reactor(Spring 5 的反应式编程模型的核心库)来实现异步非阻塞的负载均衡请求。它与 Spring Cloud 的服务发现和配置结合得非常好,可以很容易地与 Eureka、Consul、Nacos 等服务发现组件一起使用。

当将 spring-cloud-starter-loadbalancer 添加到Spring Boot 应用程序中时,可以使用 WebClient.Builder 的 loadBalancer 方法来创建一个具有负载均衡功能的 WebClient 实例。这个 WebClient 实例会自动从服务发现中获取服务实例列表,并使用内置的负载均衡算法(如轮询、随机等)来选择一个服务实例来发送请求。

例如,如果正在使用 Eureka 作为服务发现,并且想要发送一个 GET 请求到名为 "my-service" 的服务,可以这样做:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.cloud.client.loadbalancer.LoadBalanced;  
import org.springframework.context.annotation.Bean;  
import org.springframework.stereotype.Service;  
import org.springframework.web.reactive.function.client.WebClient;  
  
@Service  
public class MyServiceClient {  
  
    @Autowired  
    private WebClient.Builder webClientBuilder;  
  
    @Bean  
    @LoadBalanced  
    public WebClient.Builder loadBalancedWebClientBuilder() {  
        return WebClient.builder();  
    }  
  
    public String getSomethingFromMyService() {  
        // 注意这里我们直接使用了 "my-service" 作为 URI,而不是具体的服务实例地址  
        return webClientBuilder.build()  
                .get()  
                .uri("http://my-service/some-endpoint")  
                .retrieve()  
                .bodyToMono(String.class)  
                .block(); // 注意:block() 方法会阻塞当前线程,通常只在非反应式上下文中使用  
    }  
}

主要特点

  • 基于 WebClient:与 Spring 5 的 WebClient 紧密集成,提供了反应式(Reactive)的 HTTP 客户端功能。
  • 服务发现集成:与 Spring Cloud 的服务发现组件(如 Eureka、Consul、Nacos 等)集成,可以自动获取服务实例列表。
  • 内置负载均衡算法:提供了内置的负载均衡算法,如轮询(Round Robin)、随机(Random)等。
  • 反应式编程:支持反应式编程模型,允许非阻塞的 I/O 操作和异步处理。
  • 灵活性:与 Ribbon 相比,提供了更多的灵活性和扩展性,可以更容易地定制负载均衡行为。
  • 与 Spring Cloud Gateway 集成:与 Spring Cloud Gateway 紧密集成,为其提供了负载均衡功能。

使用

  1. 添加依赖:在 Maven 或 Gradle 项目中添加 spring-cloud-starter-loadbalancer 依赖。
xml 复制代码
		<!-- SpringCloud Loadbalancer -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
  1. 配置 WebClient:使用 @LoadBalanced 注解来标记一个 WebClient.Builder Bean,以便将其配置为支持负载均衡。
  2. 发送请求:通过 WebClient 发送请求时,使用服务名称(而不是具体的服务实例地址)作为 URI 的主机部分。
  3. 自定义负载均衡算法:如果需要,可以自定义负载均衡算法,并通过配置或编程方式将其应用到 WebClient 上。

注意事项

  1. 阻塞调用:虽然 WebClient 是反应式的,但在某些情况下(如与同步代码交互时),可能需要使用 block() 方法来阻塞当前线程并等待响应。但应尽量避免在反应式上下文中使用 block()。
  2. 配置:负载均衡器的行为可以通过配置进行定制,包括选择负载均衡算法、设置超时时间等。
  3. 服务发现:确保应用程序已经正确配置了服务发现组件(如 Eureka、Consul 等),以便 spring-cloud-starter-loadbalancer 能够获取服务实例列表。
  4. 版本兼容性:注意 spring-cloud-starter-loadbalancer 与其他 Spring Cloud 组件的版本兼容性,确保它们能够协同工作。

负载均衡算法

1. 轮询负载均衡策略(Round Robin)

  • 描述:这是默认的负载均衡策略,它会按照顺序依次将请求发送到服务实例列表中的每个服务实例。
  • 特点
    • 简单易实现。
    • 每个服务实例接收到的请求数量大致相等(在理想情况下)。
    • 不考虑服务实例的当前负载状态或性能。

2. 随机负载均衡策略(Random)

  • 描述:该策略会随机选择一个服务实例来发送请求。
  • 特点
    • 在多次请求中,每个服务实例都有可能被选中。
    • 与轮询策略相比,它增加了随机性,但每个服务实例接收到的请求数量可能不均等。
    • 同样不考虑服务实例的当前负载状态或性能。

3. 自定义负载均衡策略

  • 描述:除了内置的负载均衡策略外,spring-cloud-starter-loadbalancer 还支持自定义负载均衡策略。
  • 特点
    • 开发者可以根据实际需求实现自己的负载均衡算法。
    • 可以考虑服务实例的当前负载状态、性能、地理位置等多种因素来做出决策。
    • 提供了更高的灵活性和定制性。

4. Nacos 权重负载均衡器

  • 描述:当与 Nacos 服务发现组件一起使用时,可以使用 Nacos 提供的权重负载均衡器。
  • 特点
    • 服务实例可以配置权重值,权重值越高的实例接收到的请求越多。
    • 权重值可以根据服务实例的性能、资源使用情况等因素进行动态调整。
    • 提供了更细粒度的控制,可以根据实际需求进行灵活配置。

5. 自定义算法

通过实现自定义的 ReactorLoadBalancer 来定义自己的负载均衡算法。

  1. 定义自定义的负载均衡器:需要实现 ReactorLoadBalancer 接口或扩展现有的实现(如 RoundRobinLoadBalancer)。
  2. 实现 choose 方法:这是负载均衡算法的核心,它接收一个请求(通常是一个 Request 对象)和一个服务实例列表(ServiceInstanceListSupplier),并返回一个 Mono,表示选定的服务实例。
  3. 配置自定义的负载均衡器 :需要将自定义的负载均衡器配置为 Spring Cloud 的默认负载均衡器。这通常是通过注册一个 ReactorLoadBalancer 的 Bean 来完成的。
    示例:
java 复制代码
import org.springframework.cloud.client.ServiceInstance;  
import org.springframework.cloud.client.loadbalancer.DefaultRequest;  
import org.springframework.cloud.client.loadbalancer.ReactiveLoadBalancer;  
import org.springframework.cloud.client.loadbalancer.ReactiveLoadBalancerFactory;  
import org.springframework.cloud.client.ServiceInstanceChooser;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import reactor.core.publisher.Mono;  
  
import java.util.List;  
  
@Configuration  
public class CustomLoadBalancerConfig {  
  
    @Bean  
    public ReactiveLoadBalancer<ServiceInstance> customLoadBalancer(  
            ReactiveLoadBalancerFactory<ServiceInstance> factory,  
            ObjectProvider<List<ServiceInstance>> serviceInstances) {  
  
        return new ReactiveLoadBalancer<ServiceInstance>() {  
  
            @Override  
            public Mono<Response<ServiceInstance>> choose(Request request) {  
                // 这里是自定义的负载均衡算法实现  
                // 例如,我们可以简单地返回服务实例列表中的第一个实例  
                return Mono.justOrEmpty(serviceInstances.getIfAvailable())  
                        .flatMapMany(List::stream)  
                        .firstElement() // 或者可以实现自己的选择逻辑  
                        .map(Response::just);  
            }  
  
            // 其他必要的方法(如 recordStats, filter, etc.)可以根据需要进行实现  
        };  
    }  
  
    // 如果想要为特定的服务配置自定义的负载均衡器,  
    // 可以通过 ServiceId 来区分并返回不同的 ReactiveLoadBalancer 实例  
    // 例如,public ReactiveLoadBalancer<ServiceInstance> customLoadBalancerForServiceX(...) {...}  
}

spring-cloud-starter-loadbalancer 提供了多种负载均衡算法,包括轮询、随机和自定义策略等。这些算法可以根据实际需求进行选择和配置,以满足不同的负载均衡需求。同时,与 Nacos 服务发现组件的集成还提供了权重负载均衡器的功能,进一步增加了负载均衡的灵活性和可定制性。开发者可以根据自己的业务场景和需求选择适合的负载均衡算法,并对其进行适当的配置和优化,以实现更高效、更可靠的微服务调用。

反应式编程

从 Spring Cloud Greenwich 版本开始,Spring Cloud 引入了对 Project Reactor 的支持,并将负载均衡器从传统的阻塞式(基于 Ribbon)转变为反应式(基于 spring-cloud-starter-loadbalancer)。

反应式编程是一种异步、非阻塞的编程范式,它使用数据流(streams)和变化传播(propagation of change)来处理数据。在反应式编程中,数据不是通过传统的调用和返回机制来传递的,而是通过异步数据流在组件之间传递。

在 spring-cloud-starter-loadbalancer 中,反应式编程主要体现在以下几个方面:

  1. 非阻塞调用:与传统的基于 Ribbon 的阻塞式负载均衡器不同,spring-cloud-starter-loadbalancer 使用反应式编程模型来执行非阻塞的负载均衡请求。这意味着它不会阻塞线程等待响应,而是异步地处理请求和响应。
  2. 响应式类型:负载均衡器的 API 使用了反应式类型,如 Mono 和 Flux,它们是 Project Reactor 提供的反应式类型。Mono 用于表示 0 或 1 个元素的异步序列,而 Flux 用于表示 0 到 N 个元素的异步序列。
  3. 背压(Backpressure):反应式编程支持背压机制,即消费者可以控制生产者生成数据的速度。这在处理大量并发请求时非常有用,可以避免因生产者过快生成数据而导致消费者处理不过来。
  4. 错误处理:反应式编程提供了丰富的错误处理机制,如 onErrorResume、retry 等操作符,可以在发生错误时优雅地处理异常情况。
  5. 组合和转换 :Mono 和 Flux 提供了丰富的操作符,用于组合和转换异步数据流。这使得可以灵活地处理负载均衡请求和响应,满足各种复杂的业务需求。
    简单的示例:
java 复制代码
import org.springframework.cloud.client.ServiceInstance;  
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  
import org.springframework.web.reactive.function.client.WebClient;  
  
import reactor.core.publisher.Mono;  
  
@RestController  
public class MyController {  
  
    private final LoadBalancerClient loadBalancerClient;  
  
    public MyController(LoadBalancerClient loadBalancerClient) {  
        this.loadBalancerClient = loadBalancerClient;  
    }  
  
    @GetMapping("/call-service")  
    public Mono<String> callService() {  
        // 获取服务实例  
        ServiceInstance serviceInstance = loadBalancerClient.choose("my-service").block();  
  
        // 使用 WebClient 发起反应式请求  
        WebClient webClient = WebClient.builder()  
                .baseUrl(serviceInstance.getUri().toString())  
                .build();  
  
        return webClient.get()  
                .uri("/some-endpoint")  
                .retrieve()  
                .bodyToMono(String.class);  
    }  
}

注意:上面的示例中使用了 block() 方法来同步获取服务实例,这在实际应用中可能不是最佳实践。通常,应该在整个调用链中保持反应式编程的异步特性。但是,为了简化示例,这里使用了 block() 方法。在实际应用中,应该将服务实例的获取和请求的发起都转换为反应式操作。

与 OpenFeign 集成

Spring Cloud 应用程序中,spring-cloud-starter-loadbalancer 通常与 spring-cloud-starter-openfeign 或其他 HTTP 客户端(如 WebClient)一起使用,以支持对服务发现的客户端进行负载均衡的调用。

当使用 OpenFeign 声明式 HTTP 客户端时,spring-cloud-starter-loadbalancer 会自动集成以提供负载均衡功能。只需在 pom.xml 或 build.gradle 文件中包含相应的依赖,并在 Feign 客户端接口上使用 @FeignClient 注解指定服务名。
Maven 依赖

xml 复制代码
<dependencies>  
   <!-- ... 其他依赖 ... -->  
   <dependency>  
       <groupId>org.springframework.cloud</groupId>  
       <artifactId>spring-cloud-starter-openfeign</artifactId>  
   </dependency>  
   <dependency>  
       <groupId>org.springframework.cloud</groupId>  
       <artifactId>spring-cloud-starter-loadbalancer</artifactId>  
   </dependency>  
   <!-- ... 其他依赖 ... -->  
</dependencies>

Feign 客户端

java 复制代码
@FeignClient(name = "my-service")  
public interface MyServiceClient {  
   // 定义 HTTP 方法  
   @GetMapping("/some-endpoint")  
   Mono<String> getSomething();  
}

WebClient 集成

使用 WebClient 作为 HTTP 客户端,可以通过 spring-cloud-starter-loadbalancer 来实现服务间的负载均衡调用。需要创建一个 WebClient.Builder bean,并使用 LoadBalancerExchangeFilterFunction 来自动处理服务发现和负载均衡。
配置 WebClient Bean

java 复制代码
@Bean  
public WebClient.Builder webClientBuilder(LoadBalancerClient loadBalancerClient) {  
    return WebClient.builder()  
            .baseUrl("lb://my-service") // 使用 'lb://' 前缀启用负载均衡  
            .filter(new LoadBalancerExchangeFilterFunction(loadBalancerClient));  
}

使用 WebClient 发起请求

java 复制代码
@Autowired  
private WebClient.Builder webClientBuilder;  
  
public Mono<String> callService() {  
    WebClient webClient = webClientBuilder.build();  
    return webClient.get()  
            .uri("/some-endpoint")  
            .retrieve()  
            .bodyToMono(String.class);  
}

注意事项

  • 确保 Spring Cloud 版本支持 spring-cloud-starter-loadbalancer。
  • 从使用 Ribbon 迁移到 spring-cloud-starter-loadbalancer,请注意两者之间的配置差异和 API 更改。
  • 在使用 WebClient 时,确保使用了正确的 URL 前缀(lb://)来启用负载均衡。
  • 在自定义负载均衡器时,确保实现是线程安全的,并且能够处理并发请求。
相关推荐
鸽鸽程序猿3 小时前
【项目】基于Spring全家桶的论坛系统 【下】
后端·spring·restful
Lisonseekpan3 小时前
Spring Boot 中使用 Caffeine 缓存详解与案例
java·spring boot·后端·spring·缓存
小许学java3 小时前
Spring AI快速入门以及项目的创建
java·开发语言·人工智能·后端·spring·ai编程·spring ai
维尔切4 小时前
Nginx 反向代理与负载均衡
运维·nginx·负载均衡
知白守黑2675 小时前
反向代理和负载均衡
运维·负载均衡
kfepiza5 小时前
Spring 如何解决循环依赖 笔记251008
java·spring boot·spring
vadvascascass5 小时前
平滑加权轮询负载均衡的底层逻辑
java·算法·负载均衡
kfepiza7 小时前
Spring的三级缓存原理 笔记251008
笔记·spring·缓存
popoxf16 小时前
spring容器启动流程(反射视角)
java·后端·spring
谷哥的小弟17 小时前
Spring Framework源码解析——ApplicationContextAware
spring·源码