SpringCloud之Hystrix容錯保護原理及配置


1 什么是災難性雪崩效應?

  如下圖的過程所示,災難性雪崩形成原因就大致如此:

  造成災難性雪崩效應的原因,可以簡單歸結為下述三種:

  • 服務提供者不可用。如:硬件故障、程序BUG、緩存擊穿、並發請求量過大等。
  • 重試加大流量。如:用戶重試、代碼重試邏輯等。
  • 服務調用者不可用。如:同步請求阻塞造成的資源耗盡等。

  雪崩效應最終的結果就是:服務鏈條中的某一個服務不可用,導致一系列的服務不可用,最終造成服務邏輯崩潰。這種問題造成的后果,往往是無法預料的。

2 如何解決災難性雪崩效應?

  解決災難性雪崩效應的方式通常有:降級、隔離、熔斷、請求緩存、請求合並。

  在Spring cloud中處理服務雪崩效應,都需要依賴hystrix組件。在pom文件中都需要引入下述依賴:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

  通常來說,開發的時候,使用ribbon處理服務災難雪崩效應(因此章節2示例均采用Ribbon,章節3是Feign實現方式詳解),開發的成本低。維護成本高。使用feign技術處理服務災難雪崩效應,開發的成本較高,維護成本低。

  2.1 降級

  降級是指,當請求超時、資源不足等情況發生時進行服務降級處理,不調用真實服務邏輯,而是使用快速失敗(fallback)方式直接返回一個托底數據,保證服務鏈條的完整,避免服務雪崩。

  解決服務雪崩效應,都是避免application client請求application service時,出現服務調用錯誤或網絡問題。處理手法都是在application client中實現。我們需要在application client相關工程中導入hystrix依賴信息。並在對應的啟動類上增加新的注解@EnableCircuitBreaker,這個注解是用於開啟hystrix熔斷器的,簡言之,就是讓代碼中的hystrix相關注解生效。

  引入依賴:

<!-- hystrix依賴, 處理服務災難雪崩效應的。 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

  啟動器代碼:

/**
 * @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務容錯能力。
 * 當應用啟用Hystrix服務容錯的時候,必須增加的一個注解。
 */ @EnableCircuitBreaker @EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
    public static void main(String[] args) {
      SpringApplication.run(HystrixApplicationClientApplication.class, args);
    }
}

  在調用application service相關代碼中,增加新的方法注解@HystrixCommand,代表當前方法啟用Hystrix處理服務雪崩效應。

  @HystrixCommand注解中的屬性:fallbackMethod - 代表當調用的application service出現問題時,調用哪個fallback快速失敗處理方法返回托底數據。

  實現類:

@Service
public class HystrixService {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    /**
     * 服務降級處理。
     * 當前方法遠程調用application service服務的時候,如果service服務出現了任何錯誤(超時,異常等)
     * 不會將異常拋到客戶端,而是使用本地的一個fallback(錯誤返回)方法來返回一個托底數據。
     * 避免客戶端看到錯誤頁面。
     * 使用注解來描述當前方法的服務降級邏輯。
     * @HystrixCommand - 開啟Hystrix命令的注解。代表當前方法如果出現服務調用問題,使用Hystrix邏輯來處理。
     *  重要屬性 - fallbackMethod
     *      錯誤返回方法名。如果當前方法調用服務,遠程服務出現問題的時候,調用本地的哪個方法得到托底數據。
     *      Hystrix會調用fallbackMethod指定的方法,獲取結果,並返回給客戶端。
     * @return
     */ @HystrixCommand(fallbackMethod="downgradeFallback")
    public List<Map<String, Object>> testDowngrade() {
        System.out.println("testDowngrade method : " + Thread.currentThread().getName());
        ServiceInstance si = 
                this.loadBalancerClient.choose("eureka-application-service");
        StringBuilder sb = new StringBuilder();
        sb.append("http://").append(si.getHost())
            .append(":").append(si.getPort()).append("/test");
        System.out.println("request application service URL : " + sb.toString());
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Map<String, Object>>> type = 
                new ParameterizedTypeReference<List<Map<String, Object>>>() {
        };
        ResponseEntity<List<Map<String, Object>>> response = 
                rt.exchange(sb.toString(), HttpMethod.GET, null, type);
        List<Map<String, Object>> result = response.getBody();
        return result;
    }

    /**
     * fallback方法。本地定義的。用來處理遠程服務調用錯誤時,返回的基礎數據。
     */
    private List<Map<String, Object>> downgradeFallback(){
        List<Map<String, Object>> result = new ArrayList<>();
        
        Map<String, Object> data = new HashMap<>();
        data.put("id", -1);
        data.put("name", "downgrade fallback datas");
        data.put("age", 0);
        result.add(data);
        return result;
    }    
}

  2.2 緩存

  緩存是指請求緩存。通常意義上說,就是將同樣的GET請求結果緩存起來,使用緩存機制(如redis、mongodb)提升請求響應效率。

  使用請求緩存時,需要注意非冪等性操作對緩存數據的影響

  請求緩存是依托某一緩存服務來實現的。在案例中使用redis作為緩存服務器,那么可以使用spring-data-redis來實現redis的訪問操作。需要在application client相關工程中導入下述依賴:

<!-- hystrix依賴, 處理服務災難雪崩效應的。 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
        
<!-- spring-data-redis spring cloud中集成的spring-data相關啟動器。 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

  在Spring Cloud應用中,啟用spring對cache的支持,需要在啟動類中增加注解@EnableCaching,此注解代表當前應用開啟spring對cache的支持。簡言之就是使spring-data-redis相關的注解生效,如:@CacheConfig、@Cacheable、@CacheEvict等。

  啟動器:

/**
 * @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務容錯能力。
 * 當應用啟用Hystrix服務容錯的時候,必須增加的一個注解。
 */ @EnableCircuitBreaker /**
 * @EnableCaching - 開啟spring cloud對cache的支持。
 * 可以自動的使用請求緩存,訪問redis等cache服務。
 */ @EnableCaching @EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixApplicationClientApplication.class, args);
    }
}

  spring cloud會檢查每個冪等性請求,如果請求完全相同(路徑、參數等完全一致),則首先訪問緩存redis,查看緩存數據,如果緩存中有數據,則不調用遠程服務application service。如果緩存中沒有數據,則調用遠程服務,並將結果緩存到redis中,供后續請求使用。

  如果請求是一個非冪等性操作,則會根據方法的注解來動態管理redis中的緩存數據,避免數據不一致。

  注意:使用請求緩存會導致很多的隱患,如:緩存管理不當導致的數據不同步、問題排查困難等。在商業項目中,解決服務雪崩效應不推薦使用請求緩存。

  實現類:

/**
 * 在類上,增加@CacheConfig注解,用來描述當前類型可能使用cache緩存。
 * 如果使用緩存,則緩存數據的key的前綴是cacheNames。
 * cacheNames是用來定義一個緩存集的前綴命名的,相當於分組。
 */ @CacheConfig(cacheNames={"test.hystrix.cache"})
@Service
public class HystrixService {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    /**
     * 請求緩存處理方法。
     * 使用注解@Cacheable描述方法。配合啟動器中的相關注解,實現一個請求緩存邏輯。
     * 將當期方法的返回值緩存到cache中。
     * 屬性 value | cacheNames - 代表緩存到cache的數據的key的一部分。
     * 可以使用springEL來獲取方法參數數據,定制特性化的緩存key * 只要方法增加了@Cacheable注解,每次調用當前方法的時候,spring cloud都會先訪問cache獲取數據, * 如果cache中沒有數據,則訪問遠程服務獲取數據。遠程服務返回數據,先保存在cache中,再返回給客戶端。 * 如果cache中有數據,則直接返回cache中的數據,不會訪問遠程服務。
     * 
     * 請求緩存會有緩存數據不一致的可能。
     * 緩存數據過期、失效、臟數據等情況。
     * 一旦使用了請求緩存來處理冪等性請求操作。則在非冪等性請求操作中必須管理緩存。避免緩存數據的錯誤。
     * @return
     */ @Cacheable("testCache4Get") public List<Map<String, Object>> testCache4Get() {
        System.out.println("testCache4Get method thread name : " + Thread.currentThread().getName());
        ServiceInstance si = 
                this.loadBalancerClient.choose("eureka-application-service");
        StringBuilder sb = new StringBuilder();
        sb.append("http://").append(si.getHost())
            .append(":").append(si.getPort()).append("/test");
        System.out.println("request application service URL : " + sb.toString());
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Map<String, Object>>> type = 
                new ParameterizedTypeReference<List<Map<String, Object>>>() {
        };
        ResponseEntity<List<Map<String, Object>>> response = 
                rt.exchange(sb.toString(), HttpMethod.GET, null, type);
        List<Map<String, Object>> result = response.getBody();
        return result;
    }
    
    /**
     * 非冪等性操作。用於模擬刪除邏輯。
     * 一旦非冪等性操作執行,則必須管理緩存。就是釋放緩存中的數據。刪除緩存數據。
     * 使用注解@CacheEvict管理緩存。
     * 通過數據cacheNames | value來刪除對應key的緩存。
     * 刪除緩存的邏輯,是在當前方法執行結束后。
     * @return
     */ @CacheEvict("testCache4Get") public List<Map<String, Object>> testCache4Del() {
        ServiceInstance si = 
                this.loadBalancerClient.choose("eureka-application-service");
        StringBuilder sb = new StringBuilder();
        sb.append("http://").append(si.getHost())
            .append(":").append(si.getPort()).append("/test");
        System.out.println("request application service URL : " + sb.toString());
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Map<String, Object>>> type = 
                new ParameterizedTypeReference<List<Map<String, Object>>>() {
        };
        ResponseEntity<List<Map<String, Object>>> response = 
                rt.exchange(sb.toString(), HttpMethod.GET, null, type);
        List<Map<String, Object>> result = response.getBody();
        return result;
    }
}

  2.3 請求合並

  請求合並是指,在一定時間內,收集一定量的同類型請求,合並請求需求后,一次性訪問服務提供者,得到批量結果。這種方式可以減少服務消費者和服務提供者之間的通訊次數,提升應用執行效率。

  未使用請求合並:

            

  使用請求合並:

            

  什么情況下使用請求合並:

  在微服務架構中,我們將一個項目拆分成很多個獨立的模塊,這些獨立的模塊通過遠程調用來互相配合工作,但是,在高並發情況下,通信次數的增加會導致總的通信時間增加,同時,線程池的資源也是有限的,高並發環境會導致有大量的線程處於等待狀態,進而導致響應延遲,為了解決這些問題,我們需要來了解Hystrix的請求合並。

  通常來說,服務鏈條超出4個,不推薦使用請求合並。因為請求合並有等待時間。

  請求合並的缺點:

  設置請求合並之后,本來一個請求可能5ms就搞定了,但是現在必須再等10ms看看還有沒有其他的請求一起的,這樣一個請求的耗時就從5ms增加到15ms了,不過,如果我們要發起的命令本身就是一個高延遲的命令,那么這個時候就可以使用請求合並了,因為這個時候時間窗的時間消耗就顯得微不足道了,另外高並發也是請求合並的一個非常重要的場景

  引入依賴:

<!-- hystrix依賴, 處理服務災難雪崩效應的。 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

  啟動器:

/**
 * @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務容錯能力。
 * 當應用啟用Hystrix服務容錯的時候,必須增加的一個注解。
 */ @EnableCircuitBreaker @EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixApplicationClientApplication.class, args);
    }
}

  使用注解@HystrixCollapser來描述需要合並請求的方法,並提供合並方法使用注解@HystrixCommand來描述。當合並條件(@HystrixCollapser)滿足時,會觸發合並方法(@HystrixCommand)來調用遠程服務並得到結果。

  @HystrixCollapser注解介紹:此注解描述的方法,返回值類型必須是java.util.concurrent.Future類型的。代表方法為異步方法

  @HystrixCollapser注解的屬性:

  batchMethod - 請求合並方法名。

  scope - 請求合並方式。可選值有REQUEST和GLOBAL。REQUEST代表在一個request請求生命周期內的多次遠程服務調用請求需要合並處理,此為默認值。GLOBAL代表所有request線程內的多次遠程服務調用請求需要合並處理。

  timerDelayInMilliseconds - 多少時間間隔內的請求進行合並處理,默認值為10ms。建議設置時間間隔短一些,如果單位時間並發量不大,並沒有請求合並的必要。

  maxRequestsInBatch - 設置合並請求的最大極值,也就是timerDelayInMilliseconds時間內,最多合並多少個請求。默認值是Integer.MAX_VALUE。

  實現類:

