Hystrix分布式系統限流、降級、熔斷框架(一)


一、為什么要用hystrix

       在大中型分布式系統中,通常系統很多依賴,如下圖:

cmd-markdown-logo

       在高並發訪問下,這些依賴的穩定性與否對系統的影響非常大,但是依賴有很多不可控問題:如網絡連接緩慢,資源繁忙,暫時不可用,服務脫機等,如下圖:

cmd-markdown-logo

       在高流量的情況下,一個后端依賴項的延遲可能導致所有服務器上的所有資源在數秒內飽和(PS:意味着后續再有請求將無法立即提供服務)

cmd-markdown-logo

例如,對於一個依賴於30個服務的應用程序,每個服務都有99.99%的正常運行時間,期望如下:

0.9999^30  =  99.7% 可用

也就是說一億個請求的0.3% = 300000 會失敗

如果一切正常,那么一年有26.2個小時服務是不可用的

當集群依賴50個服務時:

0.9999^50  =  99.5% 可用, 一年43.8小時不可用

       分布式系統環境下,服務間類似依賴非常常見,一個業務調用通常依賴多個基礎服務。如下圖,對於同步調用,當庫存服務不可用時,商品服務請求線程被阻塞,當有大批量請求調用庫存服務時,最終可能導致整個商品服務資源耗盡,無法繼續對外提供服務。並且這種不可用可能沿請求調用鏈向上傳遞,這種現象被稱為雪崩效應。

cmd-markdown-logo

雪崩效應常見場景

硬件故障: 如服務器宕機,機房斷電,光纖被挖斷等;

流量激增: 如異常流量,重試加大流量等;

緩存穿透: 一般發生在應用重啟,所有緩存失效時,以及短時間內大量緩存失效時。大量的緩存不命中,使請求直擊后端服務,造成服務提供者超負荷運行,引起服務不可用;

程序BUG: 如程序邏輯導致內存泄漏,JVM長時間FullGC等;

同步等待: 服務間采用同步調用模式,同步等待造成的資源耗盡。

雪崩效應應對策略

針對造成雪崩效應的不同場景,可以使用不同的應對策略,沒有一種通用所有場景的策略,參考如下:

硬件故障: 多機房容災、異地多活等;

流量激增:服務自動擴容、流量控制(限流、關閉重試)等;

緩存穿透: 緩存預加載、緩存異步加載等;

程序BUG:修改程序bug、及時釋放資源等;

同步等待:資源隔離、MQ解耦、不可用服務調用快速失敗等。資源隔離通常指不同服務調用采用不同的線程池;不可用服務調用快速失敗一般通過熔斷器模式結合超時機制實現。

       Hystrix,中文含義是豪豬,因其背上長滿棘[jí]刺,從而擁有了自我保護的能力。本文所說的Hystrix是Netflix開源的一款容錯框架,同樣具有自我保護能力,實現了容錯和自我保護。

       Netflix Hystrix是SOA/微服務架構中提供服務隔離、熔斷、降級機制的工具/框架。Netflix Hystrix是斷路器的一種實現,用於高微服務架構的可用性,是防止服務出現雪崩的利器。

Hystrix流程解析

cmd-markdown-logo

流程說明:

  1. 每次調用創建一個新的HystrixCommand,把依賴調用封裝在run()方法中;

  2. 執行execute()/queue做同步或異步調用;

       判斷是否使用緩存響應請求,若啟用了緩存,且緩存可用,直接使用緩存響應請求。Hystrix支持請求緩存,但需要用戶自定義啟動;

  1. 判斷熔斷器(circuit-breaker)是否打開,如果打開跳到步驟8,進行降級策略,如果關閉進入步驟4;

  2. 判斷線程池/隊列/信號量是否跑滿,如果跑滿進入降級步驟8,否則繼續后續步驟;

  3. 調用HystrixCommand的run方法,運行依賴邏輯:

       5a:依賴邏輯調用超時,進入步驟8;

  1. 判斷邏輯是否調用成功:

       6a. 返回成功調用結果

       6b. 調用出錯,進入步驟8

  1. 計算熔斷器狀態,所有的運行狀態(成功, 失敗, 拒絕,超時)上報給熔斷器,用於統計從而判斷熔斷器狀態;

  2. getFallback()降級邏輯

       8a. 沒有實現getFallback的Command將直接拋出異常

       8b. fallback降級邏輯調用成功直接返回

       8c. 降級邏輯調用失敗拋出異常

       以下四種情況將觸發getFallback調用:

       (1). run()方法拋出非HystrixBadRequestException異常。

       (2). run()方法調用超時

       (3). 熔斷器開啟攔截調用

       (4). 線程池/隊列/信號量是否跑滿

  1. 返回執行成功結果。

二、Hystrix命令

Hystrix有兩個請求命令:HystrixCommand和HystrixObservableCommand。

cmd-markdown-logo

