Spring Cloud 简明教程

Spring Cloud - Load Balancer

Introduction

在分布式环境中,服务需要相互通信。通信可以同步或异步进行。服务同步通信时,最好让这些服务在工作人员之间负载均衡请求,这样就不会让某个工作人员不胜负荷。有两种方法可以对请求进行负载均衡

  1. Server-side LB − 软件在工作人员的前面,并将传入的请求分配给工作人员。

  2. Client-side LB − 调用者服务自身将请求分配给工作人员。客户端负载均衡的优点是我们不需要以负载均衡器的形式来拥有单独的组件。我们不需要负载均衡器的高可用性等。此外,我们不需要从客户端到负载均衡器到工作人员的额外中转就可以完成请求。因此,我们可以节省延迟、基础设施和维护成本。

Spring Cloud 负载均衡器 ( SLB ) 和 Netflix Ribbon 两个著名的客户端负载均衡器,用于处理此类情况。在本教程中,我们将使用 Spring Cloud 负载均衡器。

Load Balancer Dependency Setting

让我们使用我们在前几章中已经使用过的餐厅案例。让我们重新使用拥有餐厅所有信息的餐厅服务。请注意,我们会将 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);
   }
}

Using Spring Load Balancer with 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());
   }
}

现在我们已经完成了设置,让我们尝试一下。这里有一点背景知识,我们要执行以下操作——

  1. Start the Eureka Server.

  2. 启动两个客户服务实例。

  3. 启动一个餐厅服务,它在内部调用客户服务并使用 Spring Cloud 负载均衡器

  4. 对餐厅服务执行四次 API 调用。理想情况下,每个客户服务将服务两个请求。

假设我们已启动 Eureka 服务器和客户服务实例,现在让我们编译餐厅服务代码并使用以下命令执行:

java -Dapp_port=8082 -jar .\target\spring-cloud-feign-client-1.0.jar

现在,让我们通过访问以下 API 来查找位于 DC 的 Jane 的餐厅 [role="bare"] [role="bare"]http://localhost:8082/restaurant/customer/1 ,并让我们再次访问相同的 API 三次。你会从客户服务的日志中注意到两个实例都服务了 2 个请求。每个客户服务 shell 都会打印以下内容:

Querying customer for id with: 1
Querying customer for id with: 1

这实际上意味着请求是循环轮播的。

Configuring Spring Load Balancer

我们可以配置负载均衡器来更改算法类型,或者我们还可以提供自定义算法。让我们看看如何调整我们的负载均衡器以优先考虑相同客户端的请求。

为此,让我们更新 Feign Client 以包含负载均衡器定义。

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 客户端的负载均衡器的类型。我们可以为负载均衡器创建配置类,并将类传递到注解本身。现在让我们定义 LoadBalancerConfiguratio.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);
      }
}

现在,如你所见,我们已设置客户端负载均衡,以每次都优先考虑同一个实例。现在我们已经完成了设置,让我们尝试一下。这里有一个背景知识,我们将执行以下操作:

  1. Start the Eureka Server.

  2. 启动两个客户服务实例。

  3. 启动一个餐厅服务,它在内部调用客户服务并使用 Spring Cloud 负载均衡器

  4. 对餐厅服务执行 4 次 API 调用。理想情况下,所有四个请求都将由相同的客户服务处理。

假设我们已启动 Eureka 服务器和客户服务实例,现在让我们编译餐厅服务代码并使用以下命令执行:

java -Dapp_port=8082 -jar .\target\spring-cloud-feign-client-1.0.jar

现在,让我们通过访问以下 API 来查找位于 DC 的 Jane 的餐厅 [role="bare"] [role="bare"]http://localhost:8082/restaurant/customer/1 ,并让我们再次访问相同的 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

这实际上意味着请求优先考虑相同的客户服务代理。

在类似的情况下,我们可以使用各种其他负载均衡算法来使用粘滞会话、基于提示的负载均衡、区域偏好负载均衡等。