天天看點

springcloud元件入門使用——Hystrix

一. 分布式系統面臨的問題

複雜分布式體系結構中的應用程式有數十個依賴關系,每個依賴關系在某些時候将不可避免地損失的。

服務雪崩

多個微服務之間調用的時候,假設微服務A調用微服務B和微服務C,微服務B和微服務C又調用其它的微服務,這就是所謂的"扇出"。

如果扇出的鍊路上某個微服務的調用響應時間過長或者不可用,對微服務A的調用就會占用越來越多的系統資源,進而引起系統崩潰,所謂的“雪崩效應”.

對于高流量的應用來說,單-的後端依賴可能會導緻所有伺服器上的所有資源都在幾秒鐘内飽和。比失敗更糟糕的是,這些應用程式還可能導緻服務之間的延遲增加,備份隊列,線程和其他系統資源緊張,導緻整個系統發生更多的級聯故障。這些都表示需要對故障和延遲進行隔離和管理,以便單個依賴關系的失敗,不能取消整個應用程式或系統。

是以,通常當你發現一個子產品下的某個執行個體失敗後,這時候這個子產品依然還會接收流量,然後這個有問題的子產品還調用了其他的子產品,這樣就會發生級聯故障,或者叫雪崩。

二. Hystrix是什麼?

Hystrix是一個用于處理分布式系統的延遲和容錯的開源庫, 在分布式系統裡,許多依賴不可避免的會調用失敗,比如逾時、異常等

Hystrix能夠保證在一個依賴出問題的情況下,不會導緻整體服務失敗,避免級聯故障,以提高分布式系統的彈性。

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

但是停更了…,不影響先學一波,畢竟設計理論與思想最重要嘛,大家都是抄作業,像阿裡的sentinel…

總結一下:

Hystrix主要有三個功能:

  1. 服務降級
  2. 服務熔斷
  3. 服務限流

其中服務降級和服務熔斷最為突出,這裡對于服務降級和服務熔斷做一個簡單說明:

服務降級:例如我們通路一個網站時,有時由于網絡/并發高,此時網站會給我們響應一個伺服器忙請稍後再試。此時因為網絡或某些原因,網站的背景暫時處理不過來,為了不讓使用者等太長時間,此時就會直接傳回一個服務降級頁面給使用者。

springcloud元件入門使用——Hystrix

服務熔斷:這裡相當于一個保險絲,當保險絲斷了的時候就會利用服務降級給客戶提示,并直接拒絕通路服務,當保險絲再某段時間修好了就會自動又恢複功能。熔斷相對于降級是一個更高壓力和網站錯誤出現的情況,而服務降級更多是網絡/高通路的情況觸發的保護系統其他功能的實作。

服務熔斷不等于服務降級,服務熔斷錯誤程度大于服務降級,服務熔斷通過服務降級通知使用者不可用,當服務可用時會自動恢複。

那麼此時怎麼使用Hystrix進行服務降級、熔斷處理?

一、服務降級:

1、當要在提供者服務做服務降級:

pom:

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

啟動類加上:@EnableHystrix注解

在相應處理服務降級的方法/接口上編寫,這裡改造了一個接口

(超過3秒就觸發降級方法paymentInfoTimeOutHandler,這裡我睡眠了4秒)

@HystrixCommand(fallbackMethod = "paymentInfoTimeOutHandler",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    @GetMapping("/payment/lb")
    public String getPaymentLB() throws InterruptedException {
        Thread.sleep(4000);
        return serverPort;
    }
    public String  paymentInfoTimeOutHandler(){
        return "程式運作繁忙或報錯,請稍後再試*****"+"目前線程: "+Thread.currentThread().getName()+id+"\t "+"orz!";
    }
           
http://localhost:8001/payment/lb
springcloud元件入門使用——Hystrix
如果我想應用到整個controller類的所有接口:
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;

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



//    @HystrixCommand(fallbackMethod = "paymentInfoTimeOutHandler",commandProperties = {
//            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
//    })
    @HystrixCommand  //将之前的注釋掉,如果沒注釋掉還是會走自己方法上定義的規則
    @GetMapping("/payment/lb")
    public String getPaymentLB() throws InterruptedException {

       int i =10/0;
        return serverPort;
    }
    public String  paymentInfoTimeOutHandler(){
        return "程式運作繁忙或報錯,請稍後再試*****"+"目前線程: "+Thread.currentThread().getName()+"\t "+"orz!";
    }

    public String payment_Global_FallbackMethod(){
        return "Global異常處理資訊,請稍後再試: orz~";
    }

}
           
springcloud元件入門使用——Hystrix

