1、简介 {#1简介}
随着微服务架构越来越流行,在不同服务器上运行多个服务变得越来越普遍。本文将带你了解如何使用 Spring Cloud Load Balancer(负载均衡器) 创建容错性更强的应用。
2、负载均衡是什么? {#2负载均衡是什么}
负载均衡是在同一应用的不同实例之间分配流量的过程。
为了容错,每个应用通常都要运行多个实例。因此,当一个服务需要与另一个服务通信时,它需要选择一个特定的实例来发送请求。
负载均衡,有很多算法:
- 随机选择:随机选择一个实例
- 循环:每次按相同顺序选择实例
- 最少连接:选择当前连接最少的实例
- 权重指标:使用权重指标选择最佳实例(例如 CPU 或内存使用率)
- IP 哈希(Hash):使用客户端 IP 的哈希值映射到实例
以上只是负载均衡算法的几个例子,每种算法都有其优缺点。
随机选择和轮循很容易实现,但可能无法优化服务的使用。相反,最少连接和权重指标比较复杂,但通常能创造更优化的服务利用率。IP 哈希可以保证客户端每次都命中同一台实例,意味着实例可以保存一些客户端的状态信息,但它的容错性不强。
3、Spring Cloud Load Balancer 简介 {#3spring-cloud-load-balancer-简介}
Spring Cloud Load Balancer 用来创建以负载均衡方式与其他应用通信的应用。可以使用任意算法,在进行远程服务调用时轻松实现负载均衡。
接下来,我们通过实例来进行说明。首先,创建一个简单的服务器应用。服务器只有一个 HTTP 端点,可以作为多个实例运行。
然后,创建一个客户端应用,使用 Spring Cloud Load Balancer 在服务器的不同实例之间轮流发送请求。
3.1、示例服务器 {#31示例服务器}
创建一个简单的 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、示例客户端 {#32示例客户端}
首先添加 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、其他特性 {#4其他特性}
示例服务器和客户端展示了 Spring Cloud Load Balancer 的一个非常简单的用法。它还有一些其他的特性值得了解。
首先,示例客户端使用了默认的 RoundRobinLoadBalancer
策略。该库还提供了一个 RandomLoadBalancer
类。我们也可以使用任何算法创建自己的 ReactorServiceInstanceLoadBalancer
实现。
此外,该库还提供了一种动态发现服务实例的方法。可以使用 DiscoveryClientServiceInstanceListSupplier
接口来实现这一功能。这对于与 Eureka
或 Zookeeper
等服务发现系统集成非常有用。
除了不同的负载均衡和服务发现功能外,该库还提供了基本的重试功能,底层依赖于 Spring Retry 库,可以在一定的等待期后重试失败的请求,可能会使用相同的实例进行重试。
另一项内置功能是指标,它建立在 Micrometer
库之上。开箱即可获得每个实例的基本服务级指标,也可以添加自己的指标。
最后,Spring Cloud Load Balancer 库提供了使用 LoadBalancerCacheManager
接口缓存服务实例的方式。这是很重要的,因为实际上,查找可用的服务实例很可能涉及远程调用。这意味着查找不经常更改的数据会浪费资源,并且涉及远程调用就可能出现异常。通过缓存可以一定程度上解决这些问题。
5、总结 {#5总结}
负载均衡是构建现代容错系统的重要组成部分,通过 Spring Cloud Load Balancer 可以轻松创建应用,利用各种负载均衡技术将请求分发到不同的服务实例。
参考:https://www.baeldung.com/spring-cloud-load-balancer