Spring Cloud - 负载均衡器

  • 介绍

    在分布式环境中,服务之间需要相互通信。通信可以同步或异步发生。现在,当服务同步通信时,这些服务最好在工作人员之间对请求进行负载平衡,以免单个工作节点不堪重负。有两种方法可以对请求进行负载均衡
    • 服务端 LB - 服务节点前面有一个软件,该软件在服务节点之间分配传入的请求。
    • 客户端 LB- 调用者服务自己将请求分发给工作人员。客户端负载平衡的好处在于,我们不需要以负载平衡器的形式拥有单独的组件。我们不需要负载均衡器的高可用性等。此外,我们还避免了从客户端到LB到worker需要额外的跳转来满足请求。因此,我们节省了延迟、基础设施和维护成本。Spring Cloud load balancer (SLB)Netflix Ribbon是两个著名的客户端负载均衡器,它们被用来处理这种情况。在本教程中,我们将使用SLB。
    Spring Cloud 负载均衡器(SLB) 和 Netflix Ribbon 是两个著名的客户端负载均衡器,用于处理这种情况。在本教程中,我们将使用SLB。
  • 负载均衡器依赖设置

    让我们使用我们在前几章中使用过的餐厅案例。让我们重用具有餐厅所有信息的餐厅服务。请注意,我们将使用 Feign Client 和负载均衡器。
    首先,让我们更新 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.jc2182;
    
    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);
    
       }
    
    }
    
    
  • 在 Feign 中使用 Spring Load Balancer

    我们在 Feign 中使用的 @FeignClient 注释实际上包含在负载均衡器客户端的默认设置中,该客户端对我们的请求进行轮询。让我们来测试一下。这是我们之前 Feign 部分的同一个 Feign 客户端。
    
    
    package com.jc2182;
    
    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.jc2182;
    
    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());
    
       }
    
    }
    
    
    现在我们已经完成了设置,让我们试一试。这里只是一点背景,我们将做的是以下 -
    • 启动尤里卡服务器。
    • 启动客户服务的两个实例。
    • 启动一个餐厅服务,它内部调用客户服务并使用 Spring Cloud 负载均衡器
    • 对餐厅服务进行四次 API 调用。理想情况下,每个客户服务将处理两个请求。
    假设我们已经启动了 Eureka 服务器和客户服务实例,现在让我们编译餐厅服务代码并使用以下命令执行 -
    
    
    java -Dapp_port=8082 -jar .\target\spring-cloud-feign-client-1.0.jar
    
    
    现在,让我们通过点击以下 API http://localhost:8082/restaurant/customer/1 为位于 DC 的 Jane 找到餐厅,让我们再次点击相同的 API 3 次。您会从客户服务的日志中注意到,这两个实例都服务于 2 个请求。每个客户服务外壳都会打印以下内容 -
    
    
    Querying customer for id with: 1
    
    Querying customer for id with: 1
    
    
    这实际上意味着请求是循环的。
  • 配置 Spring 负载均衡器

    我们可以配置负载均衡器来改变算法的类型,或者我们也可以提供定制的算法。让我们看看如何调整我们的负载均衡器来为请求选择相同的客户端。
    为此,让我们更新我们的 Feign Client 以包含负载均衡器定义。
    
    
    package com.jc2182;
    
    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 客户端的负载均衡器的类型。我们可以为负载均衡器创建一个配置类并将该类传递给注释本身。现在让我们定义LoadBalancerConfiguratio.java
    
    
    package com.jc2182;
    
    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);
    
          }
    
    }
    
    
    现在,如您所见,我们已将客户端负载平衡设置为每次都首选相同的实例。现在我们已经完成了设置,让我们试一试。这里只是一点背景,我们将做的是以下 -
    • 启动尤里卡服务器。
    • 启动客户服务的两个实例。
    • 启动一个餐厅服务,它内部调用客户服务并使用 Spring Cloud 负载均衡器
    • 对餐厅服务进行 4 次 API 调用。理想情况下,所有四个请求都由同一个客户服务提供服务。
    假设我们已经启动了 Eureka 服务器和客户服务实例,现在让我们编译餐厅服务代码,现在使用以下命令执行 -
    
    
    java -Dapp_port=8082 -jar .\target\spring-cloud-feign-client-1.0.jar
    
    
    现在,让我们通过点击以下 API http://localhost:8082/restaurant/customer/1 为位于 DC 的 Jane 找到餐厅,让我们再次点击相同的 API 3 次。您会从客户服务的日志中注意到单个实例服务所有 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
    
    
    这实际上意味着请求首选相同的客户服务代理。
    在类似的线路上,我们可以使用各种其他负载平衡算法来使用粘性会话、基于提示的负载平衡、区域偏好负载平衡等。