SpringCloud容錯處理:Hystrix源碼分析

容錯處理

容錯處理是指軟件運行時,能對由非正常因素引起的運行錯誤給出適當的處理或信息提示,使軟件運行正常結束

從解釋中可以看出,簡單理解,所謂容錯處理其實就是捕獲異常了,不讓異常影響系統的正常運行,正如java中的try catch一樣。

而在SpringCloud微服務調用中,自身異常可自行處理外,對於依賴的服務若發生錯誤,或者調用異常,或者調用時間過長等原因時,避免長時間等待,造成系統資源耗盡。
一般上都會通過設置請求的超時時間,如http請求中的ConnectTimeoutReadTimeout;再或者就是使用熔斷器模式,隔離問題服務,防止級聯錯誤等。

爲了防止服務之間的調用異常造成的連鎖反應,在SpringCloud中提供了Hystrix組件來實現服務調用異常的處理,或對高併發情況下的服務降級處理 。

什麼是Hystrix

 在分佈式系統中,服務與服務之間依賴錯綜複雜,一種不可避免的情況就是某些服務將會出現失敗。

Hystrix是一個庫,它提供了服務與服務之間的容錯功能,主要體現在延遲容錯和容錯,從而做到控制分佈式系統中的聯動故障。Hystrix通過隔離服務的訪問點,阻止聯動故障,並提供故障的解決方案,從而提高了這個分佈式系統的彈性。

Hystrix容錯機制:

  • 包裹請求使用HystrixCommand包裹對依賴的調用邏輯,每個命令在獨立線程中執行,這是用到了設計模式“命令模式”。
  • 跳閘機制:當某服務的錯誤率超過一定閾值時,Hystrix可以自動或手動跳閘,停止請求該服務一段時間
  • 資源隔離Hystrix爲每個依賴都維護了一個小型的線程池,如果該線程池已滿,發往該依賴的請求就被立即拒絕,而不是排隊等候,從而加速判定失敗。
  • 監控:Hystrix可以近乎實時的監控運行指標和配置的變化。如成功、失敗、超時、被拒絕的請求等。
  • 回退機制:當請求失敗、超時、被拒絕,或當斷路器打開時,執行回退邏輯。回退邏輯可自定義。
  • 自我修復:斷路器打開一段時間後,會自動進入半開狀態,斷路器打開、關閉、半開的邏輯轉換

下圖就是Hystrix的回退策略,防止級聯故障。

Hystrix fallback prevents cascading failures

Hystrix的簡單使用

1.要使用 Hystrix熔斷機制處理引入它本身的依賴之外,我們需要在主程序配置類上引入 @EnableHystrix 標籤 開啓Hystrix功能,如下

@EnableHystrix
@EnableEurekaClient
@SpringBootApplication
...
public class ConsumerApplication {

2.開啓Hystrix熔斷機制後,對方法進行熔斷處理

@Service
public class HelloService {

    @Autowired
    private RestTemplate restTemplate;

    //該註解對該方法創建了熔斷器的功能,並指定了fallbackMethod熔斷方法
    @HystrixCommand(fallbackMethod = "hiError")
    public String hiService(String name){
        //調用接口進行消費
        String result = restTemplate.getForObject("http://PRODUCER/hello?name="+name,String.class);
        return result;
    }
    public String hiError(String name) {
        return "hi,"+name+"error!";
    }
}

當hiService方法第調用異常,會觸發 fallbackMethod執行的hiError方法做成一些補救處理

Hystrix的工作原理

下圖顯示通過Hystrix向服務依賴關係發出請求時會發生什麼:

Hystrix向服務依賴關係發出請求時會發生什麼

具體將從以下幾個方面進行描述:

1.構建一個HystrixCommand或者HystrixObservableCommand 對象。

第一步是構建一個HystrixCommand或HystrixObservableCommand對象來表示你對依賴關係的請求。 其中構造函數需要和請求時的參數一致。

構造HystrixCommand對象,如果依賴關係預期返回單個響應。 可以這樣寫:

HystrixCommand command = new HystrixCommand(arg1, arg2);

同理,可以構建HystrixObservableCommand :

HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);

