1. 程式人生 > 其它 >springcloud-一篇搞定Hystrix

springcloud-一篇搞定Hystrix

Hystrix是什麼?

基礎概念

服務雪崩

在分散式微服務的系統中,一定是會存在某個A服務呼叫多個服務,而多個服務也會繼續去掉其他服務。因此服務的呼叫,從呼叫的結構上看是“扇形”趨勢。
因此就會存在一個很重大的問題:如果其中一個服務出現問題了咋辦,比如超時,宕機,異常....就會導致前面的呼叫方在死等著,佔據著資源。這還只是一個請求情況下,如果是在高併發的情況下,由於多個服務滯留佔用資源,發生蝴蝶效應,導致系統整體服務使用出現緩慢甚至不可以,叫服務雪崩。
這個影響主要有兩個:

  1. 假設A服務出現異常,呼叫A服務所在的服務鏈路將不能完成整體業務功能
  2. 由於A服務出現異常,那些滯留佔用資源的服務影響到了其他正常服務的使用。

Hystrix是為了解決第二個問題而出現的延遲和容錯的開源庫。第一種影響解決不了,本身A服務有問題,所在那條鏈路肯定完成不了整體功能了,但起碼不能影響其他服務的使用吧

服務降級

服務降級:就是說使用者傳送請求到A服務介面,由於A服務出現不明故障導致不能及時使用者返回正確的結果,這時啟動備用方案;
一般來說備用方案,通常是返回一個友好的提示給使用者,並不能真正完成使用者要做的業務
導致服務降級的幾種情況:

  1. 程式執行異常
  2. 超時
  3. 服務熔斷呼叫服務降級
  4. 執行緒池打滿

總結:服務降級是一種備用方案。

服務熔斷

服務熔斷:就是說使用者傳送請求到A服務介面,由於A服務出現不明重大故障導致不能給使用者返回正確的結果,直接把A服務熔斷掉,直接拒絕使用者訪問A服務了
這裡也說下服務熔斷和服務降級的區別:

出現服務降級的情況,可能這時的A服務還是正常的,只不過當前壓力比較大,大部分請求能及時響應,部分請求無法及時響應,因此給部分無法及時響應給出備用方案
而服務熔斷,是A服務受到了極大的影響,整個服務不可用了,這時直接拒絕使用者訪問A服務是熔斷操作

某個服務發生了服務熔斷後,後續一般會先 先呼叫服務降級,給使用者一個友好提示,接著恢復服務的使用。

服務限流

服務限流理解就比較簡單點,A服務1秒鐘只能處理10個請求,現在同一時間有100個請求呼叫A服務,A服務肯定承受不了,因此服務限流可以理解為將這100個請求進行排隊,不要擠在一塊訪問A服務

服務降級

Hystrix使用方式1:在controller介面加上@HystrixCommand,並在controller類加上備案方法

降級在生產者服務端

  1. 在主啟動類加上@EnableHystrix
  2. 給可能會出現高併發或超時的介面加上@HystrixCommand,並編寫備案方法,如下:
    @GetMapping("/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
    })
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) throws InterruptedException
    {
        String result = paymentService.paymentInfo_TimeOut(id);
        log.info("****result: "+result);
        return result;
    }

    public String paymentInfo_TimeOutHandler(Integer id){
        return "/(ㄒoㄒ)/呼叫支付介面超時或異常:\t"+ "\t當前執行緒池名字" + Thread.currentThread().getName();
    }

如果呼叫paymentInfo_TimeOut方法超過3秒或者異常,則會呼叫paymentInfo_TimeOutHandler方法進行響應。

降級在消費者服務端

  1. yml加上如下,因為消費者服務使用feign作為服務呼叫,和hystrix有緊密合作:
feign:
  hystrix:
    enabled: true
  1. 在主啟動類加上@EnableHystrix
  2. 示例程式碼:
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
    })
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
    {
        String result = paymentHystrixApi.paymentInfo_TimeOut(id);
        return result;
    }
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
    {
        return "我是消費者80,對方支付系統繁忙請10秒鐘後再試或者自己執行出錯請檢查自己,o(╥﹏╥)o";
    }

