LoadBalancer

LoadBalancer概述

负载均衡是什么

负载均衡(Load Balancing)是一种将用户请求合理分配到多个服务实例的技术,用于提升系统的高可用性(High Availability)与并发处理能力。通过负载均衡,可以避免单个节点过载,确保系统整体运行稳定、高效。

常见的负载均衡实现方式包括:

  • 软件层面:Nginx、LVS、HAProxy 等
  • 硬件层面:F5、A10 等专业负载均衡设备

Spring Cloud LoadBalancer 是什么

  1. Spring Cloud LoadBalancer 是由 Spring Cloud 官方提供的一个轻量级、可扩展的客户端负载均衡组件,用于替代早期的Netflix Ribbon
  2. 它属于spring-cloud-commons模块的一部分,提供了与 Ribbon 类似的负载均衡能力,但实现更简洁、扩展性更强。
  3. 除了支持 RestTemplate 外,Spring Cloud LoadBalancer 还原生支持 WebClient(Spring WebFlux 提供的响应式异步 HTTP 客户端)。
  4. 官网文档:https://spring.io/guides/gs/spring-cloud-loadbalancer

LoadBalancer 和 Nginx

  1. Nginx服务端负载均衡。客户端的所有请求首先发送到 Nginx,由 Nginx 根据配置(如轮询、权重、IP 哈希等)决定将请求转发到哪一个服务实例上。
    • 负载均衡逻辑在服务器端执行。
  2. Spring Cloud LoadBalancer客户端负载均衡
    • 当服务消费者调用远程服务时,会先从注册中心获取可用服务实例列表并缓存到本地(JVM 内存中)。
    • 每次调用时,根据负载均衡算法(如轮询、随机等)在本地选择一个实例进行调用。
    • 负载均衡逻辑在客户端完成。

关于Ribbon

  1. Spring Cloud Ribbon 是基于Netflix Ribbon实现的客户端负载均衡工具。
  2. Ribbon 是 Netflix 开源的客户端负载均衡库,提供多种负载均衡算法和可配置选项(如连接超时、重试策略等)。
  3. 随着 Spring Cloud 官方组件的演进,Ribbon 已进入维护模式,官方推荐使用 Spring Cloud LoadBalancer 作为替代方案。

负载均衡演示

架构说明

  1. 订单服务(cloud-consumer-order80) 通过轮询负载均衡的方式访问多个支付服务实例(cloud-provider-payment8001、8002、8003),实现请求分发与高可用。

  2. Spring Cloud LoadBalancer 的工作流程分为两个阶段:

    • 第一步:服务发现

      LoadBalancer 首先从服务注册中心(如 Consul、Eureka、Nacos 等)拉取可用服务实例列表。例如,支付服务共有三个实例:8001、8002、8003,这些实例提供相同的功能,客户端可以任选其一进行调用。类比现实场景,就像挂号系统中某个科室的多位医生,患者可以选择其中任意一位就诊。

    • 第二步:负载均衡选择

      客户端根据负载均衡算法(如轮询、随机等)从本地缓存的服务列表中选择一个实例地址并发起请求。因此,LoadBalancer 是一种客户端负载均衡机制,负载均衡的决策逻辑发生在客户端,而不是服务端。

演示步骤

参考官方文档:https://docs.spring.io/spring-cloud-commons/reference/spring-cloud-commons/loadbalancer.html

创建多个支付模块

  1. 复制8001的配置,并覆盖端口号属性,然后运行多个支付模块。这里为了让Services面板看起来整洁,对多个服务进行了分组(选择某个服务,右键选择group configurations)
image.png
  1. 启动Consul,然后启动8001、8002、8003、80四个模块
image.png
  1. 查看Consul控制台:http://localhost:8500/
img

支付模块8001新增controller

package com.sun.cloud.controller;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class ConsulController {
 
    @Value("${server.port}")
    private String port;
 
    @GetMapping(value = "/consul/info")
    private String getInfoByConsul() {
        return "支付模块提供服务,端口号为:" + port;
    }
}

订单模块80增加RestTemplate配置类

之前已经创建过这个类了,就不用再创建了。这里只是说明一下,该类是进行远程方法调用必须创建的。当然,后面可以用OpenFeign来替代RestTemplate。

package com.sun.cloud.config;
 
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
 
@Configuration
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

订单模块80新增controller

在controller中,通过RestTemplate来调用8001中的方法。

package com.sun.cloud.controller;
 
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
 
@RestController
public class ConsulController {
 
    @Resource
    RestTemplate restTemplate;
 
    // 远程服务地址
    private static final String PaymentSrv_URL = "http://cloud-payment-service";
 