2.執行Command

通過使用Hystrix命令對象的以下四種方法之一,可以執行該命令有四種方法(前兩種方法僅適用於簡單的HystrixCommand對象,並不適用於HystrixObservableCommand):

  • execute()–阻塞,,然後返回從依賴關係接收到的單個響應(或者在發生錯誤時拋出異常)
  • queue()–返回一個可以從依賴關係獲得單個響應的future 對象
  • observe()–訂閱Observable代表依賴關係的響應,並返回一個Observable,該Observable會複製該來源Observable
  • toObservable() –返回一個Observable,當您訂閱它時,將執行Hystrix命令併發出其響應
K             value   = command.execute();
Future<K>     fValue  = command.queue();
Observable<K> ohValue = command.observe();         
Observable<K> ocValue = command.toObservable();

同步調用execute()調用queue().get(). queue()依次調用toObservable().toBlocking().toFuture()。 這就是說,最終每個HystrixCommand都由一個Observable實現支持,甚至是那些旨在返回單個簡單值的命令。

3.響應是否有緩存?

如果爲該命令啓用請求緩存,並且如果緩存中對該請求的響應可用,則此緩存響應將立即以“可觀察”的形式返回。

4.斷路器是否打開?

當您執行該命令時,Hystrix將檢查斷路器以查看電路是否打開。

如果電路打開(或“跳閘”),則Hystrix將不會執行該命令,但會將流程路由到(8)獲取回退。

如果電路關閉,則流程進行到(5)以檢查是否有可用於運行命令的容量。

5.線程池/隊列/信號量是否已經滿負載?

如果與命令相關聯的線程池和隊列(或信號量,如果不在線程中運行)已滿,則Hystrix將不會執行該命令,但將立即將流程路由到(8)獲取回退。

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

在這裏,Hystrix通過您爲此目的編寫的方法調用對依賴關係的請求,其中之一是:

  • HystrixCommand.run() – 返回單個響應或者引發異常
  • HystrixObservableCommand.construct() – 返回一個發出響應的Observable或者發送一個onError通知

如果run()或construct()方法超出了命令的超時值,則該線程將拋出一個TimeoutException(或者如果命令本身沒有在自己的線程中運行,則會產生單獨的計時器線程)。 在這種情況下,Hystrix將響應通過8進行路由。獲取Fallback,如果該方法不取消/中斷,它會丟棄最終返回值run()或construct()方法。

請注意,沒有辦法強制潛在線程停止工作 – 最好的Hystrix可以在JVM上執行它來拋出一個InterruptedException。 如果由Hystrix包裝的工作不處理InterruptedExceptions,Hystrix線程池中的線程將繼續工作,儘管客戶端已經收到了TimeoutException。 這種行爲可能使Hystrix線程池飽和,儘管負載“正確地流失”。 大多數Java HTTP客戶端庫不會解釋InterruptedExceptions。 因此,請確保在HTTP客戶端上正確配置連接和讀/寫超時。

如果該命令沒有引發任何異常並返回響應,則Hystrix在執行某些日誌記錄和度量報告後返回此響應。 在run()的情況下,Hystrix返回一個Observable,發出單個響應,然後進行一個onCompleted通知; 在construct()的情況下,Hystrix返回由construct()返回的相同的Observable。

7.計算Circuit 的健康

Hystrix向斷路器報告成功,失敗,拒絕和超時,該斷路器維護了一系列的計算統計數據組。

它使用這些統計信息來確定電路何時“跳閘”,此時短路任何後續請求直到恢復時間過去,在首次檢查某些健康檢查之後,它再次關閉電路。

8.獲取Fallback

當命令執行失敗時,Hystrix試圖恢復到你的回退:當construct()或run()(6.)拋出異常時,當命令由於電路斷開而短路時(4.),當 命令的線程池和隊列或信號量處於容量(5.),或者當命令超過其超時長度時。

編寫Fallback ,它不一依賴於任何的網絡依賴,從內存中獲取獲取通過其他的靜態邏輯。如果你非要通過網絡去獲取Fallback,你可能需要些在獲取服務的接口的邏輯上寫一個HystrixCommand。

