Spring Cloud - 负载均衡器
简介
在分布式环境中,服务需要相互通信。通信可以同步进行,也可以异步进行。当服务同步通信时,最好对工作进程中的请求进行负载均衡,以避免单个工作进程过载。有两种方法可以平衡请求负载
服务器端负载均衡 - 工作进程由一个分发传入请求的软件作为前端。
客户端负载均衡 - 调用服务本身将请求分发到工作进程。客户端负载均衡的优点是,我们不需要以负载均衡器的形式拥有单独的组件。我们不需要负载均衡器的高可用性等。此外,我们避免了从客户端到负载均衡器再到工作进程的额外跳转来完成请求。因此,我们节省了延迟、基础设施和维护成本。
Spring Cloud 负载均衡器 (SLB) 和 Netflix Ribbon 是两种众所周知的客户端负载均衡器,用于处理这种情况。在本教程中,我们将使用 Spring Cloud 负载均衡器。
负载均衡器依赖项设置
让我们使用我们在前几章中一直在使用的餐厅案例。让我们重用包含所有餐厅信息的餐厅服务。请注意,我们将与我们的负载均衡器一起使用 Feign 客户端。
首先,让我们使用以下依赖项更新服务的pom.xml:
<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> <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-web</artifactId> </dependency>
我们的负载均衡器将使用 Eureka 作为发现客户端来获取有关工作实例的信息。为此,我们将必须使用 @EnableDiscoveryClient 注解。
package com.tutorialspoint; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients @EnableDiscoveryClient public class RestaurantService{ public static void main(String[] args) { SpringApplication.run(RestaurantService.class, args); } }
将 Spring 负载均衡器与 Feign 一起使用
我们在 Feign 中使用的 @FeignClient 注解实际上包含了负载均衡器客户端的默认设置,该客户端对我们的请求进行轮询。让我们测试一下。这是我们之前 Feign 部分中相同的 Feign 客户端。
package com.tutorialspoint; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @FeignClient(name = "customer-service") public interface CustomerService { @RequestMapping("/customer/{id}") public Customer getCustomerById(@PathVariable("id") Long id); }
这是我们将使用的控制器。同样,这没有改变。
package com.tutorialspoint; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController class RestaurantController { @Autowired CustomerService customerService; static HashMap<Long, Restaurant> mockRestaurantData = new HashMap(); static{ mockRestaurantData.put(1L, new Restaurant(1, "Pandas", "DC")); mockRestaurantData.put(2L, new Restaurant(2, "Indies", "SFO")); mockRestaurantData.put(3L, new Restaurant(3, "Little Italy", "DC")); mockRestaurantData.put(4L, new Restaurant(4, "Pizeeria", "NY")); } @RequestMapping("/restaurant/customer/{id}") public List<Restaurant> getRestaurantForCustomer(@PathVariable("id") Long id) { System.out.println("Got request for customer with id: " + id); String customerCity = customerService.getCustomerById(id).getCity(); return mockRestaurantData.entrySet().stream().filter( entry -> entry.getValue().getCity().equals(customerCity)) .map(entry -> entry.getValue()) .collect(Collectors.toList()); } }
现在我们已经完成了设置,让我们尝试一下。这里有一点背景,我们将执行以下操作:
启动 Eureka 服务器。
启动两个客户服务实例。
启动一个内部调用客户服务并使用 Spring Cloud 负载均衡器的餐厅服务
对餐厅服务进行四次 API 调用。理想情况下,每个客户服务将服务两个请求。
假设我们已经启动了 Eureka 服务器和客户服务实例,现在让我们编译餐厅服务代码并使用以下命令执行:
java -Dapp_port=8082 -jar .\target\spring-cloud-feign-client-1.0.jar
现在,让我们通过访问以下 API https://127.0.0.1:8082/restaurant/customer/1 来查找位于 DC 的 Jane 的餐厅,让我们再次点击同一个 API 三次。您会在客户服务的日志中注意到,这两个实例都服务了 2 个请求。每个客户服务 shell 将打印以下内容:
Querying customer for id with: 1 Querying customer for id with: 1
这有效地意味着请求已进行轮询。
配置 Spring 负载均衡器
我们可以配置负载均衡器来更改算法类型,或者我们可以提供自定义算法。让我们看看如何调整我们的负载均衡器以优先考虑同一客户端的请求。
为此,让我们更新我们的 Feign 客户端以包含负载均衡器定义。
package com.tutorialspoint; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @FeignClient(name = "customer-service") @LoadBalancerClient(name = "customer-service", configuration=LoadBalancerConfiguration.class) public interface CustomerService { @RequestMapping("/customer/{id}") public Customer getCustomerById(@PathVariable("id") Long id); }
如果您注意到了,我们添加了 @LoadBalancerClient 注解,该注解指定了将为此 Feign 客户端使用的负载均衡器类型。我们可以为负载均衡器创建一个配置类,并将该类传递给注解本身。现在让我们定义LoadBalancerConfiguration.java
package com.tutorialspoint; import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class LoadBalancerConfiguration { @Bean public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( ConfigurableApplicationContext context) { System.out.println("Configuring Load balancer to prefer same instance"); return ServiceInstanceListSupplier.builder() .withBlockingDiscoveryClient() .withSameInstancePreference() .build(context); } }
现在,正如您所看到的,我们已经将我们的客户端负载均衡设置为每次都优先考虑相同的实例。现在我们已经完成了设置,让我们尝试一下。这里有一点背景,我们将执行以下操作:
启动 Eureka 服务器。
启动两个客户服务实例。
启动一个内部调用客户服务并使用 Spring Cloud 负载均衡器的餐厅服务
对餐厅服务进行 4 次 API 调用。理想情况下,所有四个请求都将由同一个客户服务处理。
假设我们已经启动了 Eureka 服务器和客户服务实例,现在让我们编译餐厅服务代码,然后使用以下命令执行:
java -Dapp_port=8082 -jar .\target\spring-cloud-feign-client-1.0.jar
现在,让我们通过访问以下 API https://127.0.0.1:8082/restaurant/customer/1 来查找位于 DC 的 Jane 的餐厅,让我们再次点击同一个 API 三次。您会在客户服务的日志中注意到,单个实例服务了所有 4 个请求:
Querying customer for id with: 1 Querying customer for id with: 1 Querying customer for id with: 1 Querying customer for id with: 1
这有效地意味着请求已优先考虑同一个客户服务代理。
类似地,我们可以使用各种其他负载均衡算法来使用粘性会话、基于提示的负载均衡、区域优先负载均衡等等。