    @GetMapping(value = "/consul/info")
    public String getPaymentInfo() {
        return restTemplate.getForObject(PaymentSrv_URL + "/consul/info", String.class);
    }
}

测试

重启8001、8002、8003、80四个模块,进行测试

image.pngimage.pngimage.png

根据上图可知道。不需要添加任何配置,默认已经实现了负载均衡。因为spring-cloud-starter-consul-discovery中已经引用了spring-cloud-starter-loadbalancer依赖。所以可以不用在80订单模块的pom.xml文件中再显式引入该依赖。

image.png

如果其他依赖没有引入loadbalancer的依赖,则必须在pom.xml进行添加。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

负载均衡算法原理

  1. 官网说明:https://docs.spring.io/spring-cloud-commons/reference/spring-cloud-commons/loadbalancer.html#switching-between-the-load-balancing-algorithms

  2. 默认实现的算法有两种:

RoundRobinLoadBalancer

RoundRobinLoadBalancer 是 Spring Cloud LoadBalancer 的默认策略。它会按照请求顺序,依次循环选择服务实例

核心原理:

  1. 每次请求时,内部维护一个自增计数器(AtomicInteger)。
  2. 通过 currentIndex = (counter++) % instanceCount 计算出目标实例的下标。
  3. 实现简单,能保证请求均匀分布在所有可用实例上。

优点:

  • 简单高效,适合大多数场景。
  • 不依赖实例性能或响应时间。

缺点:

  • 未考虑实例健康状态和性能差异。
RandomLoadBalancer

RandomLoadBalancer 使用 随机算法 在服务实例中选择目标节点。每次请求时,从可用实例列表中随机选取一个进行调用。

核心原理:

  1. 获取当前可用实例集合。
  2. 使用 ThreadLocalRandom.current().nextInt(instanceCount) 生成随机下标。
  3. 返回对应实例完成调用。

优点:

  • 简单、均衡,适用于实例数量变化频繁的场景。

缺点:

  • 可能造成短时间内请求集中到同一实例,随机分布不稳定。

获取所有上线的服务列表

通过下面的方法,就能获取到注册进Consul中的所有服务的信息。获取到该信息后,保存在本地,再结合负载均衡算法来选择一个服务实例,就能实现服务的负载均衡了。

package com.sun.cloud.controller;
 
import jakarta.annotation.Resource;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.List;
 
@RestController
public class DiscoveryClientController {
 
    @Resource
    private DiscoveryClient discoveryClient;
 
    // 记录当前是第几次请求
    private Integer requestCount = 0;
 
    @GetMapping("/discovery")
    public String discovery() {
        // 获取服务列表
        List<String> services = discoveryClient.getServices();
        services.forEach(System.out::println);
 
        System.out.println("===================================");
 
        // 获取某个服务的实例列表
        List<ServiceInstance> instances = discoveryClient.getInstances("cloud-payment-service");
        instances.forEach(instance -> {
            System.out.println(instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri());
        });
 
        // 负载均衡获取服务实例
        int index = requestCount % instances.size();
        requestCount++;
        return instances.get(index).getServiceId() + " : " + instances.get(index).getUri();
    }
}

对上面的controller进行测试。

  1. 获取到所有的服务实例
image.png
  1. 通过负载均衡,返回服务实例
image.pngimage.pngimage.png

负载均衡算法切换

定义负载均衡策略

官网说明:https://docs.spring.io/spring-cloud-commons/reference/spring-cloud-commons/loadbalancer.html#custom-loadbalancer-configuration

package com.sun.cloud.config;
 
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
 
// 定义使用的负载均衡器
// 注意:不能使用 @Configuration 标注此类为配置类,否则会抛出异常
public class LoadBalancerConfig {
    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                                            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(loadBalancerClientFactory
                .getLazyProvider(name, ServiceInstanceListSupplier.class),
                name);
    }
}

配置负载均衡策略

由于远程调用使用的是 RestTemplate ,所以要在 RestTemplate 的配置类中加上@LoadBalancerClient注解,并且指明是对哪个服务生效以及采用的负载均衡策略。

  • value:指定服务的名称(即注册到服务注册中心的服务名)。通过该属性,可以为某个具体的服务配置自定义的负载均衡逻辑。
  • configuration:指定一个或多个配置类,这些类可以用来为指定的服务提供自定义的负载均衡逻辑或规则。
package com.sun.cloud.config;
 
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
 
@Configuration
@LoadBalancerClient(value = "cloud-payment-service", configuration = LoadBalancerConfig.class)
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

配置好后,重启80服务。访问:http://localhost/consul/info,每次调用服务的实例就是随机的。

相关文章

评论区