Soul網關源碼閱讀(十四)—— Hystrix插件詳解

Soul網關源碼閱讀(十四)—— Hystrix插件詳解

概要

上一篇介紹了hystrix插件的使用方法,這一篇我們來詳細介紹一下hystrix的原理及使用方法,並實現一個簡單的demo。

服務熔斷、降級的場景

微服務架構下,客戶端發起一個業務請求,通常情況下會在後端進行多次服務之間的調用。

試想,如果後端服務的調用順序爲A->B->C,如果C宕機了無法響應,那麼B的請求線程作爲調用方也會阻塞,最終可能導致服務器線程池中的線程爆掉,而導致B服務也不可用,直到整個系統崩潰,導致服務雪崩

img

爲了解決以上問題,我們可以進行服務熔斷降級處理。熔斷和降級一般都是成對出現的,但是他們又有一些區別。

熔斷是指依賴的外部接口出現故障的情況斷絕和外部接口的關係。

降級是指由於自身不能提供正常服務而採取的迫不得已的處理手段。

打個比方就是A調用B,B宕機了不能正常響應,A嘗試了幾次都沒能正常訪問B,於是A決定斷絕與B的交互。這個過程叫熔斷

但是A的可能也是服務方,它接收客戶端C的調用請求,由於A熔斷了B不能提供正常服務,但是它還是得給C一個交代,迫不得已採取一個替代方案,諸如返回一些報錯信息給A,是整個調用流程不受阻塞。這個過程叫服務降級

搞清楚我們面臨的問題過後,我們來思考一下對應的解決方案。

  1. 解決因爲服務B不可用,而導致服務A因爲線程阻塞而被打爆的問題
  2. 服務A如何判定服務B不可用,也就是需要一個抽象的熔斷規則,當滿足熔斷條件就關閉與B的調用,反之就開打。
  3. 熔斷後,需要一個代替方案,需要定義熔斷後的降級策略

以上三個問題的核心在於問題1,如何避免服務A因爲線程阻塞且增長導致的宕機?

容易想到的辦法就是將A調用B的線程,從服務器(如tomcat)接管過來,不讓tomcat直接調用B,而是先交給我們的熔斷器進行管理和處理,熔斷器有權不進行服務B的調用,而採取降級策略。

Hystrix原理

Hystrix是解決以上場景的解決方案,下圖展示了當你用使用 Hystrix 封裝後的客戶端請求一個服務時的流程。其中抽象的概念後面在一一解釋。

流程圖

在這裏插入圖片描述

1. 創建 HystrixCommand 或 HystrixObservableCommand 對象

​ 這兩個對象則是我們請求的委託的對象,他們負責發起請求。對於他們的區別,暫時先記住:

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

- execute():同步執行。從依賴的服務返回一個單一的結果對象,或是在發生錯誤的時候拋出異常。

- queue():異步執行。直接返回一個Future對象,其中包含了服務執行結束時要返回的單一結果對象。

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

- observe():返回Obervable對象,他代表了操作的多個結果,他是一個HotObservable

- toObservable():同樣返回Observable對象,也代表了操作多個結果,但它返回的是一個Cold Observable。

2. 執行 command

一共有四種方式可以執行 command,其中前兩種方式都只適用於簡單的 HystrixCommand 對象:

  • excute() — 以阻塞方式運行,並返回返回其包裝對象的響應值,或者拋出異常
  • queue() — 返回一個 Future 對象,你可以選擇在適當時機 get
  • observe() —
  • toObservable() —
K             value   = command.execute();
Future     fValue  = command.queue();
Observable ohValue = command.observe();         //hot observable
Observable ocValue = command.toObservable();    //cold observable

實際上,同步方法 execute() 底層邏輯是調用 queue().get(),然後 queue() 實際上是調用了 toObservable().toBlocking().toFuture(),也就是說所有 HystrixCommand 的邏輯都是走 Observable 實現

3. 請求是否使用緩存

如果開啓了請求緩存,並且該響應可以在緩存中找到,那就立刻返回緩存的響應值,而不會再走遠程調用邏輯

4. 是否開啓熔斷

當執行 command 時,Hystrix 會判斷熔斷是否開啓,如果是開啓狀態則走 (8) 進行 Fallback 降級策略,如果未開啓則走 (5) ,繼續下一步判斷是否可以執行 command

5. 線程池\隊列\信號量 是否已滿

如果上述三者已達到閾值,Hystrix 就會直接走 (8) 進行 Fallback 降級策略

6. HystrixObservableCommand.construct() 或 HystrixCommand.run()

執行調用邏輯。

7. 判斷斷路器健康狀態

8. 進行降級處理

9. 接收響應

soul-plugin-hystrix實戰

瞭解了hystrix的原理及使用流程過後我們來分析一下,soul中對hystrix的實現。

我們先看一下其目錄結構:
在這裏插入圖片描述

  • HystrixBuilder

    它是一個構造器,用於構造我們創建HystrixCommand或者HystrixObservableCommand是的構造參數,它封裝了我們的熔斷規則

    /**
         * this is build HystrixObservableCommand.Setter.
         *
         * @param hystrixHandle {@linkplain HystrixHandle}
         * @return {@linkplain HystrixObservableCommand.Setter}
         */
        public static HystrixObservableCommand.Setter build(final HystrixHandle hystrixHandle) {
         
         
            //設置默認值
            initHystrixHandleOnRequire(hystrixHandle);
            //groupKey
            HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey(hystrixHandle.getGroupKey());
            HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(hystrixHandle.getCommandKey());
            HystrixCommandProperties.Setter propertiesSetter =
                    HystrixCommandProperties.Setter()
                            .withExecutionTimeoutInMilliseconds((int) hystrixHandle.getTimeout())
                            .withCircuitBreakerEnabled(true)
                            .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
                            .withExecutionIsolationSemaphoreMaxConcurrentRequests(hystrixHandle.getMaxConcurrentRequests())
                            .withCircuitBreakerErrorThresholdPercentage(hystrixHandle.getErrorThresholdPercentage())
                            .withCircuitBreakerRequestVolumeThreshold(hystrixHandle.getRequestVolumeThreshold())
                            .withCircuitBreakerSleepWindowInMilliseconds(hystrixHandle.getSleepWindowInMilliseconds());
            return HystrixObservableCommand.Setter
                    .withGroupKey(groupKey)
                    .andCommandKey(commandKey)
                    .andCommandPropertiesDefaults(propertiesSetter);
        }
    
        /**
         * this is build HystrixCommand.Setter.
         * @param hystrixHandle {@linkplain HystrixHandle}
         * @return {@linkplain HystrixCommand.Setter}
         */
        public static HystrixCommand.Setter buildForHystrixCommand(final HystrixHandle hystrixHandle) {
         
         
            initHystrixHandleOnRequire(hystrixHandle);
            HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey(hystrixHandle.getGroupKey());
            HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(hystrixHandle.getCommandKey());
            HystrixCommandProperties.Setter propertiesSetter =
                    HystrixCommandProperties.Setter()
                            .withExecutionTimeoutInMilliseconds((int) hystrixHandle.getTimeout())
                            .withCircuitBreakerEnabled(true)
                            .withCircuitBreakerErrorThresholdPercentage(hystrixHandle.getErrorThresholdPercentage())
                            .withCircuitBreakerRequestVolumeThreshold(hystrixHandle.getRequestVolumeThreshold())
                            .withCircuitBreakerSleepWindowInMilliseconds(hystrixHandle.getSleepWindowInMilliseconds());
            HystrixThreadPoolConfig hystrixThreadPoolConfig = hystrixHandle.getHystrixThreadPoolConfig();
            HystrixThreadPoolProperties.Setter threadPoolPropertiesSetter =
                    HystrixThreadPoolProperties.Setter()
                            .withCoreSize(hystrixThreadPoolConfig.getCoreSize())
                            .withMaximumSize(hystrixThreadPoolConfig.getMaximumSize())
                            .withMaxQueueSize(hystrixThreadPoolConfig.getMaxQueueSize())
                            .withKeepAliveTimeMinutes(hystrixThreadPoolConfig.getKeepAliveTimeMinutes())
                            .withAllowMaximumSizeToDivergeFromCoreSize(true);
            return HystrixCommand.Setter
                    .withGroupKey(groupKey)
                    .andCommandKey(commandKey)
                    .andCommandPropertiesDefaults(propertiesSetter)
                    .andThreadPoolPropertiesDefaults(threadPoolPropertiesSetter);
        }
    
  • Command

    執行命令的接口,HystrixCommand和HystrixObservableCommand的擴展類需要實現它。

    它實現了一個默認的降級方法doFallback,主要邏輯是將請求跳轉到降級的uri上進行處理。

    /**
     * do fall back when some error occurs on hystrix execute.
     * @param exchange {@link ServerWebExchange}
     * @param exception {@link Throwable}
     * @return {@code Mono<Void>} to indicate when request processing is complete.
     */
    default Mono<Void> doFallback(ServerWebExchange exchange, Throwable exception) {
         
         
        if (Objects.isNull(getCallBackUri())) {
         
         
            Object error;
            error = generateError(exchange, exception);
            return WebFluxResultUtils.result(exchange, error);
        }
        DispatcherHandler dispatcherHandler =
            SpringBeanUtils.getInstance().getBean(DispatcherHandler.class);
        ServerHttpRequest request = exchange.getRequest().mutate().uri(getCallBackUri()).build();
        ServerWebExchange mutated = exchange.mutate().request(request).build();
        return dispatcherHandler.handle(mutated);
    }
    
  • HystrixCommand(這裏名字剛好取的相反,不要混淆了。)

    它是HystrixObservableCommand的擴展類,主要是實現器construct方法

     @Override
        protected Observable<Void> construct() {
         
         
            return RxReactiveStreams.toObservable(chain.execute(exchange));
        }
    
  • HystrixCommandOnThread

    它是HystrixCommand的擴展類,主要是實現run方法

      @Override
        protected Mono<Void> run() {
         
         
            RxReactiveStreams.toObservable(chain.execute(exchange)).toBlocking().subscribe();
            return Mono.empty();
        }
    
  • HystrixPlugin

    實現doEexcute方法,使用HystrixCommand或HystrixObservableCommand來調用請求。

    @Slf4j
    public class HystrixPlugin extends AbstractSoulPlugin {
         
         
    
        @Override
        protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
         
         
            final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
            assert soulContext != null;
            //構造從admin配置的規則封裝成HystrixHandle對象
            final HystrixHandle hystrixHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), HystrixHandle.class);
            if (StringUtils.isBlank(hystrixHandle.getGroupKey())) {
         
         
                hystrixHandle.setGroupKey(Objects.requireNonNull(soulContext).getModule());
            }
            if (StringUtils.isBlank(hystrixHandle.getCommandKey())) {
         
         
                hystrixHandle.setCommandKey(Objects.requireNonNull(soulContext).getMethod());
            }
            //根據HystrixIsolationModeEnum類型,選擇構建command對象,信號量——HystrixCommand,線程池——HystrixCommandOnThread(見步驟1)
            Command command = fetchCommand(hystrixHandle, exchange, chain);
            return Mono.create(s -> {
         
         
    						//執行command,execute方法或者toObservable方法(見步驟2)
                Subscription sub = command.fetchObservable().subscribe(s::success,
                        s::error, s::success);
                s.onCancel(sub::unsubscribe);
               //如果熔斷器打開會打印以下日誌
                if (command.isCircuitBreakerOpen()) {
         
         
                    log.error("hystrix execute have circuitBreaker is Open! groupKey:{},commandKey:{}", hystrixHandle.getGroupKey(), hystrixHandle.getCommandKey());
                }
            }).doOnError(throwable -> {
         
         
                //異常處理
                log.error("hystrix execute exception:", throwable);
                exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.ERROR.getName());
                chain.execute(exchange);
            }).then();
        }
    
        private Command fetchCommand(final HystrixHandle hystrixHandle, final ServerWebExchange exchange, final SoulPluginChain chain) {
         
         
            if (hystrixHandle.getExecutionIsolationStrategy() == HystrixIsolationModeEnum.SEMAPHORE.getCode()) {
         
         
                return new HystrixCommand(HystrixBuilder.build(hystrixHandle),
                    exchange, chain, hystrixHandle.getCallBackUri());
            }
            return new HystrixCommandOnThread(HystrixBuilder.buildForHystrixCommand(hystrixHandle),
                exchange, chain, hystrixHandle.getCallBackUri());
        }
    
       //...
    }
    

總結

這一篇粗略的介紹了服務熔斷、降級的基本概念,以及hystrix的實現原理。

其中還有很多細節地方沒有分析到,比如隔離策略線程池和信號量的實現原理是什麼,他們的區別是什麼?還有待繼續深入分析。另外一點是由於hystrix是基於事件流rxjava庫構建的,所以源碼中使用了大量的鏈式調用、異步處理等邏輯,所以需要補充這兩個基礎知識:

  1. 線程隔離策略實現原理。
  2. rxjava語法規則及原理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章