
SpringCloud系列—Spring Cloud 源码分析之Hystrix
作者 | 宇木木兮
来源 |今日头条
学习目标
- 手写Mini版的Hystrix
- RxJava知识梳理
- Hystrix的核心流程分析
- 源码验证
第1章 手写Mini版
上文中已经给大家介绍过了Hystrix的核心功能和使用了,它无非就是提供了熔断、降级、隔离等功能,其中熔断和隔离是目的,降级是结果。在使用过程中其实最核心的有三个注解:@EnableHystrix、@HystrixCommand和@HystrixCollapser。可以通过注解 @HystrixCommand、或者继承 HystrixCommand 来实现降级,以及一些请求合并等操作。
在正式讲解原理之前,我们首先要明确一个点,当采用 @HystrixCommand 注解来实现服务降级,在Hystrix 的内部是采用AOP的方式进行拦截处理请求的,这块内容,后面也会详细分析。我们这里就先来实现一下简易版的 Hystrix 来体会一下,主要分为以下步骤
- 定义自己的@HystrixCommand 注解。
- 实现拦截请求的处理逻辑。
- 测试调用。
1.自定义注解
2.自定义切面类
3.自定义测试
当请求http://localhost:8080/myhystrix/get/1时会触发降级,因为在服务端,当num=1时会休眠3s。
OK,这样我们就实现了一个简易版的HystrixCommand,但是我们只是实现了Hystrix的第一步,定义了一个注解和切面,但是它的底层逻辑远远没有这么简单,在讲源码之前,我们先来捋一捋RxJava是什么,因为Hystrix底层逻辑是基于响应式编程实现的。
第2章 RxJava体验
2.1 RxJava概述
RxJava 是一种响应式编程,来创建基于事件的异步操作库。基于事件流的链式调用、逻辑清晰简洁。
RxJava观察者模式的对比
- 传统观察者是一个被观察者多过观察者,当被观察者发生改变时候及时通知所有观察者
- RxJava是一个观察者多个被观察者,被观察者像链条一样串起来,数据在被观察者之间朝着一个方向传递,直到传递给观察者 。
其实说白了,就是在RxJava中存在2种概念,一种是被观察者,一种是观察者,当多个被观察者订阅了同一个观察者的时候,那么随着被观察者完成某个事件的时候就会去回调观察者。
2.2 观察者
Observer
Subscriber 抽象类与Observer 接口的区别
二者基本使用方式一致(在RxJava的subscribe过程中,Observer会先被转换成Subscriber再使用)
Subscriber抽象类对 Observer 接口进行了扩展,新增了两个方法:
- onStart():在还未响应事件前调用,用于做一些初始化工作,他是在subscribe 所在的线程调用,不能切换线程,所以不能进行界面UI更新比如弹框这些。
- unsubscribe():用于取消订阅。在该方法被调用后,观察者将不再接收响应事件,比如在onStop方法中可以调用此方法结束订阅。调用该方法前,先使用 isUnsubscribed() 判断状态,确定被观察者Observable是否还持有观察者Subscriber的引用。
2.3 被观察者
RxJava 提供了多种方法用于 创建被观察者对象Observable,这里介绍两种
2.4 订阅
2.5 案例
OK,大体的指导如何使用RxJava编程了,记下来我们开始撸源码。
第3章 源码解析
先上官网提供的源码流程图,从图上可以看出来,其实就是先去扫描带有HystrixCommand注解的方法,然后进行切面拦截,执行切面的逻辑。这个切面定义了两个方法:execute和queue,二选一进行调用,然后进入真正的拦截逻辑。所以入口是HystrixCommand注解,而开启Hystrix是@EnableHystrix注解。
进入到@EnableHystrix注解中
看到这步代码,我相信很多学过springboot的同学都很熟悉了,这里用到了Import注解,那肯定是引进来一些配置类了,然后我们再进
EnableCircuitBreakerImportSelector类中;
EnableCircuitBreakerImportSelector继承了SpringFactoryImportSelector,进入SpringFactoryImportSelector类后发现是我们熟悉的代码,它实现了DeferredImportSelector接口,实现了selectImports方法,selectImports方法会从配置文件spring.factories里加载对应的类 org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker,我们来看看spring.facotries文件。
对应EnableAutoConfiguration的这些实现类在spring启动的时候通过自动装配机制会去实例化并且注入到IoC容器中,这里我们核心关注
HystrixCircuitBreakerConfiguration类。
进入到这个切面类中会发现,这个切面主要针对了两个注解作为切入点@HystrixCommand和@HystrixCollapser,当执行这两个注解修饰的方法时,会被拦截执行
methodsAnnotatedWithHystrixCommand
3.1 HystrixCommandAspect
我们重点分析下同步处理,通过代码我们可以看到HystrixInvokable 是 GenericCommand,我们同步里的看下 CommandExecutor.execute(invokable, executionType, metaHolder)
这个方法主要用来执行命令,从代码中可以看出这里有三个执行类型,分别是同步、异步、以及响应式。其中,响应式又分为Cold Observable(observable.toObservable()) 和 HotObservable(observable.observe())默认的executionType=SYNCHRONOUS ,同步请求。
- execute():同步执行,返回一个单一的对象结果,发生错误时抛出异常。
- queue():异步执行,返回一个 Future 对象,包含着执行结束后返回的单一结果。
- observe():这个方法返回一个 Observable 对象,它代表操作的多个结果,但是已经被订阅者消费掉了。
- toObservable():这个方法返回一个 Observable 对象,它代表操作的多个结果,需要咱们自己手动订阅并消费掉。
类图关系如下:
通过GenericCommand一层层的往上翻,最终定位到HystrixCommand有个execute()
在上述代码中,重点来了,构建了一个
java.util.concurrent.Future ,然后调用 get的时候委派给 delegate,而 delegate来自于 toObservable().toBlocking().toFuture(); 这正是我们上面例子里面得代码。所以现在的重点应该放在 toObservable() 方法中:
3.2 toObservable
通过Observable定义一个被观察者,这个被观察者会被toObservable().toBlocking().toFuture() ,实际上这行代码的核心含义就是去根据一些熔断逻辑判断是执行真实的业务逻辑还是执行fallback的回调方法,然后将结果返回给Future。里面的 run() 方法就是执行正常的业务逻辑。这个方法主要做了以下几件事:
- 创建一堆的动作,我也不知道这些动作是干啥的,不重要。
- 判断是否开启了缓存,如果开了,而且也命中了,就去缓存里面以Observable形式返回一个缓存结果
- 创建一个被观察者,这个被观察者后面会去回调真实业务逻辑或者fallback。
核心逻辑是这个被观察者会去执行applyHystrixSemantics里面的动作
接下来看看核心逻辑applyHystrixSemantics
这里传入的_cmd是一个GenericCommand,最终会执行到这个GenericCommand中的run方法。
circuitBreaker.allowRequest() 这个是判断是否处于熔断状态的,true表示没有处于熔断状态,正常执行,否则,调用 handleShortCircuitViaFallback 实现服务降级,最终会回调到我们自定义的fallback方法中。
如果当前hystrix处于未熔断状态,则
- getExecutionSemaphore 判断当前策略是否为信号量还是线程池,显然默认是线程池,然后再调用tryAcquire时写死了为true。
调用executeCommandAndObserve。
先来看一下执行失败进入降级的逻辑,这里我们直接进入到 HystrixCommand#getFallbackObservable
这里的getFallback最终会回调我们自定的fallback方法。
回到executeCommandAndObserve,这个方法主要做了以下三件事情
- 定义不同的回调,doOnNext、doOnCompleted、onErrorResumeNext、doOnEach。
- 调用executeCommandWithSpecifiedIsolation。
若执行命令超时特性开启,调用 Observable.lift方法实现执行命令超时功能。
3.3 executeCommandWithSpecifiedIsolation
这个方法首先是根据当前不同的资源隔离策略执行不同的逻辑,THREAD、SEMAPHORE。
- 判断是否允许发送请求,这是基于断路器实现,如果断路器打开,则进行对应回调处理(失败或降级)。
- 如果 断路器 关闭,则进行请求,先获取信号,获取失败则处理对应回调。
- 获取成功,则由方法 executeCommandAndObserve 创建对应的 Observable 实现 线程隔离、请求发送 等操作,同时注册了对应的 生命周期回调。
3.4 getUserExecutionObservable
然后会执行 HystrixCommand#getExecutionObservable
这个 run() 方法在上面已经讲过了,就是真正的业务执行方法。
最终调用到我们自己的业务逻辑。
下面总结一下我们整体的业务逻辑图:
