01深度解析Spring Cloud Hystrix---入門搭建以及流程分析
總結
本章 主要搭建Hystrix 的簡單demo, 再從註解着手,大致梳理如何通過一個@EnableHystrix 和 @HystrixCommand就能生效的原理。
一.背景
1.1 介紹
Spring Cloud Hystrix 基於 Netflix Hystrix 實現,具備服務降級、服務熔斷、線程與信號隔離、請求緩存、請求合併以及服務監控等強大功能。
1.2 demo
先走一個簡單的例子,然後我們再繼續深入解析,我這裏 用的springBoot 的版本是
2.0.6.RELEASE,spring Cloud 的版本 是Finchley.RELEASE,這裏的 版本要匹配,可以自行搜索一下.
Pom 文件 配置:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
新建一個Application.class
在 啓動類上面開啓Hystrix(@EnableHystrix)@EnableHystrixDashboard 這個只是一個 數據面板 ,是查看接口的情況的,可配可不配
@SpringBootApplication
@EnableHystrix
@EnableHystrixDashboard
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
再建一個 TestController.class
在接口上面 加上 熔斷註解@HystrixCommand,並需要指定一個異常時調用的 方法名稱,這裏定義了一個 getDefaultMethod方法,入參和出參 要和原先的方法一致,不然會報錯。
@RestController
public class TestController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("user/{id}")
@HystrixCommand(fallbackMethod = "getDefaultMethod")
public String getClientById(@PathVariable Long id) throws InterruptedException {
Thread.sleep(2000);
String client = restTemplate.getForObject("http://localhost:2223/user/" + 100, String.class);
System.out.println(client);
return client;
}
public String getDefaultMethod(Long id) {
System.out.println("熔斷,默認回調函數");
return "this is hystrix call back function !";
}
}
啓動然後測試
我這裏的端口號是 2225,結果如下:
可以看到 代碼裏面去請求2223 端口號的服務器,然後走的 默認的接口,這樣就起到了一個 在 調用的服務 出現異常時,可以走默認接口,達到一個 服務降級的目的.
二. 分析
從上面的例子,先引入jar ,然後在啓動類上面加上註解@EnableHystrix 開啓Hystrix ,接着在 需要熔斷的方法上面加上@HystrixCommand 並指定對應的出現異常時運行的方法即可. 內部是如何運轉的呢,我們就從 @EnableHystrix 開始分析
2.1 @EnableHystrix
@EnableHystrix 註解裏面什麼都沒有,引入了@EnableCircuitBreaker ,所以在 啓動類上面 用 @EnableHystrix 或者@EnableCircuitBreaker 是一個效果
我們繼續看一下 @EnableCircuitBreaker,裏面也沒有啥,引入了 EnableCircuitBreakerImportSelector.class,此類繼承了SpringFactoryImportSelector.class,這個類從名字 就能看出來 實現了 接口 ImportSelector //TODO, 作用 應該是手動注入特定的Bean ,直接看 selectImports 的具體邏輯
@Override
public String[] selectImports(AnnotationMetadata metadata) {
//判斷是否開啓Hystrix
if (!isEnabled()) {
return new String[0];
}
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(this.annotationClass.getName(), true));
Assert.notNull(attributes, "No " + getSimpleName() + " attributes found. Is "
+ metadata.getClassName() + " annotated with @" + getSimpleName() + "?");
// 獲取所有的 可能的配置,過濾掉重複的
List<String> factories = new ArrayList<>(new LinkedHashSet<>(SpringFactoriesLoader
.loadFactoryNames(this.annotationClass, this.beanClassLoader)));
// 沒有或者超過一個 都會報錯
if (factories.isEmpty() && !hasDefaultFactory()) {
throw new IllegalStateException("Annotation @" + getSimpleName()
+ " found, but there are no implementations. Did you forget to include a starter?");
}
if (factories.size() > 1) {
log.warn("More than one implementation " + "of @" + getSimpleName()
+ " (now relying on @Conditionals to pick one): " + factories);
}
return factories.toArray(new String[factories.size()]);
}
大致的流程如下:
- 判斷是否開啓Hystrix,如果沒有開啓 直接返回
- 獲取到所有的 可能的配置,SpringFactoryImportSelector 類 實現了 BeanClassLoaderAware,獲取類加載器, 這裏用到了 類似 SPI的技術,是SpringBoot 從將 需要注入的Bean 放在 "META-INF/spring.factories"文件裏面,這樣使用時就不必再進行注入了. 加載註解 @EnableCircuitBreaker具體需要注入的配置實現類,爲:org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration
- 接下來就是 注入 HystrixCircuitBreakerConfiguration 這個Bean了
2.2 HystrixCircuitBreakerConfiguration
@Configuration
public class HystrixCircuitBreakerConfiguration {
// 注入了一個攔截器
@Bean
public HystrixCommandAspect hystrixCommandAspect() {
return new HystrixCommandAspect();
}
// 注入了Hystrix 關閉的Hook
@Bean
public HystrixShutdownHook hystrixShutdownHook() {
return new HystrixShutdownHook();
}
@Bean
public HasFeatures hystrixFeature() {
return HasFeatures.namedFeatures(new NamedFeature("Hystrix", HystrixCommandAspect.class));
}
private class HystrixShutdownHook implements DisposableBean {
@Override
public void destroy() throws Exception {
// Just call Hystrix to reset thread pool etc.
Hystrix.reset();
}
}
}
//Hystrix 類的 reset 方法, 將線程池關閉,將一些統計數據重置
public static void reset() {
// shutdown thread-pools
HystrixThreadPool.Factory.shutdown();
_reset();
}
從這個Bean 裏面可以看出 ,主要是 注入了一個攔截器HystrixCommandAspect ,注入了一個HystrixShutdownHook 用於關閉線程池以及重置數據,重點就在hystrixCommandAspect 裏面了
2.2 hystrixCommandAspect
首先看一下 hystrixCommandAspect 裏面的大致內容:
@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();
}
@Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)")
public void hystrixCommandAnnotationPointcut() {
}
@Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)")
public void hystrixCollapserAnnotationPointcut() {
}
@Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
...
}
...
}
這裏就是入口的核心所在了, 從代碼可以看出 對 @HystrixCommand 和 @HystrixCollapser 進行了 @Around的攔截 , 這樣便實現了 只要在方法上面 加上一個 @HystrixCommand 註解,便可以實現服務的熔斷或者降級操作.具體的 實現邏輯請查看下一章.