Hystrix源码浅析

前言
最近在项目中需要使用Hystrix的request cache来提升服务的稳定性与高性能,其对于单个request请求,能够对指定方法methodA的返回值进行缓存,在多次调用methodA方法时仅会执行方法体一次,从而降低本次请求的复杂度。如果在methodA中存在对下游服务的依赖,则同步能够提高下游服务的稳定性,一定程度降低其并发性要求。Cache中的数据会存在更新,Hystrix同步提供了clear cache的对应实现。本章将从源码的角度来剖析下Hystrix是如何实现降级、缓存操作的。


本章概要
1、Hystrix是如何实现fallback的?
2、Hystrix是如何实现request cache的存储与获取?
3、Hystrix是如何实现request cache的清除?

Hystrix是如何实现fallback的?
在实际应用过程通过 @HystrixCommand注解能够更加简单快速的实现Hystrix的应用,那么我们就直接从 @HystrixCommand注解入手,其中包含了诸多参数配置,如执行隔离策略,线程池定义等,这些参数就不一一说明了,我们来看看其是如何实现服务降级的,如下图

其定义了 fallbackMethod方法名,正如其名,其提供了一个定义回退方法映射,在异常触发时此方法名对应的method将被触发执行,从而实现服务的降级。那么 @HystrixCommand注解又是如何被执行的呢,我们找到 HystrixCommandAspect.java,其切点定义如下
Hystrix源码浅析_第1张图片
可以看到被 @HystrixCommand注解的方法将会执行切面处理。通过Hystrix的类关系图先来了解下其定义了哪些类来实现我们的 HystrixCommand
Hystrix源码浅析_第2张图片
HystrixCommandAspectmethodsAnnotatedWithHystrixCommand方法中我们可以看到如下
Hystrix源码浅析_第3张图片
以上通过红色框标记了3个重点。
首先来看第一个,通过类关系图已经可以了解到,其定义了后续真正执行 HystrixCommandGenericCommand实例,其定义如下
Hystrix源码浅析_第4张图片
这里有两个重点,一个是 metaHolder包含了当前被注解方法的所有相关有效信息(定义在 HystrixCommandAspect.java中),如下可以看到红色框中已经标记了fallback回退方法的设定:
Hystrix源码浅析_第5张图片
另一个是其构造参数中的 HystrixCommandBuilder,其定义来源于 HystrixCommandBuilderFactory,如下主要参数设定均通过 metaHolder来实现:
Hystrix源码浅析_第6张图片
此处我们仍然需要关注红色框中的标记信息,在创建 HystrixCommandBuilder实例过程中会设定当前执行方法体是 CacheResult还是 CacheRemove操作,同时设定需要被忽略的异常以及回退方法指令。简单了解下 CacheInvocationContext的设定过程,如下
Hystrix源码浅析_第7张图片
后续在cache处理的部分会再次提到 CacheInvocationContext,这里先伏笔下。
再来看第二个红色框中的标记,根据名称即可判断,其主要是作为后续执行体类型的一个判断条件,在定义切点时已经可以看到当前切面不仅可以处理 HystrixCommand还能够处理 HystrixCollapser(HystrixCollapser是做请求合并的,不在本章的分析范围内)。
跟着代码继续往下,到了第三个红色框,在进入执行体前,其有一个判断条件,判断其是否是一个Observable模式(在Hystrix中,其实现大量依赖RXJAVA,会无处不在的看到Observable,其是一种观察者模式的实现,具体可以到RxJava项目官方做更多了解)。那么我们就来看看 CommandExecutor中是如何执行,方法体如下
Hystrix源码浅析_第8张图片
其根据 executionType决定了最终的执行方式,如下
  • execute():同步执行,从依赖的服务返回一个单一的结果对象,或是在发生错误的时候抛出异常;
  • queue():异步执行,直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果对象;
  • observe():返回Observable对象,它代表了操作的多个结果,他是一个Hot Observable;
  • toObservable():同样返回Observable对象,也代表了操作的多个结果,但其返回的是一个Cold Observable。
这里我们通过 execute()同步执行的方式继续跟踪,在原理上各方法差异不大,其均是由 toObservable()扩展后实现的, execute()方法的实现是执行 queue()后获取返回的 Future对象,通过get()方法阻塞然后获取对应的返回值实现同步,在 HystrixCommand中定义如下:
Hystrix源码浅析_第9张图片
此时我们需要特别关注注释中的一段话,其提示了我们,一旦方法执行失败,则会通过 getFallback()方法来执行一个回退操作。 直接进入 GenericCommand类中,可以找到最终的实现
Hystrix源码浅析_第10张图片
如上,其呼应了上面构造 HystrixCommandBuilder是的 CommandAction指令。可能我们会有亦或,又是如何触发 getFallback()run()方法的,具体的方法调用线路可以从 AbstractCommandtoObservable()方法开始找,这里就不展开了。通过下图可以很容易理解为何我们上面只需要分析 execute()即可,而方法的调用线程从 toObservable()方法开始找:
Hystrix源码浅析_第11张图片



Hystrix是如何实现request cache的存储与获取?
上面已经介绍了Hystrix是如何实现 fallback的,同时也在构造 HystrixCommandBuilder时已经提及了其对后续的cache将会起到一个关键作用。本小节将重点来分析Hystrix是如何实现Request cache的获取和存储的。

首先来看看 HystrixRequestCache的定义

其通过 ConcurrentHashMap来保证了cache的线程安全,其key值通过 prefix前缀+hystrix并发策略+cachekey组合
上面已经提及,调用的线路从 AbstractCommand中的 toObservable()方法开始,如下我们先来看看 AbstractCommand构造函数的定义
Hystrix源码浅析_第12张图片
通过红色框中的标记已经可以看到其通过 HystrixPlugins获取 concurrencyStrategy并发策略,从而实例化了 HystrixRequestCache实例requestCache。
找到方法链的入口,在 toObservable()方法中如下首先尝试从cache中获取value
Hystrix源码浅析_第13张图片
继续跟进至 HystrixRequestCache的get方法获取cache对应的value,我们先关注下其获取过程与当前的并发策略有关
Hystrix源码浅析_第14张图片
获取返回值后会进行如下判断:
  • fromCache如果不为空,则直接返回cache中缓存的value值;
  • fromCache为空,则如果启用了requestCache并且设定了cacheKey则会进行cache存储
但首次调用其值必然为null,那么我们来看看是如何实现cache的存储的
Hystrix源码浅析_第15张图片
通过 putIfAbsent实现cache的存储
Hystrix源码浅析_第16张图片
cache中存储值为 HystrixCachedObservable实例,通过already能够判断是否已经有其他线程保存cache至,如果已经存储则为直接使用

小节:本小节即完成了cache的存储与cache的获取使用分析。


Hystrix是如何实现request cache的清除?
既然有存储和获取使用,那么必然有清除动作,本小节来看看,Hystrix又是如何对request cache实现清除的呢?有两种方案:
1、通过@HystrixCommand+@CacheRemove注解组合实现;
2、单用@CacheRemove注解实现;

通过@HystrixCommand+@CacheRemove注解组合实现
既然注解中有 @HystrixCommand,根据上面的源码分析,代码的主体实现仍然是基于 HystrixCommand作为线路的,我们直接找到 AbstractHystrixCommand类,如下 process方法在每次的调用链路中均会被执行,
Hystrix源码浅析_第17张图片
可以看到其中明确调用了 flushCache方法来实现cache的清除动作,其定义如下
Hystrix源码浅析_第18张图片
其根据 cacheRemoveInvocationContext判断是否需要清除cache,而 cacheRemoveInvocationContext值在 HystrixCommandBuilderFactory中通过 metaHolder已经设定完成,具体在第一小节也有相关说明,
往下继续找到 HystrixRequestCacheManager中的clearCache方法,
Hystrix源码浅析_第19张图片
这里我们需要特别注意,在clear过程中, HystrixRequestCache实例采用了默认的并发策略,与我们存储时采用的并发可能不一致,在实际开发过程中默认的并发策略是无法满足需求的,此时我们执行下面的remove动作是无法clear成功的:
Hystrix源码浅析_第20张图片

单用@CacheRemove注解实现
Hystrix对于没有 @HystrixCommand注解的方法同样能够实现cache的清除功能,仅仅需要 @CacheRemove一个注解即可。在源码中我们找到 HystrixCacheAspect切面类,其作用就是清除request cache,下面来看下源码中的定义吧。

首先来看其切点定义

可以看到其切点定义明确说明了被 @CacheRemove且不被 @HystrixCommand注解方生效,再来看看其核心定义
Hystrix源码浅析_第21张图片
这里的核心代码已经被红色框标出,其仍然是通过调用 HystrixRequestCacheManager中的 clearCache方法,此处与第一种方法最大的差异在于 CacheInvocationContext来源差异,这里可以看到,在调用 clearCache之前通过切点方法信息,构建了与 HystrixCommandAspect中类似的MetaHolder实例,从而构建 CacheInvocationContext实例。
这里有一个特别需要注意的,springcloud默认并没有帮我们注册 HystrixCacheAspect实例,如果需要启用该切面,则需要注册 HystrixCacheAspect对应的bean即可。

小节,结合源码的分析,在最后一步竟然可能会造成无法clear成功,有点失望,目前已经在Github中提了issue( https://github.com/Netflix/Hystrix/issues/1802),其实现如下扩展部分内容。


扩展
为了避免clearCache失败导致功能开发阻塞,故直接对其做了如下调整即可正常使用,后续等待官方确认回复:
Hystrix源码浅析_第22张图片
其调整在 HystrixRequestCacheManagerclearCache方法中展开,仅仅是通过 HystrixPlugins来获取当前的并发策略而已。


你可能感兴趣的:(springcloud,hystrix)