HystrixCommand用在依賴服務返回單個操作結果的時候。有兩種執行方式:

execute()

以同步堵塞方式執行run(),只支持接收一個值對象。hystrix會從線程池中取一個線程來執行run(),並等待返回值。

queue()

以異步非阻塞方式執行run(),只支持接收一個值對象。調用queue()就直接返回一個Future對象。可通過Future.get()拿到run()的返回結果,但Future.get()是阻塞執行的。若執行成功,Future.get()返回單個返回值。當執行失敗時,如果沒有重寫fallback,Future.get()拋出異常。

使用示例:

public class MyHystrixCommand extends HystrixCommand<String> {

    private final String param;

    public MyHystrixCommand(String param) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("testGroup")).
                andCommandKey(HystrixCommandKey.Factory.asKey("testCommand")).
                andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("testThreadPool")));
        this.param = param;
    }

    @Override
    protected String run() throws Exception {
        int i = 1/0;    //模擬異常
        return "hello " + param + "!";
    }

    @Override
    protected String getFallback() {
        return "execute faild";
    }

    public static void main(String [] args) throws ExecutionException, InterruptedException {
        MyHystrixCommand command = new MyHystrixCommand("test execute");
        System.out.println(command.execute());

        Future<String> future = new MyHystrixCommand("test queue").queue();
        System.out.println(future.get());
    }
}

HystrixObservableCommand 用在依賴服務返回多個操作結果的時候。它也實現了兩種執行方式:

observe()

事件注冊前執行construct(),支持接收多個值對象,取決於發射源。調用observe()會返回一個hot Observable,也就是說,調用observe()自動觸發執行construct(),無論是否存在訂閱者。

observe()使用方法:

調用observe()會返回一個Observable對象

調用這個Observable對象的subscribe()方法完成事件注冊,從而獲取結果。

toObservable()

事件注冊后執行construct(),支持接收多個值對象,取決於發射源。調用toObservable()會返回一個cold Observable,也就是說,調用toObservable()不會立即觸發執行construct(),必須有訂閱者訂閱Observable時才會執行。

使用示例:

public class MyObservableCommand extends HystrixObservableCommand<String>{

    private final String param;

    public MyObservableCommand(String param) {
        super(HystrixObservableCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("testGroup")).
                andCommandKey(HystrixCommandKey.Factory.asKey("testCommand")));
        this.param = param;
    }

    @Override
    protected Observable<String> construct() {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                try {
                    if(!subscriber.isUnsubscribed()) {
                        subscriber.onNext("Hello");
                        int i = 1 / 2; //模擬異常
                        subscriber.onNext(param + "!");
                        subscriber.onCompleted();
                    }
                } catch (Exception e) {
                    subscriber.onError(e);
                }
            }
        }).subscribeOn(Schedulers.io());
    }

    /**
     * 服務降級
     */
    @Override
    protected Observable<String> resumeWithFallback() {
        return Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                try {
                    if (!subscriber.isUnsubscribed()) {
                        subscriber.onNext("execute faild!");
                        subscriber.onNext("figure out the reason!");
                        subscriber.onCompleted();
                    }
                } catch (Exception e) {
                    subscriber.onError(e);
                }
            }
        }).subscribeOn(Schedulers.io());
    }

    public static void main(String [] args){
        Observable<String> observable = new MyObservableCommand("observable").observe();
        Iterator<String> iterator = observable.toBlocking().getIterator();
        while(iterator.hasNext()) {
            System.out.println("observable >>>>" + iterator.next());
        }

        Observable<String> observable2 = new MyObservableCommand("toObservable").toObservable();
        ReplaySubject subject = ReplaySubject.create();
        observable2.subscribe(subject);
        Iterator<String> iterator2 = observable2.toBlocking().getIterator();
        while(iterator2.hasNext()) {
            System.out.println("toObservable >>>>" + iterator2.next());
        }
    }
}

       如果繼承的是HystrixCommand,hystrix會從線程池中取一個線程以非阻塞方式執行run(),調用線程不必等待run();如果繼承的是HystrixObservableCommand,將以調用線程堵塞執行construct(),調用線程需等待construct()執行完才能繼續往下走。

       需注意的是,HystrixCommand也支持toObservable()和observe(),但是即使將HystrixCommand轉換成Observable,它也只能發射一個值對象。只有HystrixObservableCommand才支持發射多個值對象。

幾種方法之前的關系

cmd-markdown-logo

execute()實際是調用了queue().get()

queue()實際調用了toObservable().toBlocking().toFuture()

observe()實際調用toObservable()獲得一個cold Observable,再創建一個ReplaySubject對象訂閱Observable,將源Observable轉化為hot Observable。因此調用observe()會自動觸發執行run()/construct()。

Hystrix總是以Observable的形式作為響應返回,不同執行命令的方法只是進行了相應的轉換。


免責聲明!

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



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