總結

上述的寫法有兩個弊病:

  1. 備案方法和controller耦合在一起了
  2. 每個需要降級的方法都得對應一個備案方法,程式碼冗餘

Hystrix使用方式2:在controller介面加上@DefaultProperties,在controller需要降級的方法加上@HystrixCommand

使用方式1的弊病之一:程式碼會出現冗餘。而使用方式2是一定程度上減少了程式碼冗餘,具體使用步驟如下(啟用註解和yml步驟省略):

  1. 在controller類上加上@DefaultPropertie,如下:
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod",commandProperties = {
        @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
})
public class OrderHystirxController
{}
  1. 在@DefaultProperties所在類,需要降級的方法加上@HystrixCommand,如下:
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
    {
        String result = paymentHystrixApi.paymentInfo_TimeOut(id);
        return result;
    }

總結

優點:

  1. 一定程度上減少了降級方法冗餘
  2. 類裡的降級方法,@HystrixCommand如果沒有指定具體的降級方法和超時時間,則走預設的@DefaultProperties。如果指定了,則走自己的。

缺點:

  1. 備案方法和controller耦合在一起了

Hystrix使用方式3:建立一個備案類實現ClientApi介面,重寫的方法即為備案方法

話不多說,操作如下(啟用註解和yml省略):

  1. 建立PaymentHystrixApiFallback實現 PaymentHystrixApi,重寫的方法即為備案方法:
@Component
public class PaymentHystrixApiFallback implements PaymentHystrixApi {

    @Override
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        return "服務呼叫失敗,提示來自:cloud-consumer-feign-order80";
    }

    @Override
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        return "服務呼叫失敗,提示來自:cloud-consumer-feign-order80";
    }
}
  1. 在指定備案類PaymentHystrixApiFallback,如下:
@Component
@FeignClient(value = "cloud-provider-hystrix-payment",fallback = PaymentHystrixApiFallback.class)
public interface PaymentHystrixApi {
    @GetMapping("/payment/hystrix/ok/{id}")
    String paymentInfo_OK(@PathVariable("id") Integer id);

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

假設我們呼叫paymentInfo_OK方法失敗了,就會呼叫其重寫方法返回"服務呼叫失敗,提示來自:cloud-consumer-feign-order80";

總結

優點:

  1. 將降級方法抽取出來,實現解耦。
    2.從根本上解決問題,此方式偏向考慮服務之間呼叫失敗而做出的降級反饋,而方式1和2有很大的不同,方式1和2是針對自身方法,而方式3針對的是服務呼叫方法

缺點:

  1. 每個服務介面都有一個備案方法,程式碼冗餘

方式3使用的會比較多一點,但具體情況依專案而定;服務降級的使用多用在消費者服務端

服務熔斷

Hystrix服務熔斷有3個狀態,open,close,half-open

open: 熔斷已開啟
close:熔斷關閉
half-open:熔斷半開啟

如果服務在正常情況下,一般是close狀態,如果由於某種原因出現熔斷了,會進行open狀態,在休眠一段時間後會進入half-open狀態,嘗試處理請求,如果處理請求還是失敗的,又會回到open狀態,反之close。
程式碼示例實際上和服務降級差不多,但不一樣是服務熔斷只針對自身方法,如下:

    //=========服務熔斷
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
	//是否開啟熔斷機制
            @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
			//某段時間內請求次數閾值
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
			//進入open狀態,等待多久才進入到half-open狀態
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
			//達到多少失敗百分比進入open狀態
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),
    })
    public String paymentCircuitBreaker(@PathVariable("id") 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 不能負數,請稍後再試,/(ㄒoㄒ)/~~   id: " +id;
    }

上面的一些引數可舉個例子:最近10次如果失敗率達到60%以上,則進入open狀態,後面的請求(不管對錯)直接返回備案方法。在休眠10秒後進入half-open狀態嘗試處理請求,如果請求能正常處理,則進入close狀態,反之open。