<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringCloud中Hystrix容錯(cuò)保護(hù)原理及配置,看它就夠了!

          共 62946字,需瀏覽 126分鐘

           ·

          2021-03-27 19:08


          作者:kosamino

          cnblogs.com/jing99/p/11625306.html

          1 什么是災(zāi)難性雪崩效應(yīng)?

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

          造成災(zāi)難性雪崩效應(yīng)的原因,可以簡(jiǎn)單歸結(jié)為下述三種:

          • 服務(wù)提供者不可用。如:硬件故障、程序BUG、緩存擊穿、并發(fā)請(qǐng)求量過大等。
          • 重試加大流量。如:用戶重試、代碼重試邏輯等。
          • 服務(wù)調(diào)用者不可用。如:同步請(qǐng)求阻塞造成的資源耗盡等。

          雪崩效應(yīng)最終的結(jié)果就是:服務(wù)鏈條中的某一個(gè)服務(wù)不可用,導(dǎo)致一系列的服務(wù)不可用,最終造成服務(wù)邏輯崩潰。這種問題造成的后果,往往是無法預(yù)料的。

          2 如何解決災(zāi)難性雪崩效應(yīng)?

          解決災(zāi)難性雪崩效應(yīng)的方式通常有:降級(jí)、隔離、熔斷、請(qǐng)求緩存、請(qǐng)求合并。

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

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

          通常來說,開發(fā)的時(shí)候,使用ribbon處理服務(wù)災(zāi)難雪崩效應(yīng)(因此章節(jié)2示例均采用Ribbon,章節(jié)3是Feign實(shí)現(xiàn)方式詳解),開發(fā)的成本低。維護(hù)成本高。使用feign技術(shù)處理服務(wù)災(zāi)難雪崩效應(yīng),開發(fā)的成本較高,維護(hù)成本低。

          2.1 降級(jí)

          降級(jí)是指,當(dāng)請(qǐng)求超時(shí)、資源不足等情況發(fā)生時(shí)進(jìn)行服務(wù)降級(jí)處理,不調(diào)用真實(shí)服務(wù)邏輯,而是使用快速失?。╢allback)方式直接返回一個(gè)托底數(shù)據(jù),保證服務(wù)鏈條的完整,避免服務(wù)雪崩。

          解決服務(wù)雪崩效應(yīng),都是避免application client請(qǐng)求application service時(shí),出現(xiàn)服務(wù)調(diào)用錯(cuò)誤或網(wǎng)絡(luò)問題。處理手法都是在application client中實(shí)現(xiàn)。我們需要在application client相關(guān)工程中導(dǎo)入hystrix依賴信息。

          并在對(duì)應(yīng)的啟動(dòng)類上增加新的注解@EnableCircuitBreaker,這個(gè)注解是用于開啟hystrix熔斷器的,簡(jiǎn)言之,就是讓代碼中的hystrix相關(guān)注解生效。

          引入依賴:

          <!-- hystrix依賴, 處理服務(wù)災(zāi)難雪崩效應(yīng)的。 -->
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-hystrix</artifactId>
          </dependency>

          啟動(dòng)器代碼:

          /**
           * @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務(wù)容錯(cuò)能力。
           * 當(dāng)應(yīng)用啟用Hystrix服務(wù)容錯(cuò)的時(shí)候,必須增加的一個(gè)注解。
           */

          @EnableCircuitBreaker
          @EnableEurekaClient
          @SpringBootApplication
          public class HystrixApplicationClientApplication {
              public static void main(String[] args) {
                SpringApplication.run(HystrixApplicationClientApplication.classargs);
              }
          }

          在調(diào)用application service相關(guān)代碼中,增加新的方法注解@HystrixCommand,代表當(dāng)前方法啟用Hystrix處理服務(wù)雪崩效應(yīng)。

          @HystrixCommand注解中的屬性:fallbackMethod - 代表當(dāng)調(diào)用的application service出現(xiàn)問題時(shí),調(diào)用哪個(gè)fallback快速失敗處理方法返回托底數(shù)據(jù)。

          實(shí)現(xiàn)類:

          @Service
          public class HystrixService {

              @Autowired
              private LoadBalancerClient loadBalancerClient;

              /**
               * 服務(wù)降級(jí)處理。
               * 當(dāng)前方法遠(yuǎn)程調(diào)用application service服務(wù)的時(shí)候,如果service服務(wù)出現(xiàn)了任何錯(cuò)誤(超時(shí),異常等)
               * 不會(huì)將異常拋到客戶端,而是使用本地的一個(gè)fallback(錯(cuò)誤返回)方法來返回一個(gè)托底數(shù)據(jù)。
               * 避免客戶端看到錯(cuò)誤頁面。
               * 使用注解來描述當(dāng)前方法的服務(wù)降級(jí)邏輯。
               * @HystrixCommand - 開啟Hystrix命令的注解。代表當(dāng)前方法如果出現(xiàn)服務(wù)調(diào)用問題,使用Hystrix邏輯來處理。
               *  重要屬性 - fallbackMethod
               *      錯(cuò)誤返回方法名。如果當(dāng)前方法調(diào)用服務(wù),遠(yuǎn)程服務(wù)出現(xiàn)問題的時(shí)候,調(diào)用本地的哪個(gè)方法得到托底數(shù)據(jù)。
               *      Hystrix會(huì)調(diào)用fallbackMethod指定的方法,獲取結(jié)果,并返回給客戶端。
               * @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方法。本地定義的。用來處理遠(yuǎn)程服務(wù)調(diào)用錯(cuò)誤時(shí),返回的基礎(chǔ)數(shù)據(jù)。
               */

              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 緩存

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

          使用請(qǐng)求緩存時(shí),需要注意非冪等性操作對(duì)緩存數(shù)據(jù)的影響。

          請(qǐng)求緩存是依托某一緩存服務(wù)來實(shí)現(xiàn)的。在案例中使用redis作為緩存服務(wù)器,那么可以使用spring-data-redis來實(shí)現(xiàn)redis的訪問操作。需要在application client相關(guān)工程中導(dǎo)入下述依賴:

          <!-- hystrix依賴, 處理服務(wù)災(zāi)難雪崩效應(yīng)的。 -->
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-hystrix</artifactId>
          </dependency>
                  
          <!-- spring-data-redis spring cloud中集成的spring-data相關(guān)啟動(dòng)器。 -->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-data-redis</artifactId>
          </dependency>

          在Spring Cloud應(yīng)用中,啟用spring對(duì)cache的支持,需要在啟動(dòng)類中增加注解@EnableCaching,此注解代表當(dāng)前應(yīng)用開啟spring對(duì)cache的支持。簡(jiǎn)言之就是使spring-data-redis相關(guān)的注解生效,如:@CacheConfig、@Cacheable、@CacheEvict等。

          啟動(dòng)器:

          /**
           * @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務(wù)容錯(cuò)能力。
           * 當(dāng)應(yīng)用啟用Hystrix服務(wù)容錯(cuò)的時(shí)候,必須增加的一個(gè)注解。
           */

          @EnableCircuitBreaker
          /**
           * @EnableCaching - 開啟spring cloud對(duì)cache的支持。
           * 可以自動(dòng)的使用請(qǐng)求緩存,訪問redis等cache服務(wù)。
           */

          @EnableCaching
          @EnableEurekaClient
          @SpringBootApplication
          public class HystrixApplicationClientApplication {
              public static void main(String[] args) {
                  SpringApplication.run(HystrixApplicationClientApplication.classargs);
              }
          }

          spring cloud會(huì)檢查每個(gè)冪等性請(qǐng)求,如果請(qǐng)求完全相同(路徑、參數(shù)等完全一致),則首先訪問緩存redis,查看緩存數(shù)據(jù),如果緩存中有數(shù)據(jù),則不調(diào)用遠(yuǎn)程服務(wù)application service。如果緩存中沒有數(shù)據(jù),則調(diào)用遠(yuǎn)程服務(wù),并將結(jié)果緩存到redis中,供后續(xù)請(qǐng)求使用。

          如果請(qǐng)求是一個(gè)非冪等性操作,則會(huì)根據(jù)方法的注解來動(dòng)態(tài)管理redis中的緩存數(shù)據(jù),避免數(shù)據(jù)不一致。

          注意:使用請(qǐng)求緩存會(huì)導(dǎo)致很多的隱患,如:緩存管理不當(dāng)導(dǎo)致的數(shù)據(jù)不同步、問題排查困難等。在商業(yè)項(xiàng)目中,解決服務(wù)雪崩效應(yīng)不推薦使用請(qǐng)求緩存。

          實(shí)現(xiàn)類:

          /**
           * 在類上,增加@CacheConfig注解,用來描述當(dāng)前類型可能使用cache緩存。
           * 如果使用緩存,則緩存數(shù)據(jù)的key的前綴是cacheNames。
           * cacheNames是用來定義一個(gè)緩存集的前綴命名的,相當(dāng)于分組。
           */

          @CacheConfig(cacheNames={"test.hystrix.cache"})
          @Service
          public class HystrixService {

              @Autowired
              private LoadBalancerClient loadBalancerClient;

              /**
               * 請(qǐng)求緩存處理方法。
               * 使用注解@Cacheable描述方法。配合啟動(dòng)器中的相關(guān)注解,實(shí)現(xiàn)一個(gè)請(qǐng)求緩存邏輯。
               * 將當(dāng)期方法的返回值緩存到cache中。
               * 屬性 value | cacheNames - 代表緩存到cache的數(shù)據(jù)的key的一部分。
               * 可以使用springEL來獲取方法參數(shù)數(shù)據(jù),定制特性化的緩存key。
               * 只要方法增加了@Cacheable注解,每次調(diào)用當(dāng)前方法的時(shí)候,spring cloud都會(huì)先訪問cache獲取數(shù)據(jù),
               * 如果cache中沒有數(shù)據(jù),則訪問遠(yuǎn)程服務(wù)獲取數(shù)據(jù)。遠(yuǎn)程服務(wù)返回?cái)?shù)據(jù),先保存在cache中,再返回給客戶端。
               * 如果cache中有數(shù)據(jù),則直接返回cache中的數(shù)據(jù),不會(huì)訪問遠(yuǎn)程服務(wù)。
               * 
               * 請(qǐng)求緩存會(huì)有緩存數(shù)據(jù)不一致的可能。
               * 緩存數(shù)據(jù)過期、失效、臟數(shù)據(jù)等情況。
               * 一旦使用了請(qǐng)求緩存來處理冪等性請(qǐng)求操作。則在非冪等性請(qǐng)求操作中必須管理緩存。避免緩存數(shù)據(jù)的錯(cuò)誤。
               * @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;
              }
              
              /**
               * 非冪等性操作。用于模擬刪除邏輯。
               * 一旦非冪等性操作執(zhí)行,則必須管理緩存。就是釋放緩存中的數(shù)據(jù)。刪除緩存數(shù)據(jù)。
               * 使用注解@CacheEvict管理緩存。
               * 通過數(shù)據(jù)cacheNames | value來刪除對(duì)應(yīng)key的緩存。
               * 刪除緩存的邏輯,是在當(dāng)前方法執(zhí)行結(jié)束后。
               * @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 請(qǐng)求合并

          請(qǐng)求合并是指,在一定時(shí)間內(nèi),收集一定量的同類型請(qǐng)求,合并請(qǐng)求需求后,一次性訪問服務(wù)提供者,得到批量結(jié)果。這種方式可以減少服務(wù)消費(fèi)者和服務(wù)提供者之間的通訊次數(shù),提升應(yīng)用執(zhí)行效率。

          未使用請(qǐng)求合并:

          使用請(qǐng)求合并:

          什么情況下使用請(qǐng)求合并:

          在微服務(wù)架構(gòu)中,我們將一個(gè)項(xiàng)目拆分成很多個(gè)獨(dú)立的模塊,這些獨(dú)立的模塊通過遠(yuǎn)程調(diào)用來互相配合工作,但是,在高并發(fā)情況下,通信次數(shù)的增加會(huì)導(dǎo)致總的通信時(shí)間增加,同時(shí),線程池的資源也是有限的,高并發(fā)環(huán)境會(huì)導(dǎo)致有大量的線程處于等待狀態(tài),進(jìn)而導(dǎo)致響應(yīng)延遲,為了解決這些問題,我們需要來了解Hystrix的請(qǐng)求合并。

          通常來說,服務(wù)鏈條超出4個(gè),不推薦使用請(qǐng)求合并。因?yàn)檎?qǐng)求合并有等待時(shí)間。

          請(qǐng)求合并的缺點(diǎn):

          設(shè)置請(qǐng)求合并之后,本來一個(gè)請(qǐng)求可能5ms就搞定了,但是現(xiàn)在必須再等10ms看看還有沒有其他的請(qǐng)求一起的,這樣一個(gè)請(qǐng)求的耗時(shí)就從5ms增加到15ms了,不過,如果我們要發(fā)起的命令本身就是一個(gè)高延遲的命令,那么這個(gè)時(shí)候就可以使用請(qǐng)求合并了,因?yàn)檫@個(gè)時(shí)候時(shí)間窗的時(shí)間消耗就顯得微不足道了,另外高并發(fā)也是請(qǐng)求合并的一個(gè)非常重要的場(chǎng)景。

          引入依賴:

          <!-- hystrix依賴, 處理服務(wù)災(zāi)難雪崩效應(yīng)的。 -->
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-hystrix</artifactId>
          </dependency>

          啟動(dòng)器:

          /**
           * @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務(wù)容錯(cuò)能力。
           * 當(dāng)應(yīng)用啟用Hystrix服務(wù)容錯(cuò)的時(shí)候,必須增加的一個(gè)注解。
           */

          @EnableCircuitBreaker
          @EnableEurekaClient
          @SpringBootApplication
          public class HystrixApplicationClientApplication {
              public static void main(String[] args) {
                  SpringApplication.run(HystrixApplicationClientApplication.classargs);
              }
          }

          使用注解@HystrixCollapser來描述需要合并請(qǐng)求的方法,并提供合并方法使用注解@HystrixCommand來描述。當(dāng)合并條件(@HystrixCollapser)滿足時(shí),會(huì)觸發(fā)合并方法(@HystrixCommand)來調(diào)用遠(yuǎn)程服務(wù)并得到結(jié)果。

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

          @HystrixCollapser注解的屬性:

          • batchMethod - 請(qǐng)求合并方法名。
          • scope - 請(qǐng)求合并方式。可選值有REQUEST和GLOBAL。REQUEST代表在一個(gè)request請(qǐng)求生命周期內(nèi)的多次遠(yuǎn)程服務(wù)調(diào)用請(qǐng)求需要合并處理,此為默認(rèn)值。GLOBAL代表所有request線程內(nèi)的多次遠(yuǎn)程服務(wù)調(diào)用請(qǐng)求需要合并處理。
          • timerDelayInMilliseconds - 多少時(shí)間間隔內(nèi)的請(qǐng)求進(jìn)行合并處理,默認(rèn)值為10ms。建議設(shè)置時(shí)間間隔短一些,如果單位時(shí)間并發(fā)量不大,并沒有請(qǐng)求合并的必要。
          • maxRequestsInBatch - 設(shè)置合并請(qǐng)求的最大極值,也就是timerDelayInMilliseconds時(shí)間內(nèi),最多合并多少個(gè)請(qǐng)求。默認(rèn)值是Integer.MAX_VALUE。

          實(shí)現(xiàn)類:

          @Service
          public class HystrixService {

              @Autowired
              private LoadBalancerClient loadBalancerClient;

              /**
               * 需要合并請(qǐng)求的方法。
               * 這種方法的返回結(jié)果一定是Future類型的。
               * 這種方法的處理邏輯都是異步的。
               * 是application client在一定時(shí)間內(nèi)收集客戶端請(qǐng)求,或收集一定量的客戶端請(qǐng)求,一次性發(fā)給application service。
               * application service返回的結(jié)果,application client會(huì)進(jìn)行二次處理,封裝為future對(duì)象并返回
               * future對(duì)象需要通過get方法獲取最終的結(jié)果。get方法是由控制器調(diào)用的。所以控制器調(diào)用service的過程是一個(gè)異步處理的過程。
               * 合并請(qǐng)求的方法需要使用@HystrixCollapser注解描述。
               * batchMethod - 合并請(qǐng)求后,使用的方法是什么。如果當(dāng)前方法有參數(shù),合并請(qǐng)求后的方法參數(shù)是當(dāng)前方法參數(shù)的集合,如 int id >> int[] ids。
               * scope - 合并請(qǐng)求的請(qǐng)求作用域??蛇x值有g(shù)lobal和request。
               *     global代表所有的請(qǐng)求線程都可以等待可合并。 常用,所有瀏覽器或者請(qǐng)求源(Postman、curl等)調(diào)用的請(qǐng)求
               *     request代表一個(gè)請(qǐng)求線程中的多次遠(yuǎn)程服務(wù)調(diào)用可合并
               * collapserProperties - 細(xì)致配置。就是配置合并請(qǐng)求的特性。如等待多久,如可合并請(qǐng)求的數(shù)量。
               *     屬性的類型是@HystrixProperty類型數(shù)組,可配置的屬性值可以直接通過字符串或常量類定義。
               *     timerDelayInMilliseconds - 等待時(shí)長(zhǎng)
               *     maxRequestsInBatch - 可合并的請(qǐng)求最大數(shù)量。
               * 
               * 方法處理邏輯不需要實(shí)現(xiàn),直接返回null即可。
               * 合并請(qǐng)求一定是可合并的。也就是同類型請(qǐng)求。同URL的請(qǐng)求。
               * @param id
               * @return
               */

              @HystrixCollapser(batchMethod = "mergeRequest"
                      scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,  
                      collapserProperties = {  
                      // 請(qǐng)求時(shí)間間隔在20ms之內(nèi)的請(qǐng)求會(huì)被合并為一個(gè)請(qǐng)求,默認(rèn)為10ms
                      @HystrixProperty(name = "timerDelayInMilliseconds", value = "20"),
                      // 設(shè)置觸發(fā)批處理執(zhí)行之前,在批處理中允許的最大請(qǐng)求數(shù)。
                      @HystrixProperty(name = "maxRequestsInBatch", value = "200"),  
              })  
              public Future<Map<String, Object>> testMergeRequest(Long id){
                  return null;
              }
              
              /**
               * 批量處理方法。就是合并請(qǐng)求后真實(shí)調(diào)用遠(yuǎn)程服務(wù)的方法。
               * 必須使用@HystrixCommand注解描述,代表當(dāng)前方法是一個(gè)Hystrix管理的服務(wù)容錯(cuò)方法。
               * 是用于處理請(qǐng)求合并的方法。
               * @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 熔斷

          當(dāng)一定時(shí)間內(nèi),異常請(qǐng)求比例(請(qǐng)求超時(shí)、網(wǎng)絡(luò)故障、服務(wù)異常等)達(dá)到閥值時(shí),啟動(dòng)熔斷器,熔斷器一旦啟動(dòng),則會(huì)停止調(diào)用具體服務(wù)邏輯,通過fallback快速返回托底數(shù)據(jù),保證服務(wù)鏈的完整。

          熔斷有自動(dòng)恢復(fù)機(jī)制,如:當(dāng)熔斷器啟動(dòng)后,每隔5秒,嘗試將新的請(qǐng)求發(fā)送給服務(wù)提供者,如果服務(wù)可正常執(zhí)行并返回結(jié)果,則關(guān)閉熔斷器,服務(wù)恢復(fù)。如果仍舊調(diào)用失敗,則繼續(xù)返回托底數(shù)據(jù),熔斷器持續(xù)開啟狀態(tài)。

          引入依賴:

          <!-- hystrix依賴, 處理服務(wù)災(zāi)難雪崩效應(yīng)的。 -->
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-hystrix</artifactId>
          </dependency>

          啟動(dòng)器:

          /**
           * @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務(wù)容錯(cuò)能力。
           * 當(dāng)應(yīng)用啟用Hystrix服務(wù)容錯(cuò)的時(shí)候,必須增加的一個(gè)注解。
           */

          @EnableCircuitBreaker
          @EnableEurekaClient
          @SpringBootApplication
          public class HystrixApplicationClientApplication {
              public static void main(String[] args) {
                  SpringApplication.run(HystrixApplicationClientApplication.classargs);
              }
          }

          熔斷的實(shí)現(xiàn)是在調(diào)用遠(yuǎn)程服務(wù)的方法上增加@HystrixCommand注解。當(dāng)注解配置滿足則開啟或關(guān)閉熔斷器。

          @Service
          public class HystrixService {

              @Autowired
              private LoadBalancerClient loadBalancerClient;
              
              /**
               * 熔斷機(jī)制
               * 相當(dāng)于一個(gè)強(qiáng)化的服務(wù)降級(jí)。 服務(wù)降級(jí)是只要遠(yuǎn)程服務(wù)出錯(cuò),立刻返回fallback結(jié)果。
               * 熔斷是收集一定時(shí)間內(nèi)的錯(cuò)誤比例,如果達(dá)到一定的錯(cuò)誤率。則啟動(dòng)熔斷,返回fallback結(jié)果。
               * 間隔一定時(shí)間會(huì)將請(qǐng)求再次發(fā)送給application service進(jìn)行重試。如果重試成功,熔斷關(guān)閉。
               * 如果重試失敗,熔斷持續(xù)開啟,并返回fallback數(shù)據(jù)。
               * @HystrixCommand 描述方法。
               *  fallbackMethod - fallback方法名
               *  commandProperties - 具體的熔斷標(biāo)準(zhǔn)。類型是HystrixProperty數(shù)組。
               *   可以通過字符串或常亮類配置。
               *   CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD - 錯(cuò)誤數(shù)量。在10毫秒內(nèi),出現(xiàn)多少次遠(yuǎn)程服務(wù)調(diào)用錯(cuò)誤,則開啟熔斷。
               *       默認(rèn)20個(gè)。10毫秒內(nèi)有20個(gè)錯(cuò)誤請(qǐng)求則開啟熔斷。
               *   CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE - 錯(cuò)誤比例。在10毫秒內(nèi),遠(yuǎn)程服務(wù)調(diào)用錯(cuò)誤比例達(dá)標(biāo)則開啟熔斷。
               *   CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS - 熔斷開啟后,間隔多少毫秒重試遠(yuǎn)程服務(wù)調(diào)用。默認(rèn)5000毫秒。
               * @return
               */

              @HystrixCommand(fallbackMethod = "breakerFallback",
                      commandProperties = {
                        // 默認(rèn)20個(gè);10ms內(nèi)請(qǐng)求數(shù)大于20個(gè)時(shí)就啟動(dòng)熔斷器,當(dāng)請(qǐng)求符合熔斷條件時(shí)將觸發(fā)getFallback()。
                        @HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, 
                                    value="10"),
                        // 請(qǐng)求錯(cuò)誤率大于50%時(shí)就熔斷,然后for循環(huán)發(fā)起請(qǐng)求,當(dāng)請(qǐng)求符合熔斷條件時(shí)將觸發(fā)getFallback()。
                        @HystrixProperty(name=HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, 
                                    value="50"),
                        // 默認(rèn)5秒;熔斷多少秒后去嘗試請(qǐng)求
                        @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";
          # 是否開啟熔斷策略。默認(rèn)值為true。

          CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD
          "circuitBreaker.requestVolumeThreshold";
          # 10ms內(nèi),請(qǐng)求并發(fā)數(shù)超出則觸發(fā)熔斷策略。默認(rèn)值為20。

          CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS
          "circuitBreaker.sleepWindowInMilliseconds";
          # 當(dāng)熔斷策略開啟后,延遲多久嘗試再次請(qǐng)求遠(yuǎn)程服務(wù)。默認(rèn)為5秒。

          CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE
          "circuitBreaker.errorThresholdPercentage";
          # 10ms內(nèi),出現(xiàn)錯(cuò)誤的請(qǐng)求百分比達(dá)到限制,則觸發(fā)熔斷策略。默認(rèn)為50%。

          CIRCUIT_BREAKER_FORCE_OPEN
          "circuitBreaker.forceOpen";
          # 是否強(qiáng)制開啟熔斷策略。即所有請(qǐng)求都返回fallback托底數(shù)據(jù)。默認(rèn)為false。

          CIRCUIT_BREAKER_FORCE_CLOSED
          "circuitBreaker.forceClosed";
          # 是否強(qiáng)制關(guān)閉熔斷策略。即所有請(qǐng)求一定調(diào)用遠(yuǎn)程服務(wù)。默認(rèn)為false。

          2.5 隔離

          所謂隔離,就是當(dāng)服務(wù)發(fā)生問題時(shí),使用技術(shù)手段隔離請(qǐng)求,保證服務(wù)調(diào)用鏈的完整。隔離分為線程池隔離和信號(hào)量隔離兩種實(shí)現(xiàn)方式。

          2.5.1 線程池隔離

          所謂線程池隔離,就是將并發(fā)請(qǐng)求量大的部分服務(wù)使用獨(dú)立的線程池處理,避免因個(gè)別服務(wù)并發(fā)過高導(dǎo)致整體應(yīng)用宕機(jī)。

          線程池隔離優(yōu)點(diǎn):

          • 使用線程池隔離可以完全隔離依賴的服務(wù),請(qǐng)求線程可以快速放回。
          • 當(dāng)線程池出現(xiàn)問題時(shí),線程池是完全隔離狀態(tài)的,是獨(dú)立的,不會(huì)影響到其他服務(wù)的正常執(zhí)行。
          • 當(dāng)崩潰的服務(wù)恢復(fù)時(shí),線程池可以快速清理并恢復(fù),不需要相對(duì)漫長(zhǎng)的恢復(fù)等待。
          • 獨(dú)立的線程池也提供了并發(fā)處理能力。

          線程池隔離缺點(diǎn):

          線程池隔離機(jī)制,會(huì)導(dǎo)致服務(wù)硬件計(jì)算開銷加大(CPU計(jì)算、調(diào)度等),每個(gè)命令的執(zhí)行都涉及到排隊(duì)、調(diào)度、上下文切換等,這些命令都是在一個(gè)單獨(dú)的線程上運(yùn)行的。

          線程池隔離的實(shí)現(xiàn)方式同樣是使用@HystrixCommand注解。相關(guān)注解配置屬性如下:

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

          引入依賴:

          <!-- hystrix依賴, 處理服務(wù)災(zāi)難雪崩效應(yīng)的。 -->
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-hystrix</artifactId>
          </dependency>

          啟動(dòng)器:

          /**
           * @EnableCircuitBreaker - 開啟斷路器。就是開啟hystrix服務(wù)容錯(cuò)能力。
           * 當(dāng)應(yīng)用啟用Hystrix服務(wù)容錯(cuò)的時(shí)候,必須增加的一個(gè)注解。
           */

          @EnableCircuitBreaker
          @EnableEurekaClient
          @SpringBootApplication
          public class HystrixApplicationClientApplication {
              public static void main(String[] args) {
                  SpringApplication.run(HystrixApplicationClientApplication.classargs);
              }
          }

          實(shí)現(xiàn)類:

          @Service
          public class HystrixService {

              @Autowired
              private LoadBalancerClient loadBalancerClient;

              /**
               * 如果使用了@HystrixCommand注解,則Hystrix自動(dòng)創(chuàng)建獨(dú)立的線程池。
               * groupKey和threadPoolKey默認(rèn)值是當(dāng)前服務(wù)方法所在類型的simpleName
               * 
               * 所有的fallback方法,都執(zhí)行在一個(gè)HystrixTimer線程池上。
               * 這個(gè)線程池是Hystrix提供的一個(gè),專門處理fallback邏輯的線程池。
               * 
               * 線程池隔離實(shí)現(xiàn)
               * 線程池隔離,就是為某一些服務(wù),獨(dú)立劃分線程池。讓這些服務(wù)邏輯在獨(dú)立的線程池中運(yùn)行。
               * 不使用tomcat提供的默認(rèn)線程池。
               * 線程池隔離也有熔斷能力。如果線程池不能處理更多的請(qǐng)求的時(shí)候,會(huì)觸發(fā)熔斷,返回fallback數(shù)據(jù)。
               * groupKey - 分組名稱,就是為服務(wù)劃分分組。如果不配置,默認(rèn)使用threadPoolKey作為組名。
               * commandKey - 命令名稱,默認(rèn)值就是當(dāng)前業(yè)務(wù)方法的方法名。
               * threadPoolKey - 線程池命名,真實(shí)線程池命名的一部分。Hystrix在創(chuàng)建線程池并命名的時(shí)候,會(huì)提供完整命名。默認(rèn)使用gourpKey命名
               *  如果多個(gè)方法使用的threadPoolKey是同名的,則使用同一個(gè)線程池。
               * threadPoolProperties - 為Hystrix創(chuàng)建的線程池做配置。可以使用字符串或HystrixPropertiesManager中的常量指定。
               *  常用線程池配置:
               *      coreSize - 核心線程數(shù)。最大并發(fā)數(shù)。1000*(99%平均響應(yīng)時(shí)間 + 適當(dāng)?shù)难舆t時(shí)間)
               *      maxQueueSize - 阻塞隊(duì)列長(zhǎng)度。如果是-1這是同步隊(duì)列。如果是正數(shù)這是LinkedBlockingQueue。如果線程池最大并發(fā)數(shù)不足,
               *          提供多少的阻塞等待。
               *      keepAliveTimeMinutes - 心跳時(shí)間,超時(shí)時(shí)長(zhǎng)。單位是分鐘。
               *      queueSizeRejectionThreshold - 拒絕臨界值,當(dāng)最大并發(fā)不足的時(shí)候,超過多少個(gè)阻塞請(qǐng)求,后續(xù)請(qǐng)求拒絕。
               */

              @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;
              }
          }

          關(guān)于線程池:

          • 對(duì)于所有請(qǐng)求,都交由tomcat容器的線程池處理,是一個(gè)以http-nio開頭的的線程池;
          • 開啟了線程池隔離后,tomcat容器默認(rèn)的線程池會(huì)將請(qǐng)求轉(zhuǎn)交給threadPoolKey定義名稱的線程池,處理結(jié)束后,由定義的線程池進(jìn)行返回,無需還回tomcat容器默認(rèn)的線程池。線程池默認(rèn)為當(dāng)前方法名;
          • 所有的fallback都單獨(dú)由Hystrix創(chuàng)建的一個(gè)線程池處理。

          2.5.2 信號(hào)量隔離

          所謂信號(hào)量隔離,就是設(shè)置一個(gè)并發(fā)處理的最大極值。當(dāng)并發(fā)請(qǐng)求數(shù)超過極值時(shí),通過fallback返回托底數(shù)據(jù),保證服務(wù)完整性。

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

          • commandProperty - 配置信號(hào)量隔離具體數(shù)據(jù)。屬性類型為HystrixProperty數(shù)組,常用配置內(nèi)容如下:
          • execution.isolation.strategy - 設(shè)置隔離方式,默認(rèn)為線程池隔離??蛇x值只有THREAD和SEMAPHORE。
          • execution.isolation.semaphore.maxConcurrentRequests - 最大信號(hào)量并發(fā)數(shù),默認(rèn)為10。

          依賴注入和啟動(dòng)器同線程池隔離,實(shí)現(xiàn)類如下:

          @Service
          public class HystrixService {

              @Autowired
              private LoadBalancerClient loadBalancerClient;

              /**
               * 信號(hào)量隔離實(shí)現(xiàn)
               * 不會(huì)使用Hystrix管理的線程池處理請(qǐng)求。使用容器(Tomcat)的線程處理請(qǐng)求邏輯。
               * 不涉及線程切換,資源調(diào)度,上下文的轉(zhuǎn)換等,相對(duì)效率高。
               * 信號(hào)量隔離也會(huì)啟動(dòng)熔斷機(jī)制。如果請(qǐng)求并發(fā)數(shù)超標(biāo),則觸發(fā)熔斷,返回fallback數(shù)據(jù)。
               * commandProperties - 命令配置,HystrixPropertiesManager中的常量或字符串來配置。
               *     execution.isolation.strategy - 隔離的種類,可選值只有THREAD(線程池隔離)和SEMAPHORE(信號(hào)量隔離)。
               *      默認(rèn)是THREAD線程池隔離。
               *      設(shè)置信號(hào)量隔離后,線程池相關(guān)配置失效。
               *  execution.isolation.semaphore.maxConcurrentRequests - 信號(hào)量最大并發(fā)數(shù)。默認(rèn)值是10。常見配置500~1000。
               *      如果并發(fā)請(qǐng)求超過配置,其他請(qǐng)求進(jìn)入fallback邏輯。
               */

              @HystrixCommand(fallbackMethod="semaphoreQuarantineFallback",
                      commandProperties={
                        @HystrixProperty(
                                name=HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, 
                                value="SEMAPHORE"), // 信號(hào)量隔離
                        @HystrixProperty(
                                name=HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, 
                                value="100"// 信號(hào)量最大并發(fā)數(shù)
              })
              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線程池隔離和信號(hào)量隔離的對(duì)比

          2.5.4線程池隔離和信號(hào)量隔離的選擇

          • 線程池隔離:請(qǐng)求并發(fā)大,耗時(shí)較長(zhǎng)(一般都是計(jì)算大,服務(wù)鏈長(zhǎng)或訪問數(shù)據(jù)庫(kù))時(shí)使用線程池隔離。可以盡可能保證外部容器(如Tomcat)線程池可用,不會(huì)因?yàn)榉?wù)調(diào)用的原因?qū)е抡?qǐng)求阻塞等待。
          • 信號(hào)量隔離:請(qǐng)求并發(fā)大,耗時(shí)短(計(jì)算小,服務(wù)鏈段或訪問緩存)時(shí)使用信號(hào)量隔離。因?yàn)檫@類服務(wù)的響應(yīng)快,不會(huì)占用外部容器(如Tomcat)線程池太長(zhǎng)時(shí)間,減少線程的切換,可以避免不必要的開銷,提高服務(wù)處理效率。

          3 Feign的雪崩處理

          在聲明式遠(yuǎn)程服務(wù)調(diào)用Feign中,實(shí)現(xiàn)服務(wù)災(zāi)難性雪崩效應(yīng)處理也是通過Hystrix實(shí)現(xiàn)的。而feign啟動(dòng)器spring-cloud-starter-feign中是包含Hystrix相關(guān)依賴的。

          如果只使用服務(wù)降級(jí)、熔斷功能不需要做獨(dú)立依賴。如果需要使用Hystrix其他服務(wù)容錯(cuò)能力,需要依賴spring-cloud-starter-hystrix資源。

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

          從Dalston版本后,feign默認(rèn)關(guān)閉Hystrix支持。所以必須在全局配置文件中開啟feign技術(shù)中的Hystrix支持。配置如下:

          feign.hystrix.enabled=true

          如果不使用Hystrix服務(wù)容錯(cuò)功能,在application client端,服務(wù)接口只需要繼承服務(wù)標(biāo)準(zhǔn)api接口即可實(shí)現(xiàn)遠(yuǎn)程服務(wù)調(diào)用。如果使用了Hystrix,則有不同的編寫方式。具體如下。

          3.1 代碼實(shí)現(xiàn) - 接口實(shí)現(xiàn)類方式

          定義和服務(wù)標(biāo)準(zhǔn)api相同的application client服務(wù)接口。并通過@FeignClient注解來描述fallback方法所在類是什么。這個(gè)fallback方法所在類就是接口的實(shí)現(xiàn)類,實(shí)現(xiàn)的方法就是接口中定義方法的fallback方法。

          /**
           * 如果在Feign中使用Hystrix,則不能直接繼承服務(wù)標(biāo)準(zhǔn)接口。
           * 因?yàn)槔^承接口,一般都不會(huì)給予實(shí)現(xiàn)。會(huì)缺少fallback方法。熔斷機(jī)制鏈條不完整。
           * 在當(dāng)前接口中,重復(fù)定義服務(wù)標(biāo)準(zhǔn)接口中定義的方法。
           * 遠(yuǎn)程服務(wù)調(diào)用的時(shí)候,是通過@FeignClient實(shí)現(xiàn)的。
           * 如果遠(yuǎn)程服務(wù)調(diào)用失敗,則觸發(fā)fallback注解屬性定義的接口實(shí)現(xiàn)類中的對(duì)應(yīng)方法,作為fallback方法。
           * 
           * 在默認(rèn)的Hystrix配置環(huán)境中,使用的是服務(wù)降級(jí)保護(hù)機(jī)制。
           * 
           * 服務(wù)降級(jí),默認(rèn)的情況下,包含了請(qǐng)求超時(shí)。
           * feign聲明式遠(yuǎn)程服務(wù)調(diào)用,在啟動(dòng)的時(shí)候,初始化過程比較慢(通過注釋@FeignClient描述接口,接口生成動(dòng)態(tài)代理對(duì)象,實(shí)現(xiàn)服務(wù)調(diào)用)。比ribbon要慢很多。
           * 很容易在第一次訪問的時(shí)候,產(chǎn)生超時(shí)。導(dǎo)致返回fallback數(shù)據(jù)。
           */

          @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);
              
          }

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

          /**
           * 實(shí)現(xiàn)類中的每個(gè)方法,都是對(duì)應(yīng)的接口方法的fallback。
           * 一定要提供spring相關(guān)注解(@Component/@Service/@Repository等)。
           * 注解是為了讓當(dāng)前類型的對(duì)象被spring容器管理。
           * fallback是本地方法。
           * 是接口的實(shí)現(xiàn)方法。
           */

          @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 相關(guān)配置

          在Feign技術(shù)中,一般不使用請(qǐng)求合并,請(qǐng)求緩存等容錯(cuò)機(jī)制。常用的機(jī)制是隔離,降級(jí)和熔斷。

          3.2.1 Properties全局配置

          # hystrix.command.default和hystrix.threadpool.default中的default為默認(rèn)CommandKey,CommandKey默認(rèn)值為服務(wù)方法名。# 在properties配置中配置格式混亂,如果需要為每個(gè)方法設(shè)置不同的容錯(cuò)規(guī)則,建議使用yml文件配置。
          # Command Properties

          # Execution相關(guān)的屬性的配置:
          # 隔離策略,默認(rèn)是Thread, 可選Thread|Semaphore
          hystrix.command.default.execution.isolation.strategy=THREAD
          #命令執(zhí)行超時(shí)時(shí)間,默認(rèn)1000ms,只在線程池隔離中有效。
          hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1000
          # 執(zhí)行是否啟用超時(shí),默認(rèn)啟用true,只在線程池隔離中有效。
          hystrix.command.default.execution.timeout.enabled=true
          # 發(fā)生超時(shí)是是否中斷,默認(rèn)true,只在線程池隔離中有效。
          hystrix.command.default.execution.isolation.thread.interruptOnTimeout=true
          # 最大并發(fā)請(qǐng)求數(shù),默認(rèn)10,該參數(shù)當(dāng)使用ExecutionIsolationStrategy.SEMAPHORE策略時(shí)才有效。如果達(dá)到最大并發(fā)請(qǐng)求數(shù),請(qǐng)求會(huì)被拒絕。# 理論上選擇semaphore的原則和選擇thread一致,但選用semaphore時(shí)每次執(zhí)行的單元要比較小且執(zhí)行速度快(ms級(jí)別),否則的話應(yīng)該用thread。# semaphore應(yīng)該占整個(gè)容器(tomcat)的線程池的一小部分。
          hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests=10
          # 如果并發(fā)數(shù)達(dá)到該設(shè)置值,請(qǐng)求會(huì)被拒絕和拋出異常并且fallback不會(huì)被調(diào)用。默認(rèn)10。# 只在信號(hào)量隔離策略中有效,建議設(shè)置大一些,這樣并發(fā)數(shù)達(dá)到execution最大請(qǐng)求數(shù)時(shí),會(huì)直接調(diào)用fallback,而并發(fā)數(shù)達(dá)到fallback最大請(qǐng)求數(shù)時(shí)會(huì)被拒絕和拋出異常。
          hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests=10
          # ThreadPool 相關(guān)參數(shù)
          # 并發(fā)執(zhí)行的最大線程數(shù),默認(rèn)10
          hystrix.threadpool.default.coreSize=10
          # BlockingQueue的最大隊(duì)列數(shù),當(dāng)設(shè)為-1,會(huì)使用SynchronousQueue,值為正時(shí)使用LinkedBlcokingQueue。# 該設(shè)置只會(huì)在初始化時(shí)有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默認(rèn)-1。
          hystrix.threadpool.default.maxQueueSize=-1
          # 即使maxQueueSize沒有達(dá)到,達(dá)到queueSizeRejectionThreshold該值后,請(qǐng)求也會(huì)被拒絕。
          hystrix.threadpool.default.queueSizeRejectionThreshold=20
          # 線程存活時(shí)間,單位是分鐘。默認(rèn)值為1。
          hystrix.threadpool.default.keepAliveTimeMinutes=1
          # Fallback相關(guān)的屬性 # 當(dāng)執(zhí)行失敗或者請(qǐng)求被拒絕,是否會(huì)嘗試調(diào)用fallback方法 。默認(rèn)true hystrix.command.default.fallback.enabled=true # Circuit Breaker相關(guān)的屬性 # 是否開啟熔斷器。默認(rèn)true hystrix.command.default.circuitBreaker.enabled=true # 一個(gè)rolling window內(nèi)最小的請(qǐng)求數(shù)。如果設(shè)為20,那么當(dāng)一個(gè)rolling window的時(shí)間內(nèi)(比如說1個(gè)rolling window是10毫秒)收到19個(gè)請(qǐng)求# 即使19個(gè)請(qǐng)求都失敗,也不會(huì)觸發(fā)circuit break。默認(rèn)20
          hystrix.command.default.circuitBreaker.requestVolumeThreshold=20
          # 觸發(fā)短路的時(shí)間值,當(dāng)該值設(shè)為5000時(shí),則當(dāng)觸發(fā)circuit break后的5000毫秒內(nèi)都會(huì)拒絕遠(yuǎn)程服務(wù)調(diào)用,也就是5000毫秒后才會(huì)重試遠(yuǎn)程服務(wù)調(diào)用。默認(rèn)5000
          hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000
          # 錯(cuò)誤比率閥值,如果錯(cuò)誤率>=該值,circuit會(huì)被打開,并短路所有請(qǐng)求觸發(fā)fallback。默認(rèn)50
          hystrix.command.default.circuitBreaker.errorThresholdPercentage=50
          # 強(qiáng)制打開熔斷器
          hystrix.command.default.circuitBreaker.forceOpen=false
          # 強(qiáng)制關(guān)閉熔斷器
          hystrix.command.default.circuitBreaker.forceClosed=false

          3.2.2 YML全局配置

          YML配置文件,對(duì)SpringEL的支持更加優(yōu)秀。可以通過SpringEL定制化的為每個(gè)服務(wù)調(diào)用配置Hystrix的容錯(cuò)處理方案。對(duì)Hystrix的配置粒度相比較Properties的配置方案更加細(xì)致。

          在YML中可配置的Hystrix信息,和Properties中配置的內(nèi)容是一致。

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

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

          feign:
            hystrix:
              enabled: true

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

          3.3 代碼實(shí)現(xiàn) - Factory實(shí)現(xiàn)方式

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

          再定義一個(gè)Java類,實(shí)現(xiàn)接口FallbackFactory,實(shí)現(xiàn)其中的create方法。使用匿名內(nèi)部類的方式,為服務(wù)接口定義一個(gè)實(shí)現(xiàn)類,定義fallback方法實(shí)現(xiàn)。


          本地接口定義:

          @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實(shí)現(xiàn)類:

          /**
           * 使用Factory方式實(shí)現(xiàn)Feign的Hystrix容錯(cuò)處理。
           * 編寫的自定義Factory必須實(shí)現(xiàn)接口FallbackFactory。
           * FallbackFactory中的方法是
           *  服務(wù)接口的類型 create(Throwable 遠(yuǎn)程服務(wù)調(diào)用的錯(cuò)誤)
           * 
           * 工廠實(shí)現(xiàn)方案和服務(wù)接口實(shí)現(xiàn)類實(shí)現(xiàn)方案的區(qū)別:
           *  工廠可以提供自定義的異常信息處理邏輯。因?yàn)閏reate方法負(fù)責(zé)傳遞遠(yuǎn)程服務(wù)調(diào)用的異常對(duì)象。
           *  實(shí)現(xiàn)類可以快速的開發(fā),但是會(huì)丟失遠(yuǎn)程服務(wù)調(diào)用的異常信息。
           */

          @Component
          public class FirstClientFeignServiceFallbackFactory implements FallbackFactory<FirstClientFeignService{

              Logger logger = LoggerFactory.getLogger(FirstClientFeignServiceFallbackFactory.class);
              
              /**
               * create方法 - 就是工廠的生產(chǎn)產(chǎn)品的方法。
               *  當(dāng)前工廠生產(chǎn)的產(chǎn)品就是服務(wù)接口的Fallback處理對(duì)象。 就是服務(wù)接口的實(shí)現(xiàn)類的對(duì)象。
               */

              @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");
                      }
                  };
              }
          }

          這種實(shí)現(xiàn)邏輯的優(yōu)勢(shì)是,可以獲取遠(yuǎn)程調(diào)用服務(wù)的異常信息。為后期異常處理提供參考。

          工廠實(shí)現(xiàn)方案和實(shí)現(xiàn)類的實(shí)現(xiàn)方案,沒有效率和邏輯上的優(yōu)缺點(diǎn)對(duì)比。只是在遠(yuǎn)程服務(wù)調(diào)用異常的處理上有區(qū)別。

          4 Hystrix Dashboard - 數(shù)據(jù)監(jiān)控

          Hystrix dashboard是一款針對(duì)Hystrix進(jìn)行實(shí)時(shí)監(jiān)控的工具,通過Hystrix Dashboard我們可以在直觀地看到各Hystrix Command的請(qǐng)求響應(yīng)時(shí)間, 請(qǐng)求成功率等數(shù)據(jù)。

          4.1 實(shí)現(xiàn)單服務(wù)單節(jié)點(diǎn)數(shù)據(jù)監(jiān)控

          在使用了Hystrix技術(shù)的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>

          在啟動(dòng)器上增加注解@EnableHystrixDashboard、@EnableHystrix。

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

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

          啟動(dòng)工程后,如果觸發(fā)了Hystrix,則可以通過http://ip:port/hystrix.stream得到監(jiān)控?cái)?shù)據(jù)。這種監(jiān)控?cái)?shù)據(jù)的獲取都是JSON數(shù)據(jù)。

          且數(shù)據(jù)量級(jí)較大。不易于查看??梢允褂肏ystrix Dashboard提供的視圖界面來觀察監(jiān)控結(jié)果。視圖界面訪問路徑為http://ip:port/hystrix。視圖界面中各數(shù)據(jù)的含義如下:

          建議:監(jiān)控中心建議使用獨(dú)立工程來實(shí)現(xiàn)。這樣更便于維護(hù)。

          4.2 使用Turbine實(shí)現(xiàn)多服務(wù)或集群的數(shù)據(jù)監(jiān)控

          Turbine是聚合服務(wù)器發(fā)送事件流數(shù)據(jù)的一個(gè)工具,hystrix的監(jiān)控中,只能監(jiān)控單個(gè)服務(wù)或單個(gè)節(jié)點(diǎn),實(shí)際生產(chǎn)中都為多服務(wù)集群,因此可以通過turbine來監(jiān)控多集群服務(wù)。

          Turbine在Hystrix Dashboard中的作用如下:

          4.2.1多服務(wù)監(jiān)控

          當(dāng)使用Turbine來監(jiān)控多服務(wù)狀態(tài)時(shí),需提供一個(gè)獨(dú)立工程來搭建Turbine服務(wù)邏輯。并在工程中增加下述依賴:

          <!-- 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列表,標(biāo)記監(jiān)控哪些服務(wù),多個(gè)服務(wù)名用逗號(hào)分隔,可以配置監(jiān)控的服務(wù),必須開啟了Hystrix Dashboard。
          turbine.appConfig=hystrix-application-client,test-feign-application-client
          #指定聚合哪些集群,多個(gè)使用","分割,default代表默認(rèn)集群。集群就是服務(wù)名稱。需要配置clusterNameExpression使用。
          turbine.aggregator.clusterConfig=default
          # 1. clusterNameExpression指定集群名稱,默認(rèn)表達(dá)式appName;此時(shí):turbine.aggregator.clusterConfig需要配置想要監(jiān)控的應(yīng)用名稱;
          # 2. 當(dāng)clusterNameExpression: default時(shí),turbine.aggregator.clusterConfig可以不寫,因?yàn)槟J(rèn)就是default;代表所有集群都需要監(jiān)控
          turbine.clusterNameExpression="default"

          在應(yīng)用啟動(dòng)類中,增加注解@EnableTurbine,代表開啟Turbine服務(wù),提供多服務(wù)集群監(jiān)控?cái)?shù)據(jù)收集。

          /**
           * @EnableTurbine - 開啟Turbine功能。
           *  可以實(shí)現(xiàn)收集多個(gè)App client的Dashboard監(jiān)控?cái)?shù)據(jù)。
           */

          @SpringBootApplication
          @EnableTurbine
          public class HystrixTurbineApplication {

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

          最后再Hystrix Dashboard視圖監(jiān)控服務(wù)中,使用http://ip:port/turbine.stream作為監(jiān)控?cái)?shù)據(jù)來源,提供可視化監(jiān)控界面。

          注意:使用Turbine做多服務(wù)監(jiān)控的時(shí)候,要求全局配置文件中配置的服務(wù)列表命名在Eureka注冊(cè)中心中可見。就是先啟動(dòng)Application client再啟動(dòng)Turbine。

          4.2.2服務(wù)集群監(jiān)控

          在spring cloud中,服務(wù)名相同的多服務(wù)結(jié)點(diǎn)會(huì)自動(dòng)形成集群,并提供服務(wù)。在Turbine中,監(jiān)控服務(wù)集群不需要提供任何的特殊配置,因?yàn)閠urbine.appConfig已經(jīng)配置了要監(jiān)控的服務(wù)名稱。集群監(jiān)控?cái)?shù)據(jù)會(huì)自動(dòng)收集。

          在Hystrix Dashboard的可視化監(jiān)控界面中,hosts信息會(huì)顯示出服務(wù)集群中的節(jié)點(diǎn)數(shù)量。如圖所示:

          注意:使用Turbine做服務(wù)集群監(jiān)控的時(shí)候,必須先啟動(dòng)application client集群,再啟動(dòng)Turbine。保證Turbine啟動(dòng)的時(shí)候,可以在eureka注冊(cè)中心中發(fā)現(xiàn)要監(jiān)控的服務(wù)集群。

          獲取更多優(yōu)質(zhì)文章,點(diǎn)擊關(guān)注

          ??????

          瀏覽 28
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久久极品 | 中文字幕无码视频在线观看 | 翔田千里无码在线 | 内射无码8p国产精品 | 国产激情综合五月久久 |