9.返回成功的響應

如果Hystrix命令成功,它將以Observable的形式返回對呼叫者的響應或響應。 根據您在上述步驟2中調用命令的方式,此Observable可能會在返回給您之前進行轉換:

標題
  • execute() – 以與.queue()相同的方式獲取Future,然後在此Future上調用get()來獲取Observable發出的單個值
  • queue() – 將Observable轉換爲BlockingObservable,以便將其轉換爲Future,然後返回此未來
  • observe() – 立即訂閱Observable並啓動執行命令的流程; 返回一個Observable,當您訂閱它時,重播排放和通知
  • toObservable() – 返回Observable不變; 您必須訂閱它才能實際開始導致命令執行的流程

Hystrix的源碼解析

1. @EnableHystrix 開啓Hystrix

沿着我上面的使用方式來跟蹤一下 Hystrix的 源碼。

首先我們看一下標籤:@EnableHystrix ,他的作用從名字就能看出就是開啓Hystrix ,我們看一下它的源碼

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableCircuitBreaker
public @interface EnableHystrix {

}

它上面有一個註解:@ EnableCircuitBreaker (熔斷器),那麼@ EnableHystrix標籤的本質其實是@ EnableCircuitBreaker ,我們看一下他的源碼

/**
 * Annotation to enable a CircuitBreaker implementation.
 * http://martinfowler.com/bliki/CircuitBreaker.html
 * @author Spencer Gibb
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableCircuitBreakerImportSelector.class)
public @interface EnableCircuitBreaker {

}

@EnableCircuitBreaker標籤引入了一個@Import(EnableCircuitBreakerImportSelector.class) 類,字面翻譯的意思是開啓熔斷器的導入選擇器 ,導入什麼東西呢?看源碼

/**
 * Import a single circuit breaker implementation Configuration
 * @author Spencer Gibb
 */
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableCircuitBreakerImportSelector extends
        SpringFactoryImportSelector<EnableCircuitBreaker> {

    @Override
    protected boolean isEnabled() {
        return getEnvironment().getProperty(
                "spring.cloud.circuit.breaker.enabled", Boolean.class, Boolean.TRUE);
    }

}

其實EnableCircuitBreakerImportSelector的作用就是去導入熔斷器的配置 。其實Spring中也有類似於JAVA SPI 的加載機制, 即會自動加載 jar包 spring-cloud-netflix-core 中的META-INF/spring.factories 中的Hystrix相關的自動配置類
注:SPI : 通過將服務的接口與實現分離以實現解耦,提高程序拓展性的機制,達到插拔式的效果 。

 

HystrixCircuitBreakerConfiguration 就是針對於 Hystrix熔斷器的配置

/**
 * @author Spencer Gibb
 * @author Christian Dupuis
 * @author Venil Noronha
 */
@Configuration
public class HystrixCircuitBreakerConfiguration {

    @Bean
    public HystrixCommandAspect hystrixCommandAspect() {
        return new HystrixCommandAspect();
    }

    @Bean
    public HystrixShutdownHook hystrixShutdownHook() {
        return new HystrixShutdownHook();
    }

    @Bean
    public HasFeatures hystrixFeature() {
        return HasFeatures.namedFeatures(new NamedFeature("Hystrix", HystrixCommandAspect.class));
    }
......

在該配置類中創建了 HystrixCommandAspect


/**
 * AspectJ aspect to process methods which annotated with {@link HystrixCommand} annotation.
 */
@Aspect
public class HystrixCommandAspect {

    private static final Map<HystrixPointcutType, MetaHolderFactory> META_HOLDER_FACTORY_MAP;

    static {
        META_HOLDER_FACTORY_MAP = ImmutableMap.<HystrixPointcutType, MetaHolderFactory>builder()
                .put(HystrixPointcutType.COMMAND, new CommandMetaHolderFactory())
                .put(HystrixPointcutType.COLLAPSER, new CollapserMetaHolderFactory())
                .build();
    }

