Spring Cloud斷路器

在微服務架構中,我們將系統拆分成了一個個的服務單元,各單元間通過服務註冊與訂閱的方式互相依賴。由於每個單元都在不同的進程中運行,依賴通過遠程調用的方式執行,這樣就有可能因爲網絡原因或是依賴服務自身問題出現調用故障或延遲,而這些問題會直接導致調用方的對外服務也出現延遲,若此時調用方的請求不斷增加,最後就會出現因等待出現故障的依賴方響應而形成任務積壓,最終導致自身服務的癱瘓。

舉個例子,在一個電商網站中,我們可能會將系統拆分成,用戶、訂單、庫存、積分、評論等一系列的服務單元。用戶創建一個訂單的時候,在調用訂單服務創建訂單的時候,會向庫存服務來請求出貨(判斷是否有足夠庫存來出貨)。此時若庫存服務因網絡原因無法被訪問到,導致創建訂單服務的線程進入等待庫存申請服務的響應,在漫長的等待之後用戶會因爲請求庫存失敗而得到創建訂單失敗的結果。如果在高併發情況之下,因這些等待線程在等待庫存服務的響應而未能釋放,使得後續到來的創建訂單請求被阻塞,最終導致訂單服務也不可用。

在微服務架構中,存在着那麼多的服務單元,若一個單元出現故障,就會因依賴關係形成故障蔓延,最終導致整個系統的癱瘓,這樣的架構相較傳統架構就更加的不穩定。爲了解決這樣的問題,因此產生了斷路器模式。

什麼是斷路器?

斷路器模式源於Martin Fowler的Circuit Breaker一文。“斷路器”本身是一種開關裝置,用於在電路上保護線路過載,當線路中有電器發生短路時,“斷路器”能夠及時的切斷故障電路,防止發生過載、發熱、甚至起火等嚴重後果。

在分佈式架構中,斷路器模式的作用也是類似的,當某個服務單元發生故障(類似用電器發生短路)之後,通過斷路器的故障監控(類似熔斷保險絲),向調用方返回一個錯誤響應,而不是長時間的等待。這樣就不會使得線程因調用故障服務被長時間佔用不釋放,避免了故障在分佈式系統中的蔓延。

Netflix Hystrix

在Spring Cloud中使用了Hystrix 來實現斷路器的功能。Hystrix是Netflix開源的微服務框架套件之一,該框架目標在於通過控制那些訪問遠程系統、服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。Hystrix具備擁有回退機制和斷路器功能的線程和信號隔離,請求緩存和請求打包,以及監控和配置等功能。

下面我們來看看如何使用Hystrix。

準備工作

在開始加入斷路器之前,我們先拿之前構建兩個微服務爲基礎進行下面的操作,主要使用下面幾個工程:

  • chapter9-1-1
    • eureka-server工程:服務註冊中心,端口1111
    • compute-service工程:服務單元,端口2222
  • chapter9-1-2
    • eureka-ribbon:通過ribbon實現的服務單元,依賴compute-service的服務,端口3333
    • eureka-feign:通過feign實現的服務單元,依賴compute-service的服務,端口3333

Ribbon中引入Hystrix

  • 依次啓動eureka-server、compute-service、eureka-ribbon工程
  • 訪問http://localhost:1111/可以看到註冊中心的狀態
  • 訪問http://localhost:3333/add,調用eureka-ribbon的服務,該服務會去調用compute-service的服務,計算出10+20的值,頁面顯示30
  • 關閉compute-service服務,訪問http://localhost:3333/add,我們獲得了下面的報錯信息
Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

Sat Jun 25 21:16:59 CST 2016
There was an unexpected error (type=Internal Server Error, status=500).
I/O error on GET request for "http://COMPUTE-SERVICE/add?a=10&b=20": Connection refused: connect; nested exception is java.net.ConnectException: Connection refused: connect

爲了避免出現上面的錯誤,我們來引入斷路器

快速入門

  • pom.xml中引入依賴hystrix依賴
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
  • 在eureka-ribbon的主類RibbonApplication中使用@EnableCircuitBreaker註解開啓斷路器功能:
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class RibbonApplication {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(RibbonApplication.class, args);
    }

}

或者,我們可以這樣寫:

@SpringCloudApplication
public class ConsumerApplication {

    @Bean
    //負載均衡
    @LoadBalanced
    RestTemplate restTemplate(){
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }
}

因爲一個@SpringCloudApplication註解就包含了上面三種註解


package org.springframework.cloud.client;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
  • 改造原來的服務消費方式,新增ComputeService類,在使用ribbon消費服務的函數上增加@HystrixCommand註解來指定回調方法。
@Service
public class ComputeService {

    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "addServiceFallback")
    public String addService() {
        return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
    }

    public String addServiceFallback() {
        return "error";
    }

}
  • 提供rest接口的Controller改爲調用ComputeService的addService
@RestController
public class ConsumerController {

    @Autowired
    private ComputeService computeService;

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String add() {
        return computeService.addService();
    }

}

更多關於Hystrix的使用可參考How To Use

Feign使用Hystrix

注意這裏說的是“使用”,沒有錯,我們不需要在Feigh工程中引入Hystix,Feign中已經依賴了Hystrix,我們可以在未做任何改造前,嘗試下面你的操作:

  • 依次啓動eureka-server、compute-service、eureka-feign工程
  • 訪問http://localhost:1111/可以看到註冊中心的狀態
  • 訪問http://localhost:3333/add,調用eureka-feign的服務,該服務會去調用compute-service的服務,計算出10+20的值,頁面顯示30
  • 關閉compute-service服務,訪問http://localhost:3333/add,我們獲得了下面的報錯信息
Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

Sat Jun 25 22:10:05 CST 2016
There was an unexpected error (type=Internal Server Error, status=500).
add timed-out and no fallback available.

如果您夠仔細,會發現與在ribbon中的報錯是不同的,看到add timed-out and no fallback available這句,或許您已經猜到什麼,看看我們的控制檯,可以看到報錯信息來自hystrix-core-1.5.2.jar,所以在這個工程中,我們要學習的就是如何使用Feign中集成的Hystrix。

  • 使用@FeignClient註解中的fallback屬性指定回調類
@FeignClient(value = "compute-service", fallback = ComputeClientHystrix.class)
public interface ComputeClient {

    @RequestMapping(method = RequestMethod.GET, value = "/add")
    Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b);

}
  • 創建回調類ComputeClientHystrix,實現@FeignClient的接口,此時實現的方法就是對應@FeignClient接口中映射的fallback函數。
@Component
public class ComputeClientHystrix implements ComputeClient {

    @Override
    public Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b) {
        return -9999;
    }

}
  • 再用之前的方法驗證一下,是否在compute-service服務不可用的情況下,頁面返回了-9999。

關於Feign的更多使用方法可參考:Feign

完整示例:Chapter9-1-3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章