簡介
我們繼續以之前部落格的程式碼為基礎,增加Ribbon元件來提供客戶端負載均衡。負載均衡是實現高併發、高效能、可伸縮服務的重要組成部分,它可以把請求分散到一個叢集中不同的伺服器中,以減輕每個伺服器的負擔。客戶端負載均衡是執行在客戶端程式中的,如我們的web專案,然後通過獲取叢集的IP地址列表,隨機選擇一個server傳送請求。相對於服務端負載均衡來說,它不需要消耗伺服器的資源。
基礎環境
- JDK 1.8
- Maven 3.3.9
- IntelliJ 2018.1
- Git
專案原始碼
更新配置
我們這次需要在本地啟動兩個產品服務程式,用來驗證負載均衡,所以需要為第二個程式提供不同的埠。Spring Cloud配置服務中心的配置預設會覆蓋本地系統環境變數,而我們需要通過系統環境變數來設定產品服務的埠,所以需要在配置中心git倉庫中修改產品服務的配置檔案product-service.yml
server:
port: 8081
spring:
cloud:
config:
allow-override: true
override-system-properties: false
複製程式碼
allow-override
的預設值即為true,寫出它來是想作說明,它的意思是允許遠端配置中心的配置項覆蓋本地的配置,並不是說允許本地的配置去覆蓋遠端的配置。當然我們可以把它設定成false,但是為了提供更精確的覆蓋規則,這裡保留了預設值。
我們新增了override-system-properties=false
,即雖然遠端配置中心的配置檔案可以覆蓋本地的配置,但是不要覆蓋本地系統變數。修改完成後提交到git倉庫。
另外,在productService
專案的ProductController
中新增一些log,用來驗證負載均衡是否生效:
package cn.zxuqian.controllers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
private static Logger log = LoggerFactory.getLogger(ProductController.class);
@RequestMapping("/products")
public String productList() {
log.info("Access to /products endpoint");
return "外套,夾克,毛衣,T恤";
}
}
複製程式碼
為web配置Ribbon
首先在pom.xml
中新增Ribbon的依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
複製程式碼
然後修改Application
類,新增如下程式碼:
@EnableCircuitBreaker
@EnableDiscoveryClient
@RibbonClient(name = "product-service")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
@LoadBalanced
public RestTemplate rest(RestTemplateBuilder builder) {
return builder.build();
}
}
複製程式碼
這裡用到了@RibbonClient(name = "product-service")
註解,用來標記此專案為Ribbon負載均衡的客戶端,它需要選擇產品服務叢集中其中的一臺來訪問所需要的服務,這裡的name
屬性對應於productService專案中配置的spring.application.name
屬性。
@LoadBalanced
註解標明瞭RestTemplate
會被配置為自動使用Ribbon的LoadBalancerClient
來選擇服務的uri併傳送請求。
在我們在ProductService
類中新增如下程式碼:
@Service
public class ProductService {
private final RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
public ProductService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@HystrixCommand(fallbackMethod = "backupProductList")
public String productList() {
List<ServiceInstance> instances = this.discoveryClient.getInstances("product-service");
if(instances != null && instances.size() > 0) {
return this.restTemplate.getForObject(instances.get(0).getUri() + "/products", String.class);
}
return "";
}
public String backupProductList() {
return "夾克,毛衣";
}
public String productListLoadBalanced() {
return this.restTemplate.getForObject("http://product-service/products", String.class);
}
}
複製程式碼
這裡新新增了一個productListLoadBalanced
方法,跟之前的productList
方法訪問的是同一服務,只不過是用Ribbon Client去做了負載均衡,這裡的uri的host變成了product-service
即要訪問的服務的名字,跟@RibbonClient
中配置的name
屬性保持一致。最後在我們的ProductController
中新增下面的程式碼:
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/products")
public String productList() {
return productService.productList();
}
@RequestMapping("/productslb")
public String productListLoadBalanced() {
return productService.productListLoadBalanced();
}
}
複製程式碼
來建立一個專門處理/productslb
請求的方法,呼叫productServie
提供負載均衡的方法。
到這裡我們的程式碼就完成了,程式碼看似簡單,其實是所有的配置都使用了預設值。Ribbon提供了程式設計式和配置式兩種方式來配置Ribbon Client。現簡單介紹下,後續深入Ribbon時再和大家一起看看如何修改它的配置。Ribbon提供如下配置(左邊是介面,右邊是預設實現):
IClientConfig
ribbonClientConfig: DefaultClientConfigImplIRule ribbonRule
: ZoneAvoidanceRuleIPing ribbonPing
: DummyPingServerList<Server>
ribbonServerList: ConfigurationBasedServerListServerListFilter<Server>
ribbonServerListFilter: ZonePreferenceServerListFilterILoadBalancer
ribbonLoadBalancer: ZoneAwareLoadBalancerServerListUpdater
ribbonServerListUpdater: PollingServerListUpdater
因為我們這個專案用了Eureka,所以有些配置項和預設實現有所不同,如Eureka使用DiscoveryEnabledNIWSServerList
取代ribbonServerList
來獲取在Eureka上註冊的服務的列表。下邊有一個簡單的Congiguration類,來自Spring官網:
public class SayHelloConfiguration {
@Autowired
IClientConfig ribbonClientConfig;
@Bean
public IPing ribbonPing(IClientConfig config) {
return new PingUrl();
}
@Bean
public IRule ribbonRule(IClientConfig config) {
return new AvailabilityFilteringRule();
}
}
複製程式碼
Ribbon預設不會傳送Ping檢查server的健康狀態,預設均正常,然後IRune預設實現為ZoneAvoidanceRule
用來避免AWS EC2問題較多的zone,這在本地測試環境來說是用不到的,然後替換成了AvailabilityFilteringRule
,這個可以開啟Ribbon自帶的斷路器功能,來過濾不正常工作的伺服器。
測試
首先啟動我們的configserver
配置中心服務,然後啟動registry
Eureka註冊與發現服務,然後啟動兩個productService
,第一個我們可以正常使用spring-boot:run
外掛來啟動,第二個我們需要給它提供一個新的埠,可以用如下命令啟動:
$ SERVER_PORT=8082 mvn spring-boot:run
複製程式碼
最後啟動我們的web
客戶端專案,訪問http://localhost:8080/productslb
,然後重新整理幾次,會看到執行著productService
的兩個命令列視窗會隨機出現我們的log:
Access to /products endpoint
複製程式碼
歡迎訪問我的部落格張旭乾的部落格