 //定義切點,切到 @HystrixCommand標籤所在的方法  
  @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)")
  public void hystrixCommandAnnotationPointcut() {
  }

  
@Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)")
  public void hystrixCollapserAnnotationPointcut() {
  }


   //針對切點:@hystrixCommand切點的處理
    @Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
    public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
    //獲取到目標方法
    Method method = getMethodFromTarget(joinPoint);
        Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint);
        //判斷方法上不能同時存在@HystrixCommand標籤和HystrixCollapser標籤
        if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) {
            throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " +
                    "annotations at the same time");
        }
        MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method));
        MetaHolder metaHolder = metaHolderFactory.create(joinPoint);
       //把方法封裝成 HystrixInvokable 
        HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
        ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?
                metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();

        Object result;
        try {
           // 通過CommandExecutor來執行方法
            if (!metaHolder.isObservable()) {
                result = CommandExecutor.execute(invokable, executionType, metaHolder);
            } else {
                result = executeObservable(invokable, executionType, metaHolder);
            }
        } catch (HystrixBadRequestException e) {
            throw e.getCause() != null ? e.getCause() : e;
        } catch (HystrixRuntimeException e) {
            throw hystrixRuntimeExceptionToThrowable(metaHolder, e);
        }
        return result;

HystrixCommandAspect 其實就是對 貼了@HystrixCommand標籤的方法使用 Aop機制實現處理 。代碼中通過把目標方法封裝成 HystrixInvokable對象,通過CommandExecutor工具來執行目標方法。

 

HystrixInvokable是用來幹嘛的?看源碼知道,其實他是一個空方法的接口,他的目的只是用來標記可被執行,那麼他是如何創建的我們看代碼HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);的create方法

   public HystrixInvokable create(MetaHolder metaHolder) {
        HystrixInvokable executable;
        ...省略代碼...
            executable = new GenericCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder));
        }
        return executable;
    }

其實是new了一個 GenericCommand 對象,很明顯他們是實現關係,我們看一下關係圖

跟蹤 GenericCommand 的源碼

@ThreadSafe
public class GenericCommand extends AbstractHystrixCommand<Object> {
    private static final Logger LOGGER = LoggerFactory.getLogger(GenericCommand.class);

    public GenericCommand(HystrixCommandBuilder builder) {
        super(builder);
    }

    protected Object run() throws Exception {
        LOGGER.debug("execute command: {}", this.getCommandKey().name());
        return this.process(new AbstractHystrixCommand<Object>.Action() {
            Object execute() {
                return GenericCommand.this.getCommandAction().execute(GenericCommand.this.getExecutionType());
            }
        });
    }

    protected Object getFallback() {
        final CommandAction commandAction = this.getFallbackAction();
        if (commandAction != null) {
            try {
                return this.process(new AbstractHystrixCommand<Object>.Action() {
                    Object execute() {
                        MetaHolder metaHolder = commandAction.getMetaHolder();
                        Object[] args = CommonUtils.createArgsForFallback(metaHolder, GenericCommand.this.getExecutionException());
                        return commandAction.executeWithArgs(metaHolder.getFallbackExecutionType(), args);
                    }
                });
            } catch (Throwable var3) {
                LOGGER.error(FallbackErrorMessageBuilder.create().append(commandAction, var3).build());
                throw new FallbackInvocationException(ExceptionUtils.unwrapCause(var3));
            }
        } else {
            return super.getFallback();
        }
    }
}

它本身對目標方法的正常執行和對 fallback方法的 執行做了實現 。
GenericCommand.this.getCommandAction().execute(...)獲取到目標方法並執行,底層會交給 MethodExecutionAction 使用反射去執行方法。

回到 HystrixCommandAspect的methodsAnnotatedWithHystrixCommand方法中,我們看下 CommandExecutor.execute是如何執行的

public class CommandExecutor {
    public CommandExecutor() {
    }

