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://: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://: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
這有效地意味著請求已優先考慮同一個客戶服務代理。
類似地,我們可以使用各種其他負載均衡演算法來使用粘性會話、基於提示的負載均衡、區域優先負載均衡等等。