SpringCould中的Hystrix

MXC肖某某發表於2020-10-11

一、簡介

原始碼地址:https://gitee.com/xiaocheng0902/my-cloud.git

1,定義

  Hystrix是一個用於處理分散式系統的延遲和容錯的開源庫,在分散式系統裡,許多依賴不可避免的會呼叫失敗,比如超時、異常等。Hystrix能夠保證在一個依賴出問題的情況下,不會導致整體服務失敗,避免級聯故障,以提高分散式系統的彈性。

  "斷路器"本身是一種開關裝置,當某個服務單元發生故障之後,通過斷路器的故障監控(類似熔斷保險絲),向呼叫方返回一個符合預期的、可處理的備選響應FallBack),而不是長時間的等待或者丟擲呼叫方無法處理的異常,這樣就保證了服務呼叫方的執行緒不會被長時間的、不必要地佔用,從而避免了故障在分散式系統中的蔓延,乃至雪崩。

2,作用

 a)服務降級

  服務降級是從整個系統的負荷情況出發和考慮的,對某些負荷會比較高的情況,為了預防某些功能(業務場景)出現負荷過載或者響應慢的情況,在其內部暫時捨棄對一些非核心的介面和資料的請求,而直接返回一個提前準備好的fallback(退路)錯誤處理資訊。這樣,雖然提供的是一個有損的服務,但卻保證了整個系統的穩定性和可用性

 b)服務熔斷

  服務熔斷的作用類似於我們家用的保險絲,當某服務出現不可用或響應超時的情況時,為了防止整個系統出現雪崩,暫時停止對該服務的呼叫。

 c)熔斷vs降級

3,使用依賴

<!--新增hystrix-->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
   <version>2.2.1.RELEASE</version>
</dependency>

二、服務降級

1,服務提供者(自身)

對應原始碼:cloud-provider-hystrix-payment8001

  在主啟動類Application上註解

@EnableCircuitBreaker

  在service的方法中加入

//主方法
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { //出現超時,異常呼叫fallbackMethod方法
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")  //3秒鐘以內就是正常的業務邏輯
})
public String paymentInfo_TimeOut(Integer id){
    int timeNumber = 5;  //1 的時候為正常執行當前方法
    //throw new RuntimeException();  //異常情況會直接跳過進入paymentInfo_TimeOutHandler
    try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
    return "執行緒池:"+Thread.currentThread().getName()+"   paymentInfo_TimeOut,id:  "+id+"\t"+"嗚嗚嗚"+" 耗時(秒)"+timeNumber;
}

//備選方法
public String paymentInfo_TimeOutHandler(Integer id){
    return "執行緒池:"+Thread.currentThread().getName()+"   系統繁忙, 請稍候再試  ,id:  "+id+"\t"+"哭了哇嗚";
}

2,服務呼叫者(fegin消費者)

對應原始碼:cloud-consumer-feign-hystrix-order80

  yaml檔案中新增配置

feign:
  hystrix:
    enabled: true #如果處理自身的容錯就開啟。開啟方式與生產端不一樣。
  client:
    config:
      default:
        connectTimeout: 5000 # feign 的超時設定
        readTimeout: 5000 #同時配置一下feign的讀取 超時
#超時時間配置,此處全域性超時配置時間大於@HystrixProperty配置時間後,@HystrixProperty註解中的超時才生效
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 10000

  主啟動類Application上加上註解

@EnableHystrix

  業務類controller中加入

//主方法
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="1500")//1.5秒鐘以內就是正常的業務邏輯
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
    String result = paymentHystrixService.paymentInfo_TimeOut(id);
    return result;
}

//備用方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
    return "我是消費者80,對付支付系統繁忙請10秒鐘後再試或者自己執行出錯請檢查自己,(┬_┬)";
}

3,全域性服務降級(feign消費端)

對應原始碼:cloud-consumer-feign-hystrix-order80

  解決主方法一個備用方法的程式碼冗餘。(原始碼同上)

  yaml檔案中新增配置