    public static Object execute(HystrixInvokable invokable, ExecutionType executionType, MetaHolder metaHolder) throws RuntimeException {
        Validate.notNull(invokable);
        Validate.notNull(metaHolder);
        switch(executionType) {
        //異步
        case SYNCHRONOUS:
            return castToExecutable(invokable, executionType).execute();
        //同步
        case ASYNCHRONOUS:
            HystrixExecutable executable = castToExecutable(invokable, executionType);
            if (metaHolder.hasFallbackMethodCommand() && ExecutionType.ASYNCHRONOUS == metaHolder.getFallbackExecutionType()) {
                return new FutureDecorator(executable.queue());
            }

            return executable.queue();
        case OBSERVABLE:
            HystrixObservable observable = castToObservable(invokable);
            return ObservableExecutionMode.EAGER == metaHolder.getObservableExecutionMode() ? observable.observe() : observable.toObservable();
        default:
            throw new RuntimeException("unsupported execution type: " + executionType);
        }
    }

    private static HystrixExecutable castToExecutable(HystrixInvokable invokable, ExecutionType executionType) {
        if (invokable instanceof HystrixExecutable) {
            return (HystrixExecutable)invokable;
        } else {
            throw new RuntimeException("Command should implement " + HystrixExecutable.class.getCanonicalName() + " interface to execute in: " + executionType + " mode");
        }
    }

這裏有兩種執行方式 SYNCHRONOUS 異步 ,ASYNCHRONOUS同步 ,我們先看異步: castToExecutable(invokable, executionType).execute();

這裏代碼把HystrixInvokable對象轉成 HystrixExecutable並調用execute方法執行 ,跟蹤execute方法進入HystrixCommand.execute方法中

