Hystrix系列之执行原理分析

上文中介绍了Hystrix的由来,本文会深入分析Hystrix的执行过程。

Hystrix的大部分逻辑基于RxJava,其实现让很热多人望而却步,停留在了仅仅使用的地步,从一个简单的HelloWorld开始。

public class CommandHelloWorld extends HystrixCommand<String> {

    private final String name;

    public CommandHelloWorld(String name) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.name = name;
    }

    @Override
    protected String run() {
        // 省略业务逻辑
        // 该方法可能会抛出异常
        return "Hello " + name + "!";
    }

    @Override
    protected String getFallback() {
        return "Hello Failure " + name + "!";
    }
}

通过简单的实现run方法和getFallback 方法,CommandHelloWorld具备了熔断降级的功能,其中HystrixCommand提供了4个方法:execute(),queue(),observe(),toObservable(),平时只需要关注execute和queue即可。

  • queue():
    异步调用,返回一个Future对象,后面可以通过Future获取结果。
  • execute()
    同步调用,调用后直接block住,直到依赖服务返回结果,或者抛出异常。
public R execute() {
    try {
        return queue().get();
    } catch (Exception e) {
        throw Exceptions.sneakyThrow(decomposeException(e));
    }
}

其实同步方式是通过直接执行Future的get方法进行实现的,这里需要知道这个返回的Future对象到底是什么?

通过实现可以发现,返回的Futrue对象只是对toObservable返回结果的封装代理,把注意力转移到toObservable方法的实现。

第一次看到这个方法的实现,也许会很崩溃,方法内部初始化了一系列的Action0对象,这是RxJava的内部对象,可以看作是订阅一个事件后的回调。

一开始,不要太过在意这些Action,不然会陷入迷失,在debug的时候,通过不断的回调,经常会不知身处何处。

直接看到toObservable方法的return处,又是一坨回调,崩溃。

如果不熟悉RxJava语法,看这种代码真心的累,call方法的前面部分主要做两件事。
1、记录请求日志(日志功能开启)
2、从缓存中返回结果(定义了cacheKey方法,并且缓存功能开启)

如果缓存没有开启,或者返回null,那只能执行正常逻辑从下游服务拿取数据,这里通过上面定义的applyHystrixSemantics Action进行回调,最终执行的是applyHystrixSemantics方法,这个方法才是精华所在,想调试的同学,直接在这个方法入口打个断点,事半功倍。

其中circuitBreaker.attemptExecution()的返回结果,决定了接下去是执行正常逻辑、还是降级逻辑,这才是精华的精华。

看一下熔断器的attemptExecution方法,内部涉及了多个开关

  • forceOpen
    强制开启,所以请求都执行降级逻辑
  • forceClose
    强制关闭,所以请求都执行正常逻辑
  • circuitOpened
    熔断开关,默认为-1,请求执行正常逻辑,如果发生熔断,该值会被修改成0,请求执行降级逻辑
  • HALF_OPEN
    熔断半开,即熔断之后,每隔一段会进行试探

个人觉得,这里的实现过于复杂。

如果没有发生熔断,还有一道门槛,Hystrix提供了一个信号量限流器,限制进入熔断器最大并发数,可以控制请求下游的并发量,如果超过这个阈值,会被降级处理,有效的保护下游服务不会被突发流量给攻击。

通过的请求,继续调用executeCommandAndObserve方法,在该方法中,又定义了一堆让人迷惑的Action,不过这次通过名字和实现,可以知道个大概,先把这些Action放一边,看看接下去会执行的方法。

private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {
     // 忽略一堆Action的定义
    Observable<R> execution;
    if (properties.executionTimeoutEnabled().get()) {
        execution = executeCommandWithSpecifiedIsolation(_cmd)
                .lift(new HystrixObservableTimeoutOperator<R>(_cmd));
    } else {
        execution = executeCommandWithSpecifiedIsolation(_cmd);
    }
    return execution.doOnNext(markEmits)
        .doOnCompleted(markOnCompleted)
        .onErrorResumeNext(handleFallback)
        .doOnEach(setRequestContext);
}

Hystrix内部提供了超时检查的机制,如果参数executionTimeoutEnabled开启,则每次请求都会提交一个任务到线程池中延迟执行。由于Hystrix实现中考虑的东西太多,所以在实现上还是很复杂。

这里只给出了关键逻辑,如果配置了超时时间10ms,会提交一个延迟10ms执行的任务,其中tick方法会通过CAS机制保证超时状态的变更,最终对应command的会执行onError方法,这里加入的HystrixContextRunnable主要为了跨线程的上下文数据传递。

在执行正常逻辑的实现中,Hystrix内部提供了信号量、线程池两种模式,默认使用线程池模式。在executeCommandWithSpecifiedIsolation方法中,分别对这两种模式进行了处理,而且可以根据参数随时进行切换,这就是为什么线程池一开始就要初始化的原因,虽然有资源的消耗,但是带来了更好的灵活性,在需要的时候可以从信号量模式变成线程池模式进行隔离。

下面以信号量的方式为例,分析下如何执行用户自定义的run方法.

又想吐槽Hystrix的代码,又是这种回调,在call实现中,暂时忽略前面的一大坨逻辑,跟进getUserExecutionObservable方法。

继续查看getExecutionObservable方法,该方法在HystrixCommand中被重写实现。

run方法终于执行了。run方法执行之后,如果正常返回、抛出异常、或者其它情况,都需要对应的后续处理,这时之前executeCommandAndObserve方法中定义的Action,就开始起作用了。

execution.doOnNext(markEmits)
    .doOnCompleted(markOnCompleted)
    .onErrorResumeNext(handleFallback)
    .doOnEach(setRequestContext);
  • markEmits
    run方法正常返回时执行,主要记录执行耗时;触发执行成功的通知事件,可以通过扩展插件做更多事情;如果当前是熔断状态,则关闭熔断。

  • handleFallback
    run方法发生异常时执行,最终执行降级逻辑,但是整个过程实现还是很复杂的。

后续文章
Hystrix系列之自动熔断和恢复
Hystrix系列之插件实现

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容