feign:
  hystrix:
    enabled: true #如果處理自身的容錯就開啟。開啟方式與生產端不一樣。
  client:
    config:
      default:
        connectTimeout: 5000 # feign 的超時設定
        readTimeout: 5000 #同時配置一下feign的讀取 超時
#超時時間配置,此處全域性超時配置時間大於@HystrixProperty配置時間後,@HystrixProperty註解中的超時才生效
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 10000

  主啟動類Application上加上註解

@EnableHystrix

  業務類controller中編碼

@RestController
@DefaultProperties(defaultFallback="paymentGobalFallbackMethod", commandProperties = { //如果commandProperties沒有對應的內容就會走預設的HystrixCommandProperties中的配置 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")}) public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
//主方法 @GetMapping("/consumer/payment/hystrix/timeout/{id}") @HystrixCommand //標識走全域性的備用策略 public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; } //全域性備用方法 public String paymentGobalFallbackMethod() { return "Gobal全域性備選方法,物件系統繁忙或者已經down機了,請稍後再試"; } }

4,降級分離(feign消費端)

對應原始碼:cloud-consumer-feign-hystrix-order80

  yaml檔案配置

feign:
  hystrix:
    enabled: true #如果處理自身的容錯就開啟。開啟方式與生產端不一樣

  feign呼叫的介面

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentHystrixFallbackServiceImpl.class) //配置對應的fallback的類
public interface PaymentHystrixFallbackService {
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}

  feign呼叫介面的實現類

/**
 * fallback類,用於承載整個類的降級處理
 */
@Component
public class PaymentHystrixFallbackServiceImpl implements PaymentHystrixFallbackService {
    @Override
    public String paymentInfo_OK(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_OK , (┬_┬)";
    }

    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_TimeOut , (┬_┬)";
    }
}

  controller中正常呼叫即可

@Slf4j
@RestController
public class OrderHystrixControllerFallbackClass {

    @Resource
    private PaymentHystrixFallbackService paymentHystrixFallbackService;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/consumer_all/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id) {
        String result = paymentHystrixFallbackService.paymentInfo_OK(id);
        log.info("*******result:" + result);
        return result;
    }

    @GetMapping("/consumer_all/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        String result = paymentHystrixFallbackService.paymentInfo_TimeOut(id);
        return result;
    }
}

三、服務熔斷

  熔斷機制是應對雪崩效應的一種微服務鏈路保護機制。當扇出鏈路的某個微服務出錯不可用或者響應時間太長時,會進行服務的降級進而熔斷該節點微服務的呼叫,快速返回錯誤的響應資訊。當檢測到該節點微服務呼叫響應正常後,恢復呼叫鏈路

1,例項(提供端)

原始碼:cloud-provider-hystrix-payment8001

  啟動類

@EnableCircuitBreaker //啟動熔斷
@EnableEurekaClient
@SpringBootApplication
public class CloudHystrixPaymentApplicaiton8001{...}

  service

@Service
public class PaymentCircutBreakerService {
    //服務熔斷
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),  //是否開啟斷路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),   //請求次數
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),  //時間範圍
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //失敗率達到多少後跳閘
    })
    public String paymentCircuitBreaker(Integer id){
        if (id < 0){
            throw new RuntimeException("*****id 不能負數");
        }
        String serialNumber = IdUtil.simpleUUID();

        return Thread.currentThread().getName()+"\t"+"呼叫成功,流水號:"+serialNumber;
    }
    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
        return "id 不能負數,請稍候再試,(┬_┬)/~~     id: " +id;
    }
}

  controller

//===服務熔斷
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
    String result = paymentCircutBreakerService.paymentCircuitBreaker(id);
    log.info("*******result:"+result);
    return result;
}

  測試

http://localhost:8001/payment/circuit/-1  //多次呼叫報錯就會開啟熔斷,此時再呼叫正確發現不會立馬返回正確結果
http://localhost:8001/payment/circuit/1

2,服務熔斷原理

a)熔斷型別

  熔斷開啟:請求不在進行呼叫當前服務,內部設定始終一般為MTTR(平均故障處理時間),當開啟時長達到所設時鐘則進入熔斷狀態。

  熔斷關閉熔斷關閉不會對服務進行熔斷。

  熔斷半開部分請求根據規則呼叫當前服務,如果請求成功且符合規則則認為當前服務恢復正常,關閉熔斷。

