Spring Cloud入門教程-Ribbon實現客戶端負載均衡

張旭乾發表於2018-05-05

簡介

結構

我們繼續以之前部落格的程式碼為基礎,增加Ribbon元件來提供客戶端負載均衡。負載均衡是實現高併發、高效能、可伸縮服務的重要組成部分,它可以把請求分散到一個叢集中不同的伺服器中,以減輕每個伺服器的負擔。客戶端負載均衡是執行在客戶端程式中的,如我們的web專案,然後通過獲取叢集的IP地址列表,隨機選擇一個server傳送請求。相對於服務端負載均衡來說,它不需要消耗伺服器的資源。

基礎環境

  • JDK 1.8
  • Maven 3.3.9
  • IntelliJ 2018.1
  • Git

專案原始碼

Gitee碼雲

更新配置

我們這次需要在本地啟動兩個產品服務程式,用來驗證負載均衡,所以需要為第二個程式提供不同的埠。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: DefaultClientConfigImpl
  • IRule ribbonRule: ZoneAvoidanceRule
  • IPing ribbonPing: DummyPing
  • ServerList<Server> ribbonServerList: ConfigurationBasedServerList
  • ServerListFilter<Server> ribbonServerListFilter: ZonePreferenceServerListFilter
  • ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer
  • ServerListUpdater 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
複製程式碼

歡迎訪問我的部落格張旭乾的部落格

相關文章