@Service
public class HystrixService {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    /**
     * 需要合並請求的方法。
     * 這種方法的返回結果一定是Future類型的。
     * 這種方法的處理邏輯都是異步的。
     * 是application client在一定時間內收集客戶端請求,或收集一定量的客戶端請求,一次性發給application service。
     * application service返回的結果,application client會進行二次處理,封裝為future對象並返回
     * future對象需要通過get方法獲取最終的結果get方法是由控制器調用的。所以控制器調用service的過程是一個異步處理的過程。
     * 合並請求的方法需要使用@HystrixCollapser注解描述。
     * batchMethod - 合並請求后,使用的方法是什么。如果當前方法有參數,合並請求后的方法參數是當前方法參數的集合,如 int id >> int[] ids。
     * scope - 合並請求的請求作用域。可選值有global和request。
     *     global代表所有的請求線程都可以等待可合並。 常用,所有瀏覽器或者請求源(Postman、curl等)調用的請求
     *     request代表一個請求線程中的多次遠程服務調用可合並
     * collapserProperties - 細致配置。就是配置合並請求的特性。如等待多久,如可合並請求的數量。
     *     屬性的類型是@HystrixProperty類型數組,可配置的屬性值可以直接通過字符串或常量類定義。
     *     timerDelayInMilliseconds - 等待時長
     *     maxRequestsInBatch - 可合並的請求最大數量。
     * 
 * 方法處理邏輯不需要實現,直接返回null即可。 * 合並請求一定是可合並的。也就是同類型請求。同URL的請求。
     * @param id
     * @return
     */ @HystrixCollapser(batchMethod = "mergeRequest", 
            scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,  
            collapserProperties = {  
            // 請求時間間隔在20ms之內的請求會被合並為一個請求,默認為10ms
            @HystrixProperty(name = "timerDelayInMilliseconds", value = "20"),
            // 設置觸發批處理執行之前,在批處理中允許的最大請求數。
            @HystrixProperty(name = "maxRequestsInBatch", value = "200"),  
    })  
    public Future<Map<String, Object>> testMergeRequest(Long id){
        return null;
    }
    
    /**
     * 批量處理方法。就是合並請求后真實調用遠程服務的方法。
     * 必須使用@HystrixCommand注解描述,代表當前方法是一個Hystrix管理的服務容錯方法。
     * 是用於處理請求合並的方法。
     * @param ids
     * @return
     */ @HystrixCommand public List<Map<String, Object>> mergeRequest(List<Long> ids){
        ServiceInstance si = 
                this.loadBalancerClient.choose("eureka-application-service");
        StringBuilder sb = new StringBuilder();
        sb.append("http://").append(si.getHost())
            .append(":").append(si.getPort()).append("/testMerge?");
        for(int i = 0; i < ids.size(); i++){
            Long id = ids.get(i);
            if(i != 0){
                sb.append("&");
            }
            sb.append("ids=").append(id);
        }
        System.out.println("request application service URL : " + sb.toString());
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Map<String, Object>>> type = 
                new ParameterizedTypeReference<List<Map<String, Object>>>() {
        };
        ResponseEntity<List<Map<String, Object>>> response = 
                rt.exchange(sb.toString(), HttpMethod.GET, null, type);
        List<Map<String, Object>> result = response.getBody();
        return result;
    }
}

  2.4 熔斷

  當一定時間內,異常請求比例(請求超時、網絡故障、服務異常等)達到閥值時,啟動熔斷器,熔斷器一旦啟動,則會停止調用具體服務邏輯,通過fallback快速返回托底數據,保證服務鏈的完整。

  熔斷有自動恢復機制,如:當熔斷器啟動后,每隔5秒,嘗試將新的請求發送給服務提供者,如果服務可正常執行並返回結果,則關閉熔斷器,服務恢復。如果仍舊調用失敗,則繼續返回托底數據,熔斷器持續開啟狀態。

            

  引入依賴:

<!-- hystrix依賴, 處理服務災難雪崩效應的。 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

  啟動器:

/**
 * @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務容錯能力。
 * 當應用啟用Hystrix服務容錯的時候,必須增加的一個注解。
 */ @EnableCircuitBreaker @EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixApplicationClientApplication.class, args);
    }
}

  熔斷的實現是在調用遠程服務的方法上增加@HystrixCommand注解。當注解配置滿足則開啟或關閉熔斷器。

@Service
public class HystrixService {

    @Autowired
    private LoadBalancerClient loadBalancerClient;
    
    /**
     * 熔斷機制
     * 相當於一個強化的服務降級。 服務降級是只要遠程服務出錯,立刻返回fallback結果 * 熔斷是收集一定時間內的錯誤比例,如果達到一定的錯誤率。則啟動熔斷,返回fallback結果。
     * 間隔一定時間會將請求再次發送給application service進行重試。如果重試成功,熔斷關閉。
     * 如果重試失敗,熔斷持續開啟,並返回fallback數據。
     * @HystrixCommand 描述方法。
     *  fallbackMethod - fallback方法名
     *  commandProperties - 具體的熔斷標准。類型是HystrixProperty數組。
     *   可以通過字符串或常亮類配置。
     *   CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD - 錯誤數量。在10毫秒內,出現多少次遠程服務調用錯誤,則開啟熔斷。
     *       默認20個。 10毫秒內有20個錯誤請求則開啟熔斷。
     *   CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE - 錯誤比例。在10毫秒內,遠程服務調用錯誤比例達標則開啟熔斷。
     *   CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS - 熔斷開啟后,間隔多少毫秒重試遠程服務調用。默認5000毫秒。
     * @return
     */ @HystrixCommand(fallbackMethod = "breakerFallback",
            commandProperties = {
              // 默認20個;10ms內請求數大於20個時就啟動熔斷器,當請求符合熔斷條件時將觸發getFallback()。
              @HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, 
                          value="10"),
              // 請求錯誤率大於50%時就熔斷,然后for循環發起請求,當請求符合熔斷條件時將觸發getFallback()。
              @HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, 
                          value="50"),
              // 默認5秒;熔斷多少秒后去嘗試請求
              @HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, 
                          value="5000")}
    )
    public List<Map<String, Object>> testBreaker() {
        System.out.println("testBreaker method thread name : " + Thread.currentThread().getName());
        ServiceInstance si = 
                this.loadBalancerClient.choose("eureka-application-service");
        StringBuilder sb = new StringBuilder();
        sb.append("http://").append(si.getHost())
            .append(":").append(si.getPort()).append("/test");
        System.out.println("request application service URL : " + sb.toString());
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Map<String, Object>>> type = 
                new ParameterizedTypeReference<List<Map<String, Object>>>() {
        };
        ResponseEntity<List<Map<String, Object>>> response = 
                rt.exchange(sb.toString(), HttpMethod.GET, null, type);
        List<Map<String, Object>> result = response.getBody();
        return result;
    }
    
    private List<Map<String, Object>> breakerFallback(){
        System.out.println("breakerFallback method thread name : " + Thread.currentThread().getName());
        List<Map<String, Object>> result = new ArrayList<>();
        
        Map<String, Object> data = new HashMap<>();
        data.put("id", -1);
        data.put("name", "breaker fallback datas");
        data.put("age", 0);
        
        result.add(data);
        
        return result;
    }
}

  注解屬性描述:

CIRCUIT_BREAKER_ENABLED
"circuitBreaker.enabled";
# 是否開啟熔斷策略。默認值為true。

CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD
"circuitBreaker.requestVolumeThreshold";
# 10ms內,請求並發數超出則觸發熔斷策略。默認值為20。

CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS
"circuitBreaker.sleepWindowInMilliseconds";
# 當熔斷策略開啟后,延遲多久嘗試再次請求遠程服務。默認為5秒。

CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE
"circuitBreaker.errorThresholdPercentage";
# 10ms內,出現錯誤的請求百分比達到限制,則觸發熔斷策略。默認為50%。

CIRCUIT_BREAKER_FORCE_OPEN
"circuitBreaker.forceOpen";
# 是否強制開啟熔斷策略。即所有請求都返回fallback托底數據。默認為false。 
CIRCUIT_BREAKER_FORCE_CLOSED
"circuitBreaker.forceClosed";
# 是否強制關閉熔斷策略。即所有請求一定調用遠程服務。默認為false。

  2.5 隔離

  所謂隔離,就是當服務發生問題時,使用技術手段隔離請求,保證服務調用鏈的完整。隔離分為線程池隔離和信號量隔離兩種實現方式。

  2.5.1 線程池隔離

  所謂線程池隔離,就是將並發請求量大的部分服務使用獨立的線程池處理,避免因個別服務並發過高導致整體應用宕機。

  線程池隔離優點:

  • 使用線程池隔離可以完全隔離依賴的服務,請求線程可以快速放回。
  • 當線程池出現問題時,線程池是完全隔離狀態的,是獨立的,不會影響到其他服務的正常執行。
  • 當崩潰的服務恢復時,線程池可以快速清理並恢復,不需要相對漫長的恢復等待。
  • 獨立的線程池也提供了並發處理能力。

  線程池隔離缺點:
  線程池隔離機制,會導致服務硬件計算開銷加大(CPU計算、調度等),每個命令的執行都涉及到排隊、調度、上下文切換等,這些命令都是在一個單獨的線程上運行的。

                

                   

                

                

  線程池隔離的實現方式同樣是使用@HystrixCommand注解。相關注解配置屬性如下:

  • groupKey - 分組命名,在application client中會為每個application service服務設置一個分組,同一個分組下的服務調用使用同一個線程池。默認值為this.getClass().getSimpleName();
  • commandKey - Hystrix中的命令命名,默認為當前方法的方法名。可省略。用於標記當前要觸發的遠程服務是什么。
  • threadPoolKey - 線程池命名。要求一個應用中全局唯一。多個方法使用同一個線程池命名,代表使用同一個線程池。默認值是groupKey數據。
  • threadPoolProperties - 用於為線程池設置的參數。其類型為HystrixProperty數組。常用線程池設置參數有:
  • coreSize - 線程池最大並發數,建議設置標准為:requests per second at peak when healthy * 99th percentile latency in second + some breathing room。 即每秒最大支持請求數*(99%平均響應時間 + 一定量的緩沖時間(99%平均響應時間的10%-20%))。如:每秒可以處理請求數為1000,99%的響應時間為60ms,自定義提供緩沖時間為60*0.2=12ms,那么結果是 1000*(0.060+0.012) = 72。
  • maxQueueSize - BlockingQueue的最大長度,默認值為-1,即不限制。如果設置為正數,等待隊列將從同步隊列SynchronousQueue轉換為阻塞隊列LinkedBlockingQueue。
  • queueSizeRejectionThreshold - 設置拒絕請求的臨界值。默認值為5。此屬性是配合阻塞隊列使用的,也就是不適用maxQueueSize=-1(為-1的時候此值無效)的情況。是用於設置阻塞隊列限制的,如果超出限制,則拒絕請求。此參數的意義就是在服務啟動后,可以通過Hystrix的API調用config API動態修改,而不用用重啟服務,不常用。
  • keepAliveTimeMinutes - 線程存活時間,單位是分鍾。默認值為1。
  • execution.isolation.thread.timeoutInMilliseconds - 超時時間,默認為1000ms。當請求超時自動中斷,返回fallback,避免服務長期阻塞。
  • execution.isolation.thread.interruptOnTimeout - 是否開啟超時中斷。默認為TRUE。和上一個屬性配合使用。

  引入依賴:

<!-- hystrix依賴, 處理服務災難雪崩效應的。 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

  啟動器:

/**
 * @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務容錯能力。
 * 當應用啟用Hystrix服務容錯的時候,必須增加的一個注解。
 */ @EnableCircuitBreaker @EnableEurekaClient
@SpringBootApplication
public class HystrixApplicationClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixApplicationClientApplication.class, args);
    }
}

  實現類:

@Service
public class HystrixService {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    /**
     * 如果使用了@HystrixCommand注解,則Hystrix自動創建獨立的線程池。
     * groupKey和threadPoolKey默認值是當前服務方法所在類型的simpleName
     * 
     * 所有的fallback方法,都執行在一個HystrixTimer線程池上。
     * 這個線程池是Hystrix提供的一個,專門處理fallback邏輯的線程池。
     * 
     * 線程池隔離實現 * 線程池隔離,就是為某一些服務,獨立划分線程池。讓這些服務邏輯在獨立的線程池中運行。
     * 不使用tomcat提供的默認線程池。
     * 線程池隔離也有熔斷能力。如果線程池不能處理更多的請求的時候,會觸發熔斷,返回fallback數據。
     * groupKey - 分組名稱,就是為服務划分分組。如果不配置,默認使用threadPoolKey作為組名。
     * commandKey - 命令名稱,默認值就是當前業務方法的方法名。
     * threadPoolKey - 線程池命名,真實線程池命名的一部分。Hystrix在創建線程池並命名的時候,會提供完整命名。默認使用gourpKey命名
     *  如果多個方法使用的threadPoolKey是同名的,則使用同一個線程池。
     * threadPoolProperties - 為Hystrix創建的線程池做配置。可以使用字符串或HystrixPropertiesManager中的常量指定。
     *  常用線程池配置:
     *      coreSize - 核心線程數。最大並發數。1000*(99%平均響應時間 + 適當的延遲時間)
     *      maxQueueSize - 阻塞隊列長度。如果是-1這是同步隊列。如果是正數這是LinkedBlockingQueue。如果線程池最大並發數不足,
     *          提供多少的阻塞等待。
     *      keepAliveTimeMinutes - 心跳時間,超時時長。單位是分鍾。
     *      queueSizeRejectionThreshold - 拒絕臨界值,當最大並發不足的時候,超過多少個阻塞請求,后續請求拒絕。
     */
    @HystrixCommand(groupKey="test-thread-quarantine", 
        commandKey = "testThreadQuarantine",
        threadPoolKey="test-thread-quarantine", 
        threadPoolProperties = {
            @HystrixProperty(name="coreSize", value="30"),
            @HystrixProperty(name="maxQueueSize", value="100"),
            @HystrixProperty(name="keepAliveTimeMinutes", value="2"),
            @HystrixProperty(name="queueSizeRejectionThreshold", value="15")
        },
        fallbackMethod = "threadQuarantineFallback")
    public List<Map<String, Object>> testThreadQuarantine() {
        System.out.println("testQuarantine method thread name : " + Thread.currentThread().getName());
        ServiceInstance si = 
                this.loadBalancerClient.choose("eureka-application-service");
        StringBuilder sb = new StringBuilder();
        sb.append("http://").append(si.getHost())
            .append(":").append(si.getPort()).append("/test");
        System.out.println("request application service URL : " + sb.toString());
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Map<String, Object>>> type = 
                new ParameterizedTypeReference<List<Map<String, Object>>>() {
        };
        ResponseEntity<List<Map<String, Object>>> response = 
                rt.exchange(sb.toString(), HttpMethod.GET, null, type);
        List<Map<String, Object>> result = response.getBody();
        return result;
    }
    
    private List<Map<String, Object>> threadQuarantineFallback(){
        System.out.println("threadQuarantineFallback method thread name : " + Thread.currentThread().getName());
        List<Map<String, Object>> result = new ArrayList<>();
        
        Map<String, Object> data = new HashMap<>();
        data.put("id", -1);
        data.put("name", "thread quarantine fallback datas");
        data.put("age", 0);
        
        result.add(data);
        
        return result;
    }
}

  關於線程池:

  • 對於所有請求,都交由tomcat容器的線程池處理,是一個以http-nio開頭的的線程池;
  • 開啟了線程池隔離后,tomcat容器默認的線程池會將請求轉交給threadPoolKey定義名稱的線程池,處理結束后,由定義的線程池進行返回,無需還回tomcat容器默認的線程池。線程池默認為當前方法名;
  • 所有的fallback都單獨由Hystrix創建的一個線程池處理。

  2.5.2 信號量隔離

  所謂信號量隔離,就是設置一個並發處理的最大極值。當並發請求數超過極值時,通過fallback返回托底數據,保證服務完整性。

              

  信號量隔離同樣通過@HystrixCommand注解配置,常用注解屬性有:

  • commandProperty - 配置信號量隔離具體數據。屬性類型為HystrixProperty數組,常用配置內容如下:
  • execution.isolation.strategy - 設置隔離方式,默認為線程池隔離。可選值只有THREAD和SEMAPHORE。
  • execution.isolation.semaphore.maxConcurrentRequests - 最大信號量並發數,默認為10。

  依賴注入和啟動器同線程池隔離,實現類如下:

@Service
public class HystrixService {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    /**
     * 信號量隔離實現
     * 不會使用Hystrix管理的線程池處理請求。使用容器(Tomcat)的線程處理請求邏輯。
     * 不涉及線程切換,資源調度,上下文的轉換等,相對效率高。
     * 信號量隔離也會啟動熔斷機制。如果請求並發數超標,則觸發熔斷,返回fallback數據。
     * commandProperties - 命令配置,HystrixPropertiesManager中的常量或字符串來配置。
     *     execution.isolation.strategy - 隔離的種類,可選值只有THREAD(線程池隔離)和SEMAPHORE(信號量隔離)。
 * 默認是THREAD線程池隔離。 * 設置信號量隔離后,線程池相關配置失效。
     *  execution.isolation.semaphore.maxConcurrentRequests - 信號量最大並發數。默認值是10。常見配置500~1000。
     *      如果並發請求超過配置,其他請求進入fallback邏輯。
     */
    @HystrixCommand(fallbackMethod="semaphoreQuarantineFallback",
            commandProperties={
              @HystrixProperty(
                      name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, 
                      value="SEMAPHORE"), // 信號量隔離
              @HystrixProperty(
                      name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, 
                      value="100") // 信號量最大並發數
    })
    public List<Map<String, Object>> testSemaphoreQuarantine() {
        System.out.println("testSemaphoreQuarantine method thread name : " + Thread.currentThread().getName());
        ServiceInstance si = 
                this.loadBalancerClient.choose("eureka-application-service");
        StringBuilder sb = new StringBuilder();
        sb.append("http://").append(si.getHost())
            .append(":").append(si.getPort()).append("/test");
        System.out.println("request application service URL : " + sb.toString());
        RestTemplate rt = new RestTemplate();
        ParameterizedTypeReference<List<Map<String, Object>>> type = 
                new ParameterizedTypeReference<List<Map<String, Object>>>() {
        };
        ResponseEntity<List<Map<String, Object>>> response = 
                rt.exchange(sb.toString(), HttpMethod.GET, null, type);
        List<Map<String, Object>> result = response.getBody();
        return result;
    }
    