b)斷路器開啟條件

@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
      @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),  //是否開啟斷路器
      @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),   //請求次數
      @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),  //時間範圍
      @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //失敗率達到多少後跳閘
})
涉及到斷路器的是哪個重要引數:快照時間視窗、請求總數閾值、錯誤百分比閾值。
1)快照時間:斷路器確定是否開啟需要統計一些請求和錯誤資料,而統計的時間範圍就是快照時間窗,預設為最近的10秒。
2)請求總數閾值:在快照時間窗內,必須滿足請求總數閾值才有資格熔斷。預設為20,意味著在10秒內,如果該hystrix命令的呼叫總次數不足20次,即使所有的請求都超時或其他原因失敗,斷路器都不會開啟。
3)錯誤百分比閾值:當請求總數在快照時間窗內超過閾值,比如發生了30次呼叫,如果在這30次呼叫中,有15次發生了超時異常,也就是超過50%的錯誤百分比,在預設設定50%閾值情況下,這時候就會將斷路器開啟。

c)斷路器開啟後續

  1,再有請求呼叫的時候,將不會呼叫主邏輯,而是直接呼叫降級fallback。通過斷路器,實現了自動地發現錯誤並將降級邏輯切換為主邏輯,減少響應延遲的效果。

  2,原來的主邏輯如何恢復:當斷路器開啟,對主邏輯進行熔斷之後,hystrix會啟動一個休眠時間窗,在這個窗內,降級邏輯是臨時的成為主邏輯;當休眠時間窗到期,斷路器會進入半開狀態,釋放一次請求到原來的主邏輯上,如果此時請求正常返回,那麼斷路器將繼續閉合,主邏輯恢復,如果這次請求依然有問題斷路器繼續進入開啟狀態,休眠時間窗重新計時。

d)各種引數

HystrixCommand:

commandKey:用來標識一個 Hystrix 命令,預設會取被註解的方法名。需要注意:Hystrix 裡同一個鍵的唯一標識並不包括 groupKey,建議取一個獨一二無的名字,防止多個方法之間因為鍵重複而互相影響。
groupKey:一組 Hystrix 命令的集合, 用來統計、報告,預設取類名,可不配置。
threadPoolKey:用來標識一個執行緒池,如果沒設定的話會取 groupKey,很多情況下都是同一個類內的方法在共用同一個執行緒池,如果兩個共用同一執行緒池的方法上配置了同樣的屬性,在第一個方法被執行後執行緒池的屬性就固定了,所以屬性會以第一個被執行的方法上的配置為準。
commandProperties:與此命令相關的屬性。
threadPoolProperties:與執行緒池相關的屬性,
observableExecutionMode:當 Hystrix 命令被包裝成 RxJava 的 Observer 非同步執行時,此配置指定了 Observable 被執行的模式,預設是 ObservableExecutionMode.EAGER,Observable 會在被建立後立刻執行,而 ObservableExecutionMode.EAGER模式下,則會產生一個 Observable 被 subscribe 後執行。我們常見的命令都是同步執行的,此配置項可以不配置。
ignoreExceptions:預設 Hystrix 在執行方法時捕獲到異常時執行回退,並統計失敗率以修改熔斷器的狀態,而被忽略的異常則會直接拋到外層,不會執行回退方法,也不會影響熔斷器的狀態。
raiseHystrixExceptions:當配置項包括 HystrixRuntimeException 時,所有的未被忽略的異常都會被包裝成 HystrixRuntimeException,配置其他種類的異常好像並沒有什麼影響。
fallbackMethod:方法執行時熔斷、錯誤、超時時會執行的回退方法,需要保持此方法與 Hystrix 方法的簽名和返回值一致。
defaultFallback:預設回退方法,當配置 fallbackMethod 項時此項沒有意義,另外,預設回退方法不能有引數,返回值要與 Hystrix方法的返回值相同。

commandProperties:

執行緒隔離(Isolation)
execution.isolation.strategy: 配置請求隔離的方式,有 threadPool(執行緒池,預設)和 semaphore(訊號量)兩種,訊號量方式高效但配置不靈活,我們一般採用 Java 裡常用的執行緒池方式。
execution.timeout.enabled:是否給方法執行設定超時,預設為 true。
execution.isolation.thread.timeoutInMilliseconds:方法執行超時時間,預設值是 1000,即 1秒,此值根據業務場景配置。
execution.isolation.thread.interruptOnTimeout: execution.isolation.thread.interruptOnCancel:是否在方法執行超時/被取消時中斷方法。需要注意在 JVM 中我們無法強制中斷一個執行緒,如果 Hystrix 方法裡沒有處理中斷訊號的邏輯,那麼中斷會被忽略。
execution.isolation.semaphore.maxConcurrentRequests:預設值是 10,此配置項要在 execution.isolation.strategy 配置為 semaphore 時才會生效,它指定了一個 Hystrix 方法使用訊號量隔離時的最大併發數,超過此併發數的請求會被拒絕。訊號量隔離的配置就這麼一個,也是前文說訊號量隔離配置不靈活的原因。
統計器(Metrics)
metrics.rollingStats.timeInMilliseconds:此配置項指定了視窗的大小,單位是 ms,預設值是 1000,即一個滑動視窗預設統計的是 1s 內的請求資料。
metrics.healthSnapshot.intervalInMilliseconds:它指定了健康資料統計器(影響 Hystrix 熔斷)中每個桶的大小,預設是 500ms,在進行統計時,Hystrix 通過 metrics.rollingStats.timeInMilliseconds / metrics.healthSnapshot.intervalInMilliseconds 計算出桶數,在視窗滑動時,每滑過一個桶的時間間隔時就統計一次當前視窗內請求的失敗率。
metrics.rollingStats.numBuckets:Hystrix 會將命令執行的結果型別都統計彙總到一塊,給上層應用使用或生成統計圖表,此配置項即指定了,生成統計資料流時滑動視窗應該拆分的桶數。此配置項最易跟上面的 metrics.healthSnapshot.intervalInMilliseconds 搞混,認為此項影響健康資料流的桶數。 此項預設是 10,並且需要保持此值能被 metrics.rollingStats.timeInMilliseconds 整除。
metrics.rollingPercentile.enabled:是否統計方法響應時間百分比,預設為 true 時,Hystrix 會統計方法執行的 1%,10%,50%,90%,99% 等比例請求的平均耗時用以生成統計圖表。
metrics.rollingPercentile.timeInMilliseconds:統計響應時間百分比時的視窗大小,預設為 60000,即一分鐘。
metrics.rollingPercentile.numBuckets:統計響應時間百分比時滑動視窗要劃分的桶用,預設為6,需要保持能被metrics.rollingPercentile.timeInMilliseconds 整除。
metrics.rollingPercentile.bucketSize:統計響應時間百分比時,每個滑動視窗的桶內要保留的請求數,桶內的請求超出這個值後,會覆蓋最前面儲存的資料。預設值為 100,在統計響應百分比配置全為預設的情況下,每個桶的時間長度為 10s = 60000ms / 6,但這 10s 內只保留最近的 100 條請求的資料。
熔斷器(Circuit Breaker)
circuitBreaker.enabled:是否啟用熔斷器,預設為 true;
circuitBreaker.forceOpen: circuitBreaker.forceClosed:是否強制啟用/關閉熔斷器,強制啟用關閉都想不到什麼應用的場景,保持預設值,不配置即可。
circuitBreaker.requestVolumeThreshold:啟用熔斷器功能視窗時間內的最小請求數。試想如果沒有這麼一個限制,我們配置了 50% 的請求失敗會開啟熔斷器,視窗時間內只有 3 條請求,恰巧兩條都失敗了,那麼熔斷器就被開啟了,5s 內的請求都被快速失敗。此配置項的值需要根據介面的 QPS 進行計算,值太小會有誤開啟熔斷器的可能,值太大超出了時間視窗內的總請求數,則熔斷永遠也不會被觸發。建議設定為 QPS * 視窗秒數 * 60%。
circuitBreaker.errorThresholdPercentage:在通過滑動視窗獲取到當前時間段內 Hystrix 方法執行的失敗率後,就需要根據此配置來判斷是否要將熔斷器開啟了。 此配置項預設值是 50,即視窗時間內超過 50% 的請求失敗後會開啟熔斷器將後續請求快速失敗。
circuitBreaker.sleepWindowInMilliseconds:熔斷器開啟後,所有的請求都會快速失敗,但何時服務恢復正常就是下一個要面對的問題。熔斷器開啟時,Hystrix 會在經過一段時間後就放行一條請求,如果這條請求執行成功了,說明此時服務很可能已經恢復了正常,那麼會將熔斷器關閉,如果此請求執行失敗,則認為服務依然不可用,熔斷器繼續保持開啟狀態。此配置項指定了熔斷器開啟後經過多長時間允許一次請求嘗試執行,預設值是 5000
其他(Context/Fallback)
requestCache.enabled:是否啟用請求結果快取。預設是 true,但它並不意味著我們的每個請求都會被快取。快取請求結果和從快取中獲取結果都需要我們配置 cacheKey,並且在方法上使用 @CacheResult 註解宣告一個快取上下文。
requestLog.enabled:是否啟用請求日誌,預設為 true。
fallback.enabled:是否啟用方法回退,預設為 true 即可。
fallback.isolation.semaphore.maxConcurrentRequests:回退方法執行時的最大併發數,預設是10,如果大量請求的回退方法被執行時,超出此併發數的請求會丟擲 REJECTED_SEMAPHORE_FALLBACK 異常。

threadPoolProperties:

coreSize:核心執行緒池的大小,預設值是 10,一般根據 QPS * 99% cost + redundancy count 計算得出。
allowMaximumSizeToDivergeFromCoreSize:是否允許執行緒池擴充套件到最大執行緒池數量,預設為 false;
maximumSize:執行緒池中執行緒的最大數量,預設值是 10,此配置項單獨配置時並不會生效,需要啟用 allowMaximumSizeToDivergeFromCoreSize 項。
maxQueueSize:作業佇列的最大值,預設值為 -1,設定為此值時,佇列會使用 SynchronousQueue,此時其 size 為0,Hystrix 不會向佇列記憶體放作業。如果此值設定為一個正的 int 型,佇列會使用一個固定 size 的 LinkedBlockingQueue,此時在核心執行緒池內的執行緒都在忙碌時,會將作業暫時存放在此佇列內,但超出此佇列的請求依然會被拒絕。
queueSizeRejectionThreshold:由於 maxQueueSize 值線上程池被建立後就固定了大小,如果需要動態修改佇列長度的話可以設定此值,即使佇列未滿,佇列內作業達到此值時同樣會拒絕請求。此值預設是 5,所以有時候只設定了 maxQueueSize 也不會起作用。
keepAliveTimeMinutes:由上面的 maximumSize,我們知道,執行緒池核心心執行緒數目都在忙碌,再有新的請求到達時,執行緒池容量可以被擴充為到最大數量,等到執行緒池空閒後,多於核心數量的執行緒還會被回收,此值指定了執行緒被回收前的存活時間,預設為 2,即兩分鐘。

四、Hystrix dashboard

原始碼:cloud-consumer-hystrix-dashboard9001

1,yaml檔案

#指定埠號即可
server:
  port: 9001

2,application啟動類

@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class,args);
    }
}

3,被監控類(provider服務)

pom檔案

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

application啟動類中新增

//SpringCloud升級方便使用hystrix的dashboard監控
@Bean
public ServletRegistrationBean getServlet(){
    HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
    registrationBean.setLoadOnStartup(1);
    registrationBean.addUrlMappings("/hystrix.stream");
    registrationBean.setName("HystrixMetricsStreamServlet");
    return registrationBean;
}

4,啟動並使用

訪問地址:http://localhost:9001/hystrix

    SpringCould中的Hystrix

 

 應用說明

        SpringCould中的Hystrix

 

相關文章