1. 簡介
隨着微服務架構越來越流行,運行多個服務並在不同服務器上分佈也變得越來越常見。在本快速教程中,我們將探討如何使用 Spring Cloud Load Balancer 來創建更具容錯性的應用程序。
2. 什麼是負載均衡?
負載均衡是指將流量分配到同一應用程序的不同實例之間。
為了創建一個容錯性強的系統,通常會運行每個應用程序的多個實例。因此,當一個服務需要與另一個服務進行通信時,它需要選擇一個特定的實例來發送其請求。
在負載均衡方面,有許多算法:
- 隨機選擇:隨機選擇一個實例
- 輪詢:每次按照相同的順序選擇一個實例
- 最少連接:選擇當前連接數最少的實例
- 加權指標:使用加權指標來選擇最佳實例(例如,CPU 或內存使用率)
- IP 哈希:使用客户端 IP 的哈希值將其映射到實例
這些只是負載均衡算法中的幾個例子,並且每個算法都有其優缺點。
隨機選擇和輪詢易於實現,但可能無法充分利用服務。相反,最少連接和加權指標則更復雜,但通常可以創建更優化的服務利用率。而 IP 哈希在服務器粘性很重要時非常有用,但它不太容錯。
3. Spring Cloud Load Balancer 簡介
Spring Cloud Load Balancer 庫 允許我們創建以負載均衡方式與其他應用程序通信的應用程序。 我們可以使用任何我們想要的算法,輕鬆實現遠程服務調用時的負載均衡。
為了説明,讓我們看一下一些示例代碼。 我們將從一個簡單的服務器應用程序開始。 服務器將具有一個 HTTP 端點,並且可以作為多個實例運行。
3.1. 示例服務器
對於我們的示例服務器,我們從一個簡單的 Spring Boot 應用程序開始:
@SpringBootApplication
@RestController
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
@Value("${server.instance.id}")
String instanceId;
@GetMapping("/hello")
public String hello() {
return String.format("Hello from instance %s", instanceId);
}
}我們首先注入一個可配置的變量,名為 instanceId。 這使得我們能夠區分多個運行實例。 接下來,我們添加一個單例 HTTP GET 端點,它會回顯一條消息和實例 ID。
默認實例將在端口 8080 上運行,ID 為 1。 要運行第二個實例,只需添加幾個程序參數即可:
--server.instance.id=2 --server.port=80813.2. 示例客户端
現在,讓我們來看一下客户端代碼。 這裏我們使用了 Spring Cloud Load Balancer,所以首先我們將它包含在我們的應用程序中:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>接下來,我們創建一個 ServiceInstanceListSupplier 的實現。 這是一個 Spring Cloud Load Balancer 中的關鍵接口。 它定義了我們如何查找可用的服務實例。
對於我們的示例應用程序,我們將硬編碼兩個不同的實例,這些實例運行在同一台機器上,但使用不同的端口:
class DemoInstanceSupplier implements ServiceInstanceListSupplier {
private final String serviceId;
public DemoInstanceSupplier(String serviceId) {
this.serviceId = serviceId;
}
@Override
public String getServiceId() {
return serviceId;
}
@Override
public Flux<List<ServiceInstance>> get() {
return Flux.just(Arrays
.asList(new DefaultServiceInstance(serviceId + "1", serviceId, "localhost", 8080, false),
new DefaultServiceInstance(serviceId + "2", serviceId, "localhost", 8081, false)));
}
}在實際系統中,我們希望使用一種不硬編碼服務地址的實現。稍後我們將對此進行更深入的探討。
現在,讓我們創建一個 LoadBalancerConfiguration 類:
@Configuration
@LoadBalancerClient(name = "example-service", configuration = DemoServerInstanceConfiguration.class)
class WebClientConfig {
@LoadBalanced
@Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}這個類具有唯一的作用:創建一個負載均衡的 WebClient 構建器,以便進行遠程請求。 請注意,我們的註解使用一個假名稱來表示服務。
之所以這樣做,是因為我們可能無法提前知道運行實例的實際主機名和端口。因此,我們使用假名稱作為佔位符,框架會在選擇運行實例時將其替換為真實值。
接下來,讓我們創建一個 Configuration 類,該類實例化我們的服務實例供應商。請注意,我們使用與上述相同的假名稱:
@Configuration
class DemoServerInstanceConfiguration {
@Bean
ServiceInstanceListSupplier serviceInstanceListSupplier() {
return new DemoInstanceSupplier("example-service");
}
}現在,我們可以創建實際的客户端應用程序。讓我們使用上面定義的 WebClient Bean 向示例服務器發送十個請求:
@SpringBootApplication
public class ClientApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = new SpringApplicationBuilder(ClientApplication.class)
.web(WebApplicationType.NONE)
.run(args);
WebClient loadBalancedClient = ctx.getBean(WebClient.Builder.class).build();
for(int i = 1; i <= 10; i++) {
String response =
loadBalancedClient.get().uri("http://example-service/hello")
.retrieve().toEntity(String.class)
.block().getBody();
System.out.println(response);
}
}
}我們查看輸出結果,可以確認我們正在將負載均衡到兩個不同的實例之間:
Hello from instance 2
Hello from instance 1
Hello from instance 2
Hello from instance 1
Hello from instance 2
Hello from instance 1
Hello from instance 2
Hello from instance 1
Hello from instance 2
Hello from instance 14. 其他功能
示例服務器和客户端展示了 Spring Cloud Load Balancer 的一個非常簡單的用法。但其他庫功能也值得一提。
首先,示例客户端使用了默認的 RoundRobinLoadBalancer 策略。該庫還提供了 RandomLoadBalancer 類。我們還可以使用任何算法創建自己的 ReactorServiceInstanceLoadBalancer 實現。
此外,該庫提供了一種動態發現服務實例的方式。我們使用 DiscoveryClientServiceInstanceListSupplier 接口來實現。這對於與 Eureka 或 Zookeeper 等服務發現系統集成非常有用。
除了不同的負載均衡和服務發現功能外,該庫還提供基本的重試能力。在底層,它最終依賴於 Spring Retry 庫。 這允許我們重試失敗的請求,可能在等待一段時間後使用相同的實例。
另一個內置功能是指標,它建立在 Micrometer 庫之上。默認情況下,我們為每個實例獲得基本的服務級別指標,但我們也可以添加自定義指標。
最後,Spring Cloud Load Balancer 庫提供了一種使用 LoadBalancerCacheManager 接口緩存服務實例的方式。這很重要,因為在現實中,查找可用的服務實例很可能涉及遠程調用。因此,查找不經常更改的數據可能很昂貴,並且它也可能代表應用程序中的一個潛在故障點。 通過使用服務實例緩存,我們的應用程序可以規避這些缺點。
5. 結論
負載均衡是構建現代、容錯性強的系統的關鍵組成部分。使用 Spring Cloud Load Balancer,我們可以輕鬆創建使用各種負載均衡技術將請求分發到不同服務實例的應用。