    private List<Map<String, Object>> semaphoreQuarantineFallback(){
        System.out.println("threadQuarantineFallback method thread name : " + Thread.currentThread().getName());
        List<Map<String, Object>> result = new ArrayList<>();
        
        Map<String, Object> data = new HashMap<>();
        data.put("id", -1);
        data.put("name", "thread quarantine fallback datas");
        data.put("age", 0);
        
        result.add(data);
        
        return result;
    }
}

  2.5.3線程池隔離和信號量隔離的對比

                 

  2.5.4線程池隔離和信號量隔離的選擇

  • 線程池隔離:請求並發大,耗時較長(一般都是計算大,服務鏈長或訪問數據庫)時使用線程池隔離。可以盡可能保證外部容器(如Tomcat)線程池可用,不會因為服務調用的原因導致請求阻塞等待。
  • 信號量隔離:請求並發大,耗時短計算小,服務鏈段或訪問緩存)時使用信號量隔離。因為這類服務的響應快,不會占用外部容器(如Tomcat)線程池太長時間,減少線程的切換,可以避免不必要的開銷,提高服務處理效率。

3 Feign的雪崩處理

  在聲明式遠程服務調用Feign中,實現服務災難性雪崩效應處理也是通過Hystrix實現的。而feign啟動器spring-cloud-starter-feign中是包含Hystrix相關依賴的。如果只使用服務降級、熔斷功能不需要做獨立依賴。如果需要使用Hystrix其他服務容錯能力,需要依賴spring-cloud-starter-hystrix資源。

<!-- hystrix依賴。 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

  從Dalston版本后,feign默認關閉Hystrix支持。所以必須在全局配置文件中開啟feign技術中的Hystrix支持。配置如下:

feign.hystrix.enabled=true

  如果不使用Hystrix服務容錯功能,在application client端,服務接口只需要繼承服務標准api接口即可實現遠程服務調用。如果使用了Hystrix,則有不同的編寫方式。具體如下。

  3.1 代碼實現 - 接口實現類方式

  定義和服務標准api相同的application client服務接口。並通過@FeignClient注解來描述fallback方法所在類是什么。這個fallback方法所在類就是接口的實現類,實現的方法就是接口中定義方法的fallback方法。

/**
 * 如果在Feign中使用Hystrix,則不能直接繼承服務標准接口。
 * 因為繼承接口,一般都不會給予實現。會缺少fallback方法。熔斷機制鏈條不完整。
 * 在當前接口中,重復定義服務標准接口中定義的方法。 * 遠程服務調用的時候,是通過@FeignClient實現的。 * 如果遠程服務調用失敗,則觸發fallback注解屬性定義的接口實現類中的對應方法,作為fallback方法。
 * 
 * 在默認的Hystrix配置環境中,使用的是服務降級保護機制。
 * 
 * 服務降級,默認的情況下,包含了請求超時。
 * feign聲明式遠程服務調用,在啟動的時候,初始化過程比較慢(通過注釋@FeignClient描述接口,接口生成動態代理對象,實現服務調用)。比ribbon要慢很多。
 * 很容易在第一次訪問的時候,產生超時。導致返回fallback數據。
 */ @FeignClient(name="test-feign-application-service",
        fallback=FirstClientFeignServiceImpl.class
        )
public interface FirstClientFeignService{

    @RequestMapping(value="/testFeign", method=RequestMethod.GET)
    public List<String> testFeign();
    
    @RequestMapping(value="/get", method=RequestMethod.GET)
    public FeignTestPOJO getById(@RequestParam(value="id") Long id);
    
    @RequestMapping(value="/get", method=RequestMethod.POST)
    public FeignTestPOJO getByIdWithPOST(@RequestBody Long id);
    
    @RequestMapping(value="/add", method=RequestMethod.GET)
    public FeignTestPOJO add(@RequestParam("id") Long id, @RequestParam("name") String name);
    
    @RequestMapping(value="/addWithGET", method=RequestMethod.GET)
    public FeignTestPOJO add(@RequestBody FeignTestPOJO pojo);
    
    @RequestMapping(value="/addWithPOST", method=RequestMethod.POST)
    public FeignTestPOJO addWithPOST(@RequestBody FeignTestPOJO pojo);
    
}

  為接口提供實現類,類中的方法實現就是fallback邏輯。實現類需要spring容器管理,使用@Component注解來描述類型。

/**
 * 實現類中的每個方法,都是對應的接口方法的fallback。
 * 一定要提供spring相關注解(@Component/@Service/@Repository等)。
 * 注解是為了讓當前類型的對象被spring容器管理。
 * fallback是本地方法。 * 是接口的實現方法。 */
@Component
public class FirstClientFeignServiceImpl implements FirstClientFeignService {

    @Override
    public List<String> testFeign() {
        List<String> result = new ArrayList<>();
        result.add("this is testFeign method fallback datas");
        return result;
    }

    @Override
    public FeignTestPOJO getById(Long id) {
        return new FeignTestPOJO(-1L, "this is getById method fallback datas");
    }

    @Override
    public FeignTestPOJO getByIdWithPOST(Long id) {
        return new FeignTestPOJO(-1L, "this is getByIdWithPOST method fallback datas");
    }

    @Override
    public FeignTestPOJO add(Long id, String name) {
        return new FeignTestPOJO(-1L, "this is add(id, name) method fallback datas");
    }

    @Override
    public FeignTestPOJO add(FeignTestPOJO pojo) {
        return new FeignTestPOJO(-1L, "this is add(pojo) method fallback datas");
    }

