SpringCloud-源碼分析 Hystrix 熔斷器

本文作者:陳剛,叩丁狼高級講師集灌。原創(chuàng)文章悔雹,轉(zhuǎn)載請注明出處复哆。

回顧

為了防止服務(wù)之間的調(diào)用異常造成的連鎖反應(yīng),在SpringCloud中提供了Hystrix組件來實現(xiàn)服務(wù)調(diào)用異常的處理腌零,或?qū)Ω卟l(fā)情況下的服務(wù)降級處理 梯找。簡單回顧一下Hystrix的使用:
1.要使用 Hystrix熔斷機制處理引入它本身的依賴之外,我們需要在主程序配置類上貼 @EnableHystrix 標簽 開啟Hystrix功能益涧,如下

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

2.開啟Hystrix熔斷機制后锈锤,對方法進行熔斷處理

@Service
public class HelloService {

    @Autowired
    private RestTemplate restTemplate;

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

當hiService方法第調(diào)用異常闲询,會觸發(fā) fallbackMethod執(zhí)行的hiError方法做成一些補救處理久免。

那么我們就沿著我們的使用方式來跟蹤一下 Hystrix的 工作原理。

首先我們看一下標簽:@EnableHystrix 扭弧,他的作用從名字就能看出就是開啟Hystrix 阎姥,我們看一下它的源碼

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

}

它上面有一個注解:@ EnableCircuitBreaker ,翻譯單詞意思就是啟用熔斷器(斷路器),那么@ EnableHystrix標簽的本質(zhì)其實是@ 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) 類鸽捻,翻譯類的名字就是 呼巴, 開啟熔斷器的導(dǎo)入選擇器 ,導(dǎo)入什么東西呢泊愧?看源碼

/**
 * 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);
    }

}

翻譯類上的注釋 “Import a single circuit breaker implementation Configuration”伊磺,其實EnableCircuitBreakerImportSelector的作用就是去導(dǎo)入熔斷器的配置 。其實Spring中也有類似于JAVA SPI 的加載機制, 即會自動加載 jar包 spring-cloud-netflix-core 中的META-INF/spring.factories 中的Hystrix相關(guān)的自動配置類
注:SPI : 通過將服務(wù)的接口與實現(xiàn)分離以實現(xiàn)解耦删咱,提高程序拓展性的機制屑埋,達到插拔式的效果 。

image.png

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));
    }
......

在該配置類中創(chuàng)建了 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來執(zhí)行方法
            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機制實現(xiàn)處理 摘能。代碼中通過把目標方法封裝成 HystrixInvokable對象,通過CommandExecutor工具來執(zhí)行目標方法敲街。

HystrixInvokable是用來干嘛的团搞?看源碼知道,其實他是一個空行法的接口多艇,他的目的只是用來標記可被執(zhí)行逻恐,那么他是如何創(chuàng)建的我們看代碼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 對象,很明顯他們是實現(xiàn)關(guān)系峻黍,我們看一下關(guān)系圖


叩丁狼教育.png

跟蹤 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();
        }
    }
}

它本身對目標方法的正常執(zhí)行和對 fallback方法的 執(zhí)行做了實現(xiàn) 复隆。
GenericCommand.this.getCommandAction().execute(...)獲取到目標方法并執(zhí)行,底層會交給 MethodExecutionAction 使用反射去執(zhí)行方法姆涩,

回到 HystrixCommandAspect的methodsAnnotatedWithHystrixCommand方法中挽拂,我們看下 CommandExecutor.execute是如何執(zhí)行的

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");
        }
    }

這里有兩種執(zhí)行方式 SYNCHRONOUS 異步 ,ASYNCHRONOUS同步 骨饿,我們先看異步: castToExecutable(invokable, executionType).execute(); 這里代碼把HystrixInvokable對象轉(zhuǎn)成 HystrixExecutable并調(diào)用execute方法執(zhí)行 亏栈,跟蹤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 來異步執(zhí)行台腥,調(diào)用過程中會觸發(fā) GenericCommand來完成調(diào)用,執(zhí)行完成后調(diào)用 Future.get()方法拿到執(zhí)行結(jié)果 绒北。

想獲取更多技術(shù)干貨黎侈,請前往叩丁狼官網(wǎng):http://www.wolfcode.cn/all_article.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市镇饮,隨后出現(xiàn)的幾起案子蜓竹,更是在濱河造成了極大的恐慌,老刑警劉巖储藐,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嘶是,居然都是意外死亡钙勃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門聂喇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辖源,“玉大人,你說我怎么就攤上這事希太】巳模” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵誊辉,是天一觀的道長矾湃。 經(jīng)常有香客問我,道長堕澄,這世上最難降的妖魔是什么邀跃? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮蛙紫,結(jié)果婚禮上拍屑,老公的妹妹穿的比我還像新娘。我一直安慰自己坑傅,他們只是感情好僵驰,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著唁毒,像睡著了一般蒜茴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上枉证,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天矮男,我揣著相機與錄音,去河邊找鬼室谚。 笑死毡鉴,一個胖子當著我的面吹牛崔泵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猪瞬,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼憎瘸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了陈瘦?” 一聲冷哼從身側(cè)響起幌甘,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎痊项,沒想到半個月后锅风,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡鞍泉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年皱埠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咖驮。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡边器,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出托修,到底是詐尸還是另有隱情忘巧,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布睦刃,位于F島的核電站砚嘴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏眯勾。R本人自食惡果不足惜枣宫,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吃环。 院中可真熱鬧也颤,春花似錦、人聲如沸郁轻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽好唯。三九已至竭沫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骑篙,已是汗流浹背蜕提。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留靶端,地道東北人谎势。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓凛膏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脏榆。 傳聞我的和親對象是個殘疾皇子猖毫,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容