 public R execute() {
        try {
            return queue().get();
        } catch (Exception e) {
            throw Exceptions.sneakyThrow(decomposeException(e));
        }
    }
--------------
 public Future<R> queue() {
        /*
         * The Future returned by Observable.toBlocking().toFuture() does not implement the
         * interruption of the execution thread when the "mayInterrupt" flag of Future.cancel(boolean) is set to true;
         * thus, to comply with the contract of Future, we must wrap around it.
         */
        final Future<R> delegate = toObservable().toBlocking().toFuture();
        
        final Future<R> f = new Future<R>() {

            @Override
            public boolean cancel(boolean mayInterruptIfRunning) {
                if (delegate.isCancelled()) {
                    return false;
                }

                if (HystrixCommand.this.getProperties().executionIsolationThreadInterruptOnFutureCancel().get()) {
                    /*
                     * The only valid transition here is false -> true. If there are two futures, say f1 and f2, created by this command
                     * (which is super-weird, but has never been prohibited), and calls to f1.cancel(true) and to f2.cancel(false) are
                     * issued by different threads, it's unclear about what value would be used by the time mayInterruptOnCancel is checked.
                     * The most consistent way to deal with this scenario is to say that if *any* cancellation is invoked with interruption,
                     * than that interruption request cannot be taken back.
                     */
                    interruptOnFutureCancel.compareAndSet(false, mayInterruptIfRunning);
                }

                final boolean res = delegate.cancel(interruptOnFutureCancel.get());

                if (!isExecutionComplete() && interruptOnFutureCancel.get()) {
                    final Thread t = executionThread.get();
                    if (t != null && !t.equals(Thread.currentThread())) {
                        t.interrupt();
                    }
                }

                return res;
            }
....省略...

在 HystrixCommand.execute方法中 其實是Future 來異步執行,調用過程中會觸發 GenericCommand來完成調用,執行完成後調用 Future.get()方法拿到執行結果 。HystrixCommand的queue方法實際上是調用了toObservable().toBlocking().toFuture()。

2.  @HystrixCommand

相關配置:HystrixCommandProperties

默認配置都在HystrixCommandProperties類中。

先看兩個metrics收集的配置。

  • metrics.rollingStats.timeInMilliseconds

表示滑動窗口的時間(the duration of the statistical rolling window),默認10000(10s),也是熔斷器計算的基本單位。

  • metrics.rollingStats.numBuckets

滑動窗口的Bucket數量(the number of buckets the rolling statistical window is divided into),默認10. 通過timeInMilliseconds和numBuckets可以計算出每個Bucket的時長。

metrics.rollingStats.timeInMilliseconds % metrics.rollingStats.numBuckets 必須等於 0,否則將拋異常。

再看看熔斷器的配置。

  • circuitBreaker.requestVolumeThreshold

滑動窗口觸發熔斷的最小請求數。如果值是20,但滑動窗口的時間內請求數只有19,那即使19個請求全部失敗,也不會熔斷,必須達到這個值才行,否則樣本太少,沒有意義。

  • circuitBreaker.sleepWindowInMilliseconds

這個和熔斷器自動恢復有關,爲了檢測後端服務是否恢復,可以放一個請求過去試探一下。sleepWindow指的發生熔斷後,必須隔sleepWindow這麼長的時間,才能放請求過去試探下服務是否恢復。默認是5s

  • circuitBreaker.errorThresholdPercentage

錯誤率閾值,表示達到熔斷的條件。比如默認的50%,當一個滑動窗口內,失敗率達到50%時就會觸發熔斷。

HystrixCommand類

Hystrix Command 執行過程中,各種情況都以事件形式發出,再封裝成特定的數據結構,最後匯入到事件流中(HystrixEventStream)。

事件流提供了 observe() 方法,搖身一變,事件流把自己變成了一個數據源(各小溪匯入成河,消費者從河裏取水),其他消費者可以從這裏獲取數據,而 circuit-breaker 就是消費者之一。

上面是官方完整的流程圖,策略是:

  • 不斷收集數據,達到條件就熔斷;
  • 熔斷後拒絕所有請求一段時間(sleepWindow);
  • 然後放一個請求過去,如果請求成功,則關閉熔斷器,否則繼續打開熔斷器。

查看HystrixCommand的父類AbstractCommand,其構造方法中定義了許多實例化hystrix所需的對象。

AbstractCommand這個類裏,有個initCircuitBreaker這個方法。


private static HystrixCircuitBreaker initCircuitBreaker(boolean enabled, HystrixCircuitBreaker fromConstructor, HystrixCommandKey commandKey...) {
    // 如果啓用了熔斷器
    if (enabled) {
        // 若commandKey沒有對應的CircuitBreaker,則創建
        if (fromConstructor == null) {
            return HystrixCircuitBreaker.Factory.getInstance(commandKey, groupKey, properties, metrics);
        } else {
            // 如果有則返回現有的
            return fromConstructor;
        }
    } else {
        return new NoOpCircuitBreaker();
    }
}

進入到com.netflix.hystrix.HystrixCircuitBreaker.Factory.getInstance觀察

 public static class Factory {
 // 維護一個ConcurrentHashMap保存熔斷器
        private static ConcurrentHashMap<String, HystrixCircuitBreaker> circuitBreakersByCommand = new ConcurrentHashMap();

        public Factory() {
        }

        public static HystrixCircuitBreaker getInstance(HystrixCommandKey key, HystrixCommandGroupKey group, HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
            HystrixCircuitBreaker previouslyCached = (HystrixCircuitBreaker)circuitBreakersByCommand.get(key.name());
            // 判斷ConcurrentHashMap中是否存有對應key的熔斷器對象
            if (previouslyCached != null) {
                return previouslyCached;
            } else {
            // 實例化並添加到ConcurrentHashMap裏
                HystrixCircuitBreaker cbForCommand = (HystrixCircuitBreaker)circuitBreakersByCommand.putIfAbsent(key.name(), new HystrixCircuitBreaker.HystrixCircuitBreakerImpl(key, group, properties, metrics));
                return cbForCommand == null ? (HystrixCircuitBreaker)circuitBreakersByCommand.get(key.name()) : cbForCommand;
            }
        }

        public static HystrixCircuitBreaker getInstance(HystrixCommandKey key) {
            return (HystrixCircuitBreaker)circuitBreakersByCommand.get(key.name());
        }

        static void reset() {
            circuitBreakersByCommand.clear();
        }
    }
}

接下來觀察HystrixCircuitBreaker的實現類HystrixCircuitBreakerImpl

 public static class HystrixCircuitBreakerImpl implements HystrixCircuitBreaker {
        // 熔斷器的屬性常量類
        private final HystrixCommandProperties properties;
       // 熔斷器的熔斷指標類
        private final HystrixCommandMetrics metrics;
        // 定義一個原子類型的布爾值狀態
        private AtomicBoolean circuitOpen = new AtomicBoolean(false);
        private AtomicLong circuitOpenedOrLastTestedTime = new AtomicLong();

        protected HystrixCircuitBreakerImpl(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
            this.properties = properties;
            this.metrics = metrics;
        }

// 重置統計數據
        public void markSuccess() {
            if (this.circuitOpen.get() && this.circuitOpen.compareAndSet(true, false)) {
                this.metrics.resetStream();
            }

        }

// 是否放行請求
        public boolean allowRequest() {
        // 熔斷器開啓則不放行
            if ((Boolean)this.properties.circuitBreakerForceOpen().get()) {
                return false;
            } else if ((Boolean)this.properties.circuitBreakerForceClosed().get()) {
                this.isOpen();
                return true;
            } else {
                return !this.isOpen() || this.allowSingleTest();
            }
        }

// 部分放行
        public boolean allowSingleTest() {
            long timeCircuitOpenedOrWasLastTested = this.circuitOpenedOrLastTestedTime.get();
            return this.circuitOpen.get() && System.currentTimeMillis() > timeCircuitOpenedOrWasLastTested + (long)(Integer)this.properties.circuitBreakerSleepWindowInMilliseconds().get() && this.circuitOpenedOrLastTestedTime.compareAndSet(timeCircuitOpenedOrWasLastTested, System.currentTimeMillis());
        }

// 判斷熔斷器是否開啓
        public boolean isOpen() {
            if (this.circuitOpen.get()) {
                return true;
            } else {
               // 根據採集到的數據進行判斷是否開啓
                HealthCounts health = this.metrics.getHealthCounts();
                if (health.getTotalRequests() < (long)(Integer)this.properties.circuitBreakerRequestVolumeThreshold().get()) {
                    return false;
                } else if (health.getErrorPercentage() < (Integer)this.properties.circuitBreakerErrorThresholdPercentage().get()) {
                    return false;
                } else if (this.circuitOpen.compareAndSet(false, true)) {
                    this.circuitOpenedOrLastTestedTime.set(System.currentTimeMillis());
                    return true;
                } else {
                    return true;
                }
            }
        }
    }

如何訂閱HystrixEventStream

本文最前面說到,HystrixEventStream提供了結構化的數據,提供了一個Observable對象,Hystrix只需要訂閱它即可。

這HystrixCircuitBreaker接口實現類的構造器:

protected HystrixCircuitBreakerImpl(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, final HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
    this.properties = properties;
    // 這是Command中的metrics對象,metrics對象也是commandKey維度的
    this.metrics = metrics;
    // !!!重點:訂閱事件流
    Subscription s = subscribeToStream();
    activeSubscription.set(s);
}
// 訂閱事件流, 前面打的比方: 小溪匯成的河, 各事件以結構化數據匯入了Stream中
private Subscription subscribeToStream() {
    // HealthCountsStream是重點,下面會分析
    return metrics.getHealthCountsStream()
            .observe()
            // 利用數據統計的結果HealthCounts, 實現熔斷器
            .subscribe(new Subscriber<HealthCounts>() {
                @Override
                public void onCompleted() {}
                @Override
                public void onError(Throwable e) {}
                @Override
                public void onNext(HealthCounts hc) {
                    // 檢查是否達到最小請求數,默認20個; 未達到的話即使請求全部失敗也不會熔斷
                    if (hc.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
                        // 啥也不做
                    } else {
                        // 錯誤百分比未達到設定的閥值
                        if (hc.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
                        } else {
                            // 錯誤率過高, 進行熔斷
                            if (status.compareAndSet(Status.CLOSED, Status.OPEN)) {
                                circuitOpened.set(System.currentTimeMillis());
                            }
                        }
                    }
                }
            });
}

HealthCounts 屬性如下,表示一個滑動窗口內的統計數據。

public static class HealthCounts {
    // rolling window 中請求總數量
    private final long totalCount;
    // 錯誤請求數(failure + success + timeout + threadPoolRejected + semaphoreRejected)
    private final long errorCount;
    // 錯誤率
    private final int errorPercentage;
}
熔斷器就是利用最終的統計結果HealthCounts來判斷是否進行熔斷。

熔斷器狀態變化

熔斷器有三種狀態,如下:

enum Status {
    CLOSED, OPEN, HALF_OPEN;
}
在Command的執行過程中,會調用HystrixCircuitBreaker的方法來更新狀態。下面是幾個重要的方法:

命令執行時,判斷熔斷器是否打開

// 是否允許執行
public boolean attemptExecution() {
    // 熔斷器配置了強制打開, 不允許執行命令
    if (properties.circuitBreakerForceOpen().get()) {
        return false;
    }
    // 熔斷器配置了強制關閉, 允許執行
    if (properties.circuitBreakerForceClosed().get()) {
        return true;
    }
    // AtomicLong circuitOpened, -1是表示熔斷器未打開
    if (circuitOpened.get() == -1) {
        return true;
    } else {
        // 熔斷後,會拒絕所有命令一段時間(默認5s), 稱爲sleepWindow
        if (isAfterSleepWindow()) {
            // 過了sleepWindow後,將熔斷器設置爲"HALF_OPEN",允許第一個請求過去
            if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
}

當Command成功執行結束時,會調用HystrixCircuitBreaker.markSuccess()來標記執行成功.

public void markSuccess() {
    // 如果是HALF_OPEN狀態,則關閉熔斷器
    if (status.compareAndSet(Status.HALF_OPEN, Status.CLOSED)) {
        // 重新開始統計metrics,拋棄所有原先的metrics信息
        metrics.resetStream();
        Subscription previousSubscription = activeSubscription.get();
        if (previousSubscription != null) {
            previousSubscription.unsubscribe();
        }
        Subscription newSubscription = subscribeToStream();
        activeSubscription.set(newSubscription);
        // circuitOpened設置爲-1表示關閉熔斷器
        circuitOpened.set(-1L);
    }
}

當Command執行失敗時, 如果熔斷器屬於HALF_OPEN狀態,也就是熔斷器剛過sleepWindow時間,嘗試放一個請求過去,結果又失敗了,於是馬上打開熔斷器,繼續拒絕sleepWindow的時間。

public void markNonSuccess() {
    if (status.compareAndSet(Status.HALF_OPEN, Status.OPEN)) {
        circuitOpened.set(System.currentTimeMillis());
    }
}

下面是調用markNonSuccess()的地方,handleFallback是所有失敗情況的處理者.

final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
    @Override
    public Observable<R> call(Throwable t) {
        circuitBreaker.markNonSuccess();
        Exception e = getExceptionFromThrowable(t);
        executionResult = executionResult.setExecutionException(e);
        // 線程池拒絕
        if (e instanceof RejectedExecutionException) {
            return handleThreadPoolRejectionViaFallback(e);
        // 超時
        } else if (t instanceof HystrixTimeoutException) {
            return handleTimeoutViaFallback();
        // Bad Request    
        } else if (t instanceof HystrixBadRequestException) {
            return handleBadRequestByEmittingError(e);
        } else {
            if (e instanceof HystrixBadRequestException) {
                eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
                return Observable.error(e);
            }
            return handleFailureViaFallback(e);
        }
    }
};

Circuit-Breaker的設計、實現都很有意思:

  • 滴水成河,收集每個命令的執行情況,彙總後通過滑動窗口,不斷動態計算最新統計數據,基於統計數據來開啓熔斷器
  • 巧妙的利用RxJava的window()函數來彙總數據,先彙總爲Bucket, N Bucket組成Rolling Window
  • 使用sleepWindow + 嘗試機制,自動恢復 “供電”

參考鏈接:

https://www.jianshu.com/p/07d45032bdff

https://www.javajike.com/article/2060.html

https://www.kancloud.cn/fymod/springcloud2/784132

https://www.cnblogs.com/handsome1013/p/11193746.html

 

發佈了410 篇原創文章 · 獲贊 1345 · 訪問量 208萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章