    @Override
    public FeignTestPOJO addWithPOST(FeignTestPOJO pojo) {
        return new FeignTestPOJO(-1L, "this is addWithPOST method fallback datas");
    }
} 

  3.2 相關配置

  在Feign技術中,一般不使用請求合並,請求緩存等容錯機制。常用的機制是隔離,降級和熔斷。

  3.2.1 Properties全局配置

# hystrix.command.default和hystrix.threadpool.default中的default為默認CommandKey,CommandKey默認值為服務方法名。
# 在properties配置中配置格式混亂,如果需要為每個方法設置不同的容錯規則,建議使用yml文件配置。 # Command Properties # Execution相關的屬性的配置: # 隔離策略,默認是Thread, 可選Thread|Semaphore hystrix.command.default.execution.isolation.strategy=THREAD #命令執行超時時間,默認1000ms,只在線程池隔離中有效。 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1000 # 執行是否啟用超時,默認啟用true,只在線程池隔離中有效。 hystrix.command.default.execution.timeout.enabled=true # 發生超時是是否中斷,默認true,只在線程池隔離中有效。 hystrix.command.default.execution.isolation.thread.interruptOnTimeout=true # 最大並發請求數,默認10,該參數當使用ExecutionIsolationStrategy.SEMAPHORE策略時才有效。如果達到最大並發請求數,請求會被拒絕。
# 理論上選擇semaphore的原則和選擇thread一致,但選用semaphore時每次執行的單元要比較小且執行速度快(ms級別),否則的話應該用thread。
# semaphore應該占整個容器(tomcat)的線程池的一小部分。 hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests=10 # 如果並發數達到該設置值,請求會被拒絕和拋出異常並且fallback不會被調用。默認10。
# 只在信號量隔離策略中有效,建議設置大一些,這樣並發數達到execution最大請求數時,會直接調用fallback,而並發數達到fallback最大請求數時會被拒絕和拋出異常。 hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests=10
# ThreadPool 相關參數
# 並發執行的最大線程數,默認10
hystrix.threadpool.default.coreSize=10
# BlockingQueue的最大隊列數,當設為-1,會使用SynchronousQueue,值為正時使用LinkedBlcokingQueue。
# 該設置只會在初始化時有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默認-1。 hystrix.threadpool.default.maxQueueSize=-1 # 即使maxQueueSize沒有達到,達到queueSizeRejectionThreshold該值后,請求也會被拒絕。 hystrix.threadpool.default.queueSizeRejectionThreshold=20 # 線程存活時間,單位是分鍾。默認值為1。 hystrix.threadpool.default.keepAliveTimeMinutes=1
# Fallback相關的屬性 
# 當執行失敗或者請求被拒絕,是否會嘗試調用fallback方法 。默認true

hystrix.command.default.fallback.enabled=true

# Circuit Breaker相關的屬性
# 是否開啟熔斷器。默認true

hystrix.command.default.circuitBreaker.enabled=true
# 一個rolling window內最小的請求數。如果設為20,那么當一個rolling window的時間內(比如說1個rolling window是10毫秒)收到19個請求
# 即使19個請求都失敗,也不會觸發circuit break。默認20 hystrix.command.default.circuitBreaker.requestVolumeThreshold=20 # 觸發短路的時間值,當該值設為5000時,則當觸發circuit break后的5000毫秒內都會拒絕遠程服務調用,也就是5000毫秒后才會重試遠程服務調用。默認5000 hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000 # 錯誤比率閥值,如果錯誤率>=該值,circuit會被打開,並短路所有請求觸發fallback。默認50 hystrix.command.default.circuitBreaker.errorThresholdPercentage=50 # 強制打開熔斷器 hystrix.command.default.circuitBreaker.forceOpen=false # 強制關閉熔斷器 hystrix.command.default.circuitBreaker.forceClosed=false

  3.2.2 YML全局配置

  YML配置文件,對SpringEL的支持更加優秀。可以通過SpringEL定制化的為每個服務調用配置Hystrix的容錯處理方案。對Hystrix的配置粒度相比較Properties的配置方案更加細致。

  在YML中可配置的Hystrix信息,和Properties中配置的內容是一致。

  如果需要對每個服務做定制化配置,建議使用yml配置文件。在語法和格式上更容易管理和維護。

spring:
  application:
    name: test-feign-application-client
server:
  port: 9008

feign:
  hystrix:
    enabled: true

hystrix:
  command:
 # default代表全部服務配置,如果為某個具體服務定制配置,使用:'服務接口名#方法名(參數類型列表)'的方式來定義。
    # 如:'FirstClientFeignService#test(int)'。如果接口名稱在應用中唯一,可以只寫simpleName。
    # 如果接口名稱在應用中不唯一,需要寫fullName(包名.類名) "FirstClientFeignService#testFeign()":
      fallback:
        enabled: true 

  3.3 代碼實現 - Factory實現方式

  在服務接口的@FeignClient注解中,不再使用fallback屬性,而是定義fallbackFactory屬性。這個屬性的類型是Class類型的,用於配置fallback代碼所處的Factory。

  再定義一個Java類,實現接口FallbackFactory,實現其中的create方法。使用匿名內部類的方式,為服務接口定義一個實現類,定義fallback方法實現。

  本地接口定義:

@FeignClient(name="test-feign-application-service",
        fallbackFactory=FirstClientFeignServiceFallbackFactory.class
        )
public interface FirstClientFeignService{

    @RequestMapping(value="/testFeign", method=RequestMethod.GET)
    public List<String> testFeign();
    
    @RequestMapping(value="/get", method=RequestMethod.GET)
    public FeignTestPOJO getById(@RequestParam(value="id") Long id);
    
    @RequestMapping(value="/get", method=RequestMethod.POST)
    public FeignTestPOJO getByIdWithPOST(@RequestBody Long id);
    
    @RequestMapping(value="/add", method=RequestMethod.GET)
    public FeignTestPOJO add(@RequestParam("id") Long id, @RequestParam("name") String name);
    
    @RequestMapping(value="/addWithGET", method=RequestMethod.GET)
    public FeignTestPOJO add(@RequestBody FeignTestPOJO pojo);
    
    @RequestMapping(value="/addWithPOST", method=RequestMethod.POST)
    public FeignTestPOJO addWithPOST(@RequestBody FeignTestPOJO pojo);
    
}

  FallbackFactory實現類:

/** * 使用Factory方式實現Feign的Hystrix容錯處理。 * 編寫的自定義Factory必須實現接口FallbackFactory。 * FallbackFactory中的方法是 * 服務接口的類型 create(Throwable 遠程服務調用的錯誤)
 * 
 * 工廠實現方案和服務接口實現類實現方案的區別:
 *  工廠可以提供自定義的異常信息處理邏輯。因為create方法負責傳遞遠程服務調用的異常對象。
 *  實現類可以快速的開發,但是會丟失遠程服務調用的異常信息。 */
@Component
public class FirstClientFeignServiceFallbackFactory implements FallbackFactory<FirstClientFeignService> {

    Logger logger = LoggerFactory.getLogger(FirstClientFeignServiceFallbackFactory.class);
    
    /**
     * create方法 - 就是工廠的生產產品的方法。
     *  當前工廠生產的產品就是服務接口的Fallback處理對象。 就是服務接口的實現類的對象。
     */
    @Override
    public FirstClientFeignService create(final Throwable cause) {
        
        return new FirstClientFeignService() {
            @Override
            public List<String> testFeign() {
                logger.warn("testFeign() - ", cause);
                List<String> result = new ArrayList<>();
                result.add("this is testFeign method fallback datas");
                return result;
            }

            @Override
            public FeignTestPOJO getById(Long id) {
                return new FeignTestPOJO(-1L, "this is getById method fallback datas");
            }

            @Override
            public FeignTestPOJO getByIdWithPOST(Long id) {
                return new FeignTestPOJO(-1L, "this is getByIdWithPOST method fallback datas");
            }

            @Override
            public FeignTestPOJO add(Long id, String name) {
                return new FeignTestPOJO(-1L, "this is add(id, name) method fallback datas");
            }

            @Override
            public FeignTestPOJO add(FeignTestPOJO pojo) {
                return new FeignTestPOJO(-1L, "this is add(pojo) method fallback datas");
            }

            @Override
            public FeignTestPOJO addWithPOST(FeignTestPOJO pojo) {
                return new FeignTestPOJO(-1L, "this is addWithPOST method fallback datas");
            }
        };
    }
}

  這種實現邏輯的優勢是,可以獲取遠程調用服務的異常信息。為后期異常處理提供參考。

  工廠實現方案和實現類的實現方案,沒有效率和邏輯上的優缺點對比。只是在遠程服務調用異常的處理上有區別。

4 Hystrix Dashboard - 數據監控

  Hystrix dashboard是一款針對Hystrix進行實時監控的工具,通過Hystrix Dashboard我們可以在直觀地看到各Hystrix Command的請求響應時間, 請求成功率等數據。

  4.1 實現單服務單節點數據監控

  在使用了Hystrix技術的application client工程中增加下述依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>

  在啟動器上增加注解@EnableHystrixDashboard、@EnableHystrix

@EnableCircuitBreaker
@EnableCaching
@EnableEurekaClient
@SpringBootApplication
@EnableHystrixDashboard @EnableHystrix public class HystrixDashboardApplication {

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

  啟動工程后,如果觸發了Hystrix,則可以通過http://ip:port/hystrix.stream得到監控數據。這種監控數據的獲取都是JSON數據。且數據量級較大。不易於查看。可以使用Hystrix Dashboard提供的視圖界面來觀察監控結果。視圖界面訪問路徑為http://ip:port/hystrix。視圖界面中各數據的含義如下:

             

  建議:監控中心建議使用獨立工程來實現。這樣更便於維護。

  4.2 使用Turbine實現多服務或集群的數據監控

  Turbine是聚合服務器發送事件流數據的一個工具,hystrix的監控中,只能監控單個服務或單個節點,實際生產中都為多服務集群,因此可以通過turbine來監控多集群服務。

  Turbine在Hystrix Dashboard中的作用如下:

          

  4.2.1多服務監控

  當使用Turbine來監控多服務狀態時,需提供一個獨立工程來搭建Turbine服務邏輯。並在工程中增加下述依賴:

<!-- Dashboard需要的依賴信息。 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
<!-- Turbine需要的依賴信息。 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-turbine</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix-turbine</artifactId>
</dependency>

  並在全局配置文件中增加下述配置:

#配置Eureka中的serviceId列表,標記監控哪些服務,多個服務名用逗號分隔,可以配置監控的服務,必須開啟了Hystrix Dashboard。
turbine.appConfig=hystrix-application-client,test-feign-application-client
#指定聚合哪些集群,多個使用","分割,default代表默認集群。集群就是服務名稱。需要配置clusterNameExpression使用。
turbine.aggregator.clusterConfig=default
# 1. clusterNameExpression指定集群名稱,默認表達式appName;此時:turbine.aggregator.clusterConfig需要配置想要監控的應用名稱; # 2. 當clusterNameExpression: default時,turbine.aggregator.clusterConfig可以不寫,因為默認就是default;代表所有集群都需要監控
turbine.clusterNameExpression="default"

  在應用啟動類中,增加注解@EnableTurbine,代表開啟Turbine服務,提供多服務集群監控數據收集。

/**
 * @EnableTurbine - 開啟Turbine功能。
 *  可以實現收集多個App client的Dashboard監控數據。
 */
@SpringBootApplication
@EnableTurbine
public class HystrixTurbineApplication {

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

  最后再Hystrix Dashboard視圖監控服務中,使用http://ip:port/turbine.stream作為監控數據來源,提供可視化監控界面。

  注意:使用Turbine做多服務監控的時候,要求全局配置文件中配置的服務列表命名在Eureka注冊中心中可見。就是先啟動Application client再啟動Turbine。

  4.2.2服務集群監控

  在spring cloud中,服務名相同的多服務結點會自動形成集群,並提供服務。在Turbine中,監控服務集群不需要提供任何的特殊配置,因為turbine.appConfig已經配置了要監控的服務名稱。集群監控數據會自動收集。

  在Hystrix Dashboard的可視化監控界面中,hosts信息會顯示出服務集群中的節點數量。如圖所示:

         

   注意:使用Turbine做服務集群監控的時候,必須先啟動application client集群,再啟動Turbine。保證Turbine啟動的時候,可以在eureka注冊中心中發現要監控的服務集群。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM