Spring Cloud之 Hystrix源码分析

首先感谢 诸葛老师分享

Hystrix源码流程

 

图1

首先调用findById方法 调用接口报错的话,会走降级的方法 findByIdFallback方法。如果commandKey是空默认是类名。还可以配置线程池参数,目的是线程间的隔离。熔断, 降级

图2

图3

图4

 图5

 

这个方法 META-INF/spring.factories 目录下找到 的EnableCircuitBreaker 为key 对应的类HystrixCircuitBreakerConfiguration,然后把他加入到spring容器中。

图6 

 图7

 图8

图9

这个类和最核心的流程没有关系。 

图10

 38 行HystrixCommandAspect ,看到这个注解我们想到的是HystrixCommand 注解和切面 图11;

图11

图12

当我们发现77行我们是不是清晰多了。

88行,拿我们切点要执行的方法  ;96行create()方法图12-0

当我们调用这个hystrixCommandAnnotationPointcut()方法时候,在这个方法的前后做横切。默认走的103行,这是一个命令模式执行我们的方法。

图12-0

最终会走到43行,创建了一个命令Command

图12-1

图12-2

图12-3

 

命令方式最重要的一个方法是run方法。 

图13

 默认走51行

 图14

把HystrixInvokable类型转换成HystrixExecutable类型。 

图15

 

 

图16

 

 

 图17

看来用的响应式编程。简单来说,运用了大量的观察者模式,并且比观察者模式还要复杂。 observalble被观察者,observer观察者,当被观察者发生变化会通知观察者。既然明白了观察者和被观察者,那我们的上面的call方法 就是,就是一个一个观察者,每一个观察都有对应的一个动作。当被观察者发生变化后,执行这个动作。

图18

 

   这个对象是一个被观察者。返回的是一个applyHystrixSemantics对象。图20 

 

 

最终上面的方法都没有调用,最终执行的是454行。这个是被观察者,这个返回的就是我们的观察者

 485行. applyHystrixSemantics 这个对象真正是什么413行。

当terminate这个事件发生的时候,触发这个terminateCommandCleanup这个观察者事件。当这个事件doOnUnsubscribe方法时候,会触发unsubscribeCommandCleanup这个方法。 现在被观察者只是注册,并没有 调用。 这个afterCache从哪里来的呢,506行。hystrixObservable 这个对象从485行过来的。

 图19

第一个观察者;

第二个观察者;

第三个观察者; 

 图20

 523行看我们的熔断器是否能开启,图21 ,如果是关闭走的是降级逻辑,

 如果打开走降级逻辑。图22;判断信号量如果够用的话执行这个逻辑。信号量不够的话就会降级。可以这样理解信号量是一个计数器,假如这个基数器是10,有一个命令来执行基数器加1,来10个命令来执行就是10,假如11个命令来了,信号量就是11了,这个时候判断这个时候信号量是不是超过了10,超过了直接拒绝,走降级逻辑。524行获取信号量。542行,如果没有配置信号量 542行,返回的是true. 

 又绑定了一组观察者和被观察者。

图19-0

首先判断配置文件里有没有配置信号量,线程隔离信号量的配置。如果没有配置就返回一个空的信号量。

图19-1

图19-2

 

最终看return方法。判断是否要执行超时逻辑。635行。636行执行我们的特定的隔离方法法。 

图19-3

获取

 649行从配置文件中获取线程的隔离的策略。

 680行 真正调用run方法。

 711行,线程真正隔离。只要我们能够找到线程池在什么地方初始化就能找到相关问题。 线程池配置图19-7 。

图19-4

图19-5

 重新调到run方法。

图19-6

 

图19-7

图21

从配置中获取s是否强制打开断路器, 如果为false会强制走我们的的短路逻辑 ,判断一下opend的值如果是-1也是没有打开的。

图19-8

线程池定义。

图19-9

图19-10

图19-11

注解的信息

线程池的大小都是放meaHolder中的。joninPoint切点的注解的信息,元注解的信息。

 

 metaHoder是什么呢?

 

 buiilder是什么

 builid什么

 调用this后。 commandPropertiesDefaults 通过配置获取线程池信息。那setter是什么往上找

 

 

 

 先从缓存中获取。 key 就是commandkey个方法对应一个线程池。 如果再两个方法上标注相同的commandkey那么这两个方法就会用共同的线程池。如果在两个方法中标注不同的commandkey 就会用不同的线程池。

 

图19-12

 图19-13

到这里最后 如果线程池超出,走拒绝策略,走降级逻辑。

 

 

图22

图23

 

 

 855行执行降级的逻辑。

图24

图25

 返回配置的fallback方法。使用反射调用的方法

图26

图27

 

图28

 

run方法就是我们正常执行方法。也是利用反射拿command命令,也就是方法名称去执行真是的方法。 

 

 

图29

 熔断器默认是关闭的。  如果失败调用的达到了阀值,比如调用10次,其中有5次以上失败了。熔断器熔断的阀值是50%。熔断器打开以后,所有的请求都没有办法访问了。直接降级了。熔断器还有一个半开的概念,熔断器有一个熔断器时间窗,默认熔断器的时间窗是10s,如果在熔断器刚刚打开到10s之间 不管来多少请求都是没有办法访问的。但是过了这个熔断器的时间窗口后,熔断器会变成半开状态,只要来了一个请求,容器会试探的让他执行一次,如果这次请求执行成功了,熔断器关闭;如果失败了,熔断器又被打开,又要经历一个熔断期间窗口。

图30

图31

图32

图33

 图34

断路器没有打开的情况:

如果熔断器打开走降级的逻辑。 

图35

图36

看一下调用这个方法的地方。

图37

在往上找到调用他的方法;

图38

 在往上找到调用他的方法;

图39

图40

是否配置了断路器,如果配置了走短路器逻辑。如果没有配置返回一个空的断路器。 

图41

circuitBreakersBycommand 的key是commandKey,也就是这个commandKey不单对应一个短路器,他还对应了一个线程池。换句话说就是一个方法可以对应一个断路器,一个断路器也可以对应多个方法类似上面讲的线程.101行,初始化短路器,

图42

图43

 

时间窗口我们可以简单的理解为一个时间段,在一个时间段,9点到9点10分这个时间段调用10次其中有5次调用失败那么熔断器就会打开。当我们9点10分调用一个接口的时候,我们往前推10分钟 就是9点,看看在这个10分钟内调用的错误率是不是50%都失败了,这个配置metrics.rollingStats.timeInMillisenconds(时间窗口总大小)。问题来了,如果9点11分调用,那么统计的时间段应该是9点01分到9点11分的时间段。开始时间往后错1分钟。也就是说这个时间窗口是滑动的,每过1分钟,就会向后滑动一分钟。

判断请求的总次数是不是小于circuitBreaker.requestVolumeThreshold,滑动窗口触发熔断的最小请求数;代码跳过不做任何处理。

判断如果错误率小于配置文件的错误率,代码跳过不做任何处理。

如果上面两种情况都不满足直接打开。打开的时间circuitOpened设置值。

metrics.rollingStats.numBuckets 滑动窗口的份数(如果没有配置默认是10):Hystrix会在一个滑动窗口的时间段内,假设是10s分成10分,每一份就是1秒。然后统计每1一份内调用的成功的次数,失败的次数,超时的次数,拒绝的次数。把这些值分别储存起来。最终统计的错误率,每一秒(失败的次数+超时的次数+拒绝的次数)  +++ 加10次,除以,10秒内调用次数的综合算出来的,错误率。这个错误率会和circuitBreaker.errorThresholdPercentage比较。  这种滑动窗口也可以用作接口限流。redis时间段的限流。从我这次调用过去的10秒流量的一个大小来限制;circuitBreaker.requestVolumeThreshold。滑动窗口触发熔断的最小请求数。如果滑动窗口时间内请求数只有19个,及时19个请求全部失败,也不会熔断,因为没有到达最小熔断数。如果到达20.有19个全部失败,超过了50% 就应该熔断了。为什么这样设计呢,这就是统计学的问题。数量太小不值当的统计呗。

图44。

 滑动窗口次数统计的类。HealthCounts类的数据从哪里来呢,图49

图45

 熔断器的状态。

图46

图47

判断是否强制打开或强制关闭断路器,circuitOpened 存短路器打开的时间。-1是断路器默认的值。如果是等于-1肯定是关闭的,直接返回true; 275行如果半开状态返回ture。 返回ture就会执行正常逻辑。返回false才会走降级逻辑。

图48

sleepWindowInMilliseconds 为熔断后多长时间放一些请求试探。默认是5秒。  如果 当前时间>熔断后打开的时间+熔断后多长时间后试探;

为半开状态。例如当前时间(16s)> 熔断器打开的时间是(10s)+ 熔断后多长时间试探(5s);275行改成半开状态。 

图49

每次调用的结果,无论是成功,失败,异常,熔断器是否打开都会上传到metrics。如果metirics 会把最终的结果放在HealthCounts类里。

图50

markOnCompleted 注册的事件。关注一下重点行596行。上报数据到 metirics图51

643行当我们执行完成时候需要调用doOnCompleted(markOnCompleted)方法。我们看下这个方法markOnCompleted注册的事件;

图51

 

方法正常执行关闭熔断器,熔断器打开的时间设置为-1。metrics 做一些上传工作。

图52

 最终把数据存到healthCounts中。清空数据。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值