Spring Cloud 简明教程
Spring Cloud - Load Balancer
Introduction
在分布式环境中,服务需要相互通信。通信可以同步或异步进行。服务同步通信时,最好让这些服务在工作人员之间负载均衡请求,这样就不会让某个工作人员不胜负荷。有两种方法可以对请求进行负载均衡
In a distributed environment, services need to communicate with each other. The communication can either happen synchronously or asynchronously. Now, when a service communicates synchronously, it is better for those services to load balance the request among workers so that a single worker does not get overwhelmed. There are two ways to load balance the request
-
Server-side LB − The workers are fronted by a software which distributes the incoming requests among the workers.
-
Client-side LB − The caller service themselves distribute the requests among the workers. The benefit of client-side load balancing is that we do not need to have a separate component in the form of a load balancer. We do not need to have high availability of the load balancer etc. Also, we avoid the need to have extra hop from client to LB to worker to get the request fulfilled. So, we save on latency, infrastructure, and maintenance cost.
Spring Cloud 负载均衡器 ( SLB ) 和 Netflix Ribbon 两个著名的客户端负载均衡器,用于处理此类情况。在本教程中,我们将使用 Spring Cloud 负载均衡器。
Spring Cloud load balancer (SLB) and Netflix Ribbon are two well-known client-side load balancer which are used to handle such situation. In this tutorial, we will use Spring Cloud Load Balancer.
Load Balancer Dependency Setting
让我们使用我们在前几章中已经使用过的餐厅案例。让我们重新使用拥有餐厅所有信息的餐厅服务。请注意,我们会将 Feign 客户端与我们的负载均衡器结合使用。
Let’s use the case of restaurant we have been using in the previous chapters. Let us reuse the Restaurant Service which has all the information about the restaurant. Note that we will use Feign Client with our Load balancer.
首先,让我们用以下依赖项更新服务的 pom.xml −
First, let us update the pom.xml of the service with following dependency −
<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 注释。
Our load balancer would be using Eureka as a discovery client to get information about the worker instances. For that, we will have to use @EnableDiscoveryClient annotation.
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 客户端。
@FeignClient annotation that we had used in Feign actually packs in a default setup for the load balancer client which round-robins our request. Let us test this out. Here is the same Feign client from our Feign section earlier.
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);
}
以下是我们将使用的控制器。同上,这没有更改。
And here is the controller which we will use. Again, this has not been changed.
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());
}
}
现在我们已经完成了设置,让我们尝试一下。这里有一点背景知识,我们要执行以下操作——
Now that we are done with the setup, let us give this a try. Just a bit background here, what we will do is the following −
-
Start the Eureka Server.
-
Start two instances of the Customer Service.
-
Start a Restaurant Service which internally calls Customer Service and uses the Spring Cloud Load balancer
-
Make four API calls to the Restaurant Service. Ideally, two requests would be served by each customer service.
假设我们已启动 Eureka 服务器和客户服务实例,现在让我们编译餐厅服务代码并使用以下命令执行:
Assuming, we have started the Eureka server and the Customer service instances, let us now compile the Restaurant Service code and execute with the following command −
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 都会打印以下内容:
Now, let us find restaurants for Jane who is based in DC by hitting the following API [role="bare"]http://localhost:8082/restaurant/customer/1 and let us hit the same API three times again. You would notice from the logs of the Customer Service that both the instances serve 2 requests. Each of the Customer Service shell would print the following −
Querying customer for id with: 1
Querying customer for id with: 1
这实际上意味着请求是循环轮播的。
This effectively means that the request was round robin-ed.
Configuring Spring Load Balancer
我们可以配置负载均衡器来更改算法类型,或者我们还可以提供自定义算法。让我们看看如何调整我们的负载均衡器以优先考虑相同客户端的请求。
We can configure the load balancer to change the type of algorithm or we can also provide customized algorithm. Let us see how to tweak our load balancer to prefer the same client for the request.
为此,让我们更新 Feign Client 以包含负载均衡器定义。
For that purpose, let us update our Feign Client to contain load balancer definition.
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
If you notice, we have added the @LoadBalancerClient annotation which specifies the type of load balancer which would be used for this Feign client. We can create a configuration class for the load balancer and pass on the class to the annotation itself. Now let us define 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);
}
}
现在,如你所见,我们已设置客户端负载均衡,以每次都优先考虑同一个实例。现在我们已经完成了设置,让我们尝试一下。这里有一个背景知识,我们将执行以下操作:
Now, as you see, we have setup our client-side load balancing to prefer the same instance every time. Now that we are done with the setup, let us give this a try. Just a bit background here, what we will do is the following −
-
Start the Eureka Server.
-
Start two instances of the Customer Service.
-
Start a Restaurant Service which internally calls Customer Service and uses the Spring Cloud Load balancer
-
Make 4 API calls to the Restaurant Service. Ideally, all four requests would be served by the same customer service.
假设我们已启动 Eureka 服务器和客户服务实例,现在让我们编译餐厅服务代码并使用以下命令执行:
Assuming, we have started the Eureka server and the Customer service instances, let us now compile the Restaurant Service code and now execute with the following command −
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 个请求:
Now, let us find restaurants for Jane who is based in DC by hitting the following API [role="bare"]http://localhost:8082/restaurant/customer/1 and let us hit the same API three times again. You would notice from the logs of the Customer Service that a single instance serves all 4 requests −
Querying customer for id with: 1
Querying customer for id with: 1
Querying customer for id with: 1
Querying customer for id with: 1
这实际上意味着请求优先考虑相同的客户服务代理。
This effectively means that the requests have preferred the same customer service agent.
在类似的情况下,我们可以使用各种其他负载均衡算法来使用粘滞会话、基于提示的负载均衡、区域偏好负载均衡等。
On similar lines, we can have various other load balancing algorithms to use sticky sessions, hintbased load balancing, zone preference load balancing, and so on.