Spring Cloud Load Balancer 指南

1、简介

随着微服务架构越来越流行,在不同服务器上运行多个服务变得越来越普遍。本文将带你了解如何使用 Spring Cloud Load Balancer(负载均衡器) 创建容错性更强的应用。

2、负载均衡是什么?

负载均衡是在同一应用的不同实例之间分配流量的过程。

为了容错,每个应用通常都要运行多个实例。因此,当一个服务需要与另一个服务通信时,它需要选择一个特定的实例来发送请求。

负载均衡,有很多算法:

  • 随机选择:随机选择一个实例
  • 循环:每次按相同顺序选择实例
  • 最少连接:选择当前连接最少的实例
  • 权重指标:使用权重指标选择最佳实例(例如 CPU 或内存使用率)
  • IP 哈希(Hash):使用客户端 IP 的哈希值映射到实例

以上只是负载均衡算法的几个例子,每种算法都有其优缺点。

随机选择和轮循很容易实现,但可能无法优化服务的使用。相反,最少连接和权重指标比较复杂,但通常能创造更优化的服务利用率。IP 哈希可以保证客户端每次都命中同一台实例,意味着实例可以保存一些客户端的状态信息,但它的容错性不强。

3、Spring Cloud Load Balancer 简介

Spring Cloud Load Balancer 用来创建以负载均衡方式与其他应用通信的应用。可以使用任意算法,在进行远程服务调用时轻松实现负载均衡。

接下来,我们通过实例来进行说明。首先,创建一个简单的服务器应用。服务器只有一个 HTTP 端点,可以作为多个实例运行。

然后,创建一个客户端应用,使用 Spring Cloud Load Balancer 在服务器的不同实例之间轮流发送请求。

3.1、示例服务器

创建一个简单的 Spring Boot 应用:

@SpringBootApplication
@RestController
public class ServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class, args);
    }

    @Value("${server.instance.id}")
    String instanceId;

    @GetMapping("/hello")
    public String hello() {
        return String.format("Hello from instance %s", instanceId);
    }
}

注入一个名为 instanceId 的可配置变量。这样就能区分多个正在运行的实例。然后,添加一个 HTTP GET 端点,返回信息和实例 ID。

默认实例在 8080 端口运行,ID 为 1

运行第二个实例,只需添加几个启动参数:

--server.instance.id=2 --server.port=8081

3.2、示例客户端

首先添加 Spring Cloud Load Balancer 依赖。

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

接下来,创建 ServiceInstanceListSupplier 的实现。这是 Spring Cloud Load Balancer 的关键接口之一。它定义了如何查找可用的服务实例。

在示例应用中,硬编码示例服务器的两个不同实例。它们在同一台机器上运行,但使用不同的端口:

class DemoInstanceSupplier implements ServiceInstanceListSupplier {
    private final String serviceId;

    public DemoInstanceSupplier(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", 8080, false),
              new DefaultServiceInstance(serviceId + "2", serviceId, "localhost", 8081, false)));
    }
}

创建 LoadBalancerConfiguration 类:

@Configuration
@LoadBalancerClient(name = "example-service", configuration = DemoServerInstanceConfiguration.class)
class WebClientConfig {
    @LoadBalanced
    @Bean
    WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }
}

该类的作用只有一个:创建一个负载均衡的 WebClient builder 来执行远程请求。注意,注解使用了一个伪名称来表示该服务。

这是因为我们很可能无法提前知道运行实例的实际主机名和端口。因此,使用一个伪名称作为占位符,框架会在选择运行实例时替换真实值。

接下来,创建一个配置类来实例化 ServiceInstanceListSupplier。注意,使用了与上面相同的伪名称:

@Configuration
class DemoServerInstanceConfiguration {
    @Bean
    ServiceInstanceListSupplier serviceInstanceListSupplier() {
        return new DemoInstanceSupplier("example-service");
    }
}

现在,创建实际的客户端应用。使用上面的 WebClient Bean 向示例服务器发送十个请求:

@SpringBootApplication
public class ClientApplication {

    public static void main(String[] args) {

        ConfigurableApplicationContext ctx = new SpringApplicationBuilder(ClientApplication.class)
          .web(WebApplicationType.NONE)
          .run(args);

        WebClient loadBalancedClient = ctx.getBean(WebClient.Builder.class).build();

        for(int i = 1; i <= 10; i++) {
            String response =
              loadBalancedClient.get().uri("http://example-service/hello")
                .retrieve().toEntity(String.class)
                .block().getBody();
            System.out.println(response);
        }
    }
}

查看输出结果,可以确认请求在两个不同实例之间进行负载均衡:

Hello from instance 2
Hello from instance 1
Hello from instance 2
Hello from instance 1
Hello from instance 2
Hello from instance 1
Hello from instance 2
Hello from instance 1
Hello from instance 2
Hello from instance 1

4、其他特性

示例服务器和客户端展示了 Spring Cloud Load Balancer 的一个非常简单的用法。它还有一些其他的特性值得了解。

首先,示例客户端使用了默认的 RoundRobinLoadBalancer 策略。该库还提供了一个 RandomLoadBalancer 类。我们也可以使用任何算法创建自己的 ReactorServiceInstanceLoadBalancer 实现。

此外,该库还提供了一种动态发现服务实例的方法。可以使用 DiscoveryClientServiceInstanceListSupplier 接口来实现这一功能。这对于与 EurekaZookeeper 等服务发现系统集成非常有用。

除了不同的负载均衡和服务发现功能外,该库还提供了基本的重试功能,底层依赖于 Spring Retry 库,可以在一定的等待期后重试失败的请求,可能会使用相同的实例进行重试。

另一项内置功能是指标,它建立在 Micrometer 库之上。开箱即可获得每个实例的基本服务级指标,也可以添加自己的指标。

最后,Spring Cloud Load Balancer 库提供了使用 LoadBalancerCacheManager 接口缓存服务实例的方式。这是很重要的,因为实际上,查找可用的服务实例很可能涉及远程调用。这意味着查找不经常更改的数据会浪费资源,并且涉及远程调用就可能出现异常。通过缓存可以一定程度上解决这些问题。

5、总结

负载均衡是构建现代容错系统的重要组成部分,通过 Spring Cloud Load Balancer 可以轻松创建应用,利用各种负载均衡技术将请求分发到不同的服务实例。


参考:https://www.baeldung.com/spring-cloud-load-balancer