注意如果用消費者去掉提供者,其中提供者有兩個服務叢集,其中一個出錯了,另一個沒出錯,此時會去調用沒出錯的提供者。

2、消費者實作降級(如果提供者有做降級處理,則調用出錯傳回提供者降級結果,如是消費者自己出錯/提供者沒做降級處理則傳回自己的那個降級結果)

pom:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
           
yml:(提供者不用)
#yml添加配置,開啟 hystrix
feign:
  hystrix:
    enabled: true
           
在feign接口上:其中PaymentHystrixServiceImpl是降級處理類
@FeignClient(value = "cloud-payment-service",fallback = PaymentHystrixServiceImpl.class)//調用的服務名
@Component
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id);

    @GetMapping(value="/payment/lb")
    public String getPaymentLB() throws InterruptedException;
}
           
編寫降級處理類:(實作feign接口)
@Component
public class PaymentHystrixServiceImpl implements PaymentFeignService {


    @Override
    public CommonResult getPaymentById(Long id) {
        return null;
    }

    @Override
    public String getPaymentLB() throws InterruptedException {
        return "程式運作繁忙或報錯,請稍後再試*****"+"目前線程: "+Thread.currentThread().getName()+"\t "+"orz!";
    }
}
           
啟動類:
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderOpenFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderOpenFeignMain80.class,args);
    }
}
           
controller:
@RestController
public class OrderFeignController {

    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping(value = "/consumer/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id){
        CommonResult paymentById = paymentFeignService.getPaymentById(id);
        return paymentById ;
    }

    @GetMapping(value = "/consumer/payment/lb")
    public String getPaymentB() throws InterruptedException {
        String paymentLB = paymentFeignService.getPaymentLB();

        return paymentLB ;
    }
}
           
此時遠端提供者我們僞造其接口出錯:
@GetMapping("/payment/lb")
    public String getPaymentLB() throws InterruptedException {

       int i = 10/0;
        return serverPort;
    }
           
測試:http://localhost/consumer/payment/lb
springcloud元件入門使用——Hystrix

至于服務熔斷,也是調用服務降級方法,隻是我們可以配置服務熔斷的條件:

例如:下面表示在10s内,請求10次如果成功低于60則進行熔斷
@HystrixCommand(fallbackMethod="paymentCircuitBreakerFallback", 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(@PathVariable("id") Integer id){
        if (id<0){
            throw new RuntimeException();
        }
        String serialNumber = IdUtil.simpleUUID();
        return Thread.currentThread().getName()+"\t "+"調用成功,流水号: "+serialNumber;
    }
    public String paymentCircuitBreakerFallback(@PathVariable("id") Integer id){
        return "id不能為負數,請稍後再試~ id: "+ id;
    }
           
springcloud元件入門使用——Hystrix

這裡就不作測試了

總結

  • 熔斷打開

    請求不再進行調用目前服務,内部設定時鐘一般為MTTR (平均故障處理時間),當打開時長達到所設時鐘則進入半熔斷狀态

  • 熔斷關閉

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

  • 熔斷半開

    部分請求根據規則調用目前服務,如果請求成功且符合規則則認為目前服務恢複正常,關閉熔斷

設計熔斷的三個參數
springcloud元件入門使用——Hystrix

就是這哥三~

涉汲到斷路器的三個重要參數:快照時間窗、請求總數閥值、錯誤百分比閥值。

  • 1:快照時間窗:斷路器确定是否打開需要統計一些請求和錯誤資料,而統計的時間範圍就是快照時間窗,預設為最近的10秒。
  • 2:請求總數閥值:在快照時間窗内,必須滿足請求總數閥值才有資格熔斷。預設為20, 意味着在10秒内,如果該hystrix指令的調用次數不足20次,即使所有的請求都逾時或其他原因失敗,斷路器都不會打開。
  • 3:錯誤百分比閥值:當請求總數在快照時間窗内超過了閥值,比如發生了30次調用,如果在這30次調用中,有15次發生了逾時異常,也就是超過50%的錯誤百分比,在預設設定50%閥值情況下,這時候就會将斷路器打開。

原來的主邏輯要如何恢複呢?

對于這一-問題,hystrix也為我們實作了自動恢複功能。

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

斷路器開啟/關閉條件

springcloud元件入門使用——Hystrix

Hystrix 全部配置一覽

此部分内容,可參考官方文檔:https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.strategy
@HystrixCommand(fallbackMethod = "str_fallbackMethod",
    groupKey = "strGroupCommand",
    commandKey = "strCommand",
    threadPoolKey = "strThreadPool",

    commandProperties = {
        //設定執行隔離政策,THREAD 表示線程池   SEMAPHORE:信号量隔離    預設為THREAD線程池
        @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
        // 當隔離政策選擇信号池隔離的時候,用來設定信号池的大小(最大并發數)
        @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
        // 配置指令執行的逾時時間
        @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
        // 是否啟用逾時時間
        @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
        // 執行逾時的時候是否中斷
        @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
        // 執行被取消的時候是否中斷
        @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
        // 允許回調方法執行的最大并發數
        @HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
        // 服務降級是否啟用,是否執行回調函數
        @HystrixProperty(name = "fallback.enabled", value = "true"),
        // 設定斷路器是否起作用。
        @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
        // 該屬性用來設定在滾動時間窗中,斷路器熔斷的最小請求數。例如,預設該值為 20 的時候,
        // 如果滾動時間窗(預設10s)内僅收到了19個請求,及時這19個請求都失敗了,斷路也不會打開。
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
        // 該屬性用來設定在滾動時間窗中,表示在滾動時間窗中,在請求數量超過 circuitBreaker.requestVolumeThreshold 的情況下,
        // 如果錯誤請求數的百分比超過 50,就把斷路器設定為"打開"狀态,否則就設定為"關閉"狀态
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
        // 該屬性用來設定當斷路器打開之後的休眠時間窗。休眠時間窗結束之後,會将斷路器置為"半開"狀态,
        // 嘗試熔斷的請求指令,如果依然失敗就将斷路器繼續設定為"打開"狀态,如果成功就設定為"關閉"狀态
        @HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
        // 斷路器強制打開
        @HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
        // 斷路器強制關閉
        @HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
        // 滾動時間窗設定,該時間用于斷路器判斷健康度時,需要收集資訊的持續時間
        @HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
        // 該屬性用來設定滾動時間窗統計名額資訊時,劃分"桶"的數量,斷路器在手機名額資訊的時候會根據設定的時間窗長度拆分成多個"桶"來累計各路徑成本,每個
        // "桶"記錄了一段時間内的采集名額。比如 10 秒内拆分成 10 個"桶'收集這樣,是以 timeinMilliseconds 必須能被 numBuckets 整除。否則會抛異常
        @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
        // 該屬性用來設定對指令執行的延遲是否采用百分位數來跟蹤和計算。如果設定為 false,name所有的概要統計都将傳回-1
        @HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
        // 該屬性用來設定百分位統計的滾動視窗的持續時間,機關為毫秒
        @HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
        // 該屬性用來設定百分位統計滾動視窗中使用 "桶" 的數量
        @HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
        // 該屬性用來設定在執行過程中每個"桶"中保留的最大執行次數。如果在滾動時間窗内發生超過該設定值的執行次數
        // 就從最初的位置開始重寫。例如,将該值設定為100,滾動視窗為10秒,若在10秒内一個"桶"中發生了500次執行,
        // 那麼該"桶"中隻保留最後的100次執行的統計。另外,增加該值的大小将會增加記憶體量的消耗,并增加排序百分位數所需的計算時間
        @HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
        // 該屬性用來設定采集意向斷路器狀态的健康快照(請求的成功、錯誤百分比)的間隔等待時間
        @HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
        // 是否開啟請求緩存
        @HystrixProperty(name = "requestCache.enabled", value = "true"),
        // HystrixCommand 的執行和事件是否列印日志到 HystrixRequestLog 中
        @HystrixProperty(name = "requestLog.enabled", value = "true")
    },
    threadPoolProperties = {
        // 該參數用來設定執行指令線程池的核心線程數,該值也就是指令執行的最大并發量
        @HystrixProperty(name = "coreSize", value = "10"),
        // 該參數用來設定線程池的最大隊列大小。當設定為 -1 時,線程池将使用 SynchronousQueue 實作的隊列,否則将使用 LinkedBlockingQueue 實作的隊列
        @HystrixProperty(name = "maxQueueSize", value = "-1"),
        // 該參數用來為隊列設定拒絕門檻值。通過該參數,即使隊列沒有達到最大值也能拒絕請求。該參數主要是對 LinkedBlockingQueue 隊列的補充,因為LinkedBlockingQueue
        // 隊列不能動态修改它的對象大小,而通過該屬性就可以調整拒絕請求的隊列大小了
        @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5")
    }
)
           

Hystrix DashBoard 圖形化界面搭建

這裡就不多說了,因為我也沒建成功,後面再去實作一下

還有Hystrix的限流就不講了現在都是用sentinel。Hystrix雖然已經不行了但是其設計思想和解決方案是非常不錯的,

eureka、hystrix雖然不太行了,但面試經常問,是以之後會對hystrix的原理寫一些分析分析。

(eureka、Hystrix、gateway、ribbon、openfign、nacos、Sentinel、seata都會對其原理做分析)

繼續閱讀