hystrix 源码分析以及属性的配置
一.feign与hystix结合
1.1测试环境搭建
架构如图:
非常简单,就是Order服务通过feign调用product服务的一个获取商品信息的一个接口:
package com.yang.xiao.hui.order.controller; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Primary;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; @FeignClient(name ="product",fallback =ProductHystrix.class)
@Primary
public interface ProductService {
@RequestMapping("/info/{id}")
Product getProductInfo(@PathVariable("id") Integer id);
}
@Component
public class ProductHystrix implements ProductService{
@Override
public Product getProductInfo(Integer id) {
System.out.println("被熔断;了");
Product product = new Product();
product.setName("熔断了。。。。。"); return product;
}
}
我们在order服务引入feign的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
通过下面的依赖关系图可见,已经引入了hystrix相关依赖:
根据FeignClientsConfiguration这个配置类的一个方法feignHystrixBuilder(),可以知道,要让feign跟hystrix结合,需要在application.yml配置一个feign.hystrix.enabled =true的属性
1.2 debug调试feignClient客户端的创建:
.根据上次feign的调用源码分析,可以知道,带有@FeignClient的接口都会被封装成FeignClientFactoryBean类,通过该类的getObject()方法可以获取对应的代理对象,具体源码分析,参考https://www.cnblogs.com/yangxiaohui227/p/12965340.html
断点打在FeignClientFactoryBean的getObject()方法,然后启动Order服务:(因为我的order服务的Controller注入ProductSerivce,所以启动时,会调用FeignClientFactoryBean的getObject()获取实例)
@Controller
public class OrderContorller {
@Autowired
private ProductService productService; @ResponseBody
@RequestMapping("/info/{id}")
@MyLogAnnotation
public Product getProductInfo(@PathVariable Integer id){
return productService.getProductInfo(id);
}
}
上面先调用build,在调用newInstance来创建代理对象,先看build方法:
至此代理对象创建完毕,我们看到了方法处理器是:HystrixInvocationHandler
1.3 debug调试feign调用第三方接口:
在浏览器输入http://localhost:8674/info/3,调用order服务的getProductInfo方法:
debug跟踪进去:
可见,最终是进入了HystrixInvocationHandler类的invoke方法: 方法具体如下:
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
// early exit if the invoked method is from java.lang.Object
// code is the same as ReflectiveFeign.FeignInvocationHandler
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
//这里创建一个command命令对象,里面实现了2个方法,一个是执行我们目标方法的run()方法,一个是执行目标方法失败后,调用我们的服务降级方法getFallcack()
HystrixCommand<Object> hystrixCommand =
new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
try {
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
} catch (Exception e) {
throw e;
} catch (Throwable t) {
throw (Error) t;
}
} @Override
protected Object getFallback() {
if (fallbackFactory == null) {
return super.getFallback();
}
try {
Object fallback = fallbackFactory.create(getExecutionException());
Object result = fallbackMethodMap.get(method).invoke(fallback, args);
if (isReturnsHystrixCommand(method)) {
return ((HystrixCommand) result).execute();
} else if (isReturnsObservable(method)) {
// Create a cold Observable
return ((Observable) result).toBlocking().first();
} else if (isReturnsSingle(method)) {
// Create a cold Observable as a Single
return ((Single) result).toObservable().toBlocking().first();
} else if (isReturnsCompletable(method)) {
((Completable) result).await();
return null;
} else if (isReturnsCompletableFuture(method)) {
return ((Future) result).get();
} else {
return result;
}
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an interface
throw new AssertionError(e);
} catch (InvocationTargetException | ExecutionException e) {
// Exceptions on fallback are tossed by Hystrix
throw new AssertionError(e.getCause());
} catch (InterruptedException e) {
// Exceptions on fallback are tossed by Hystrix
Thread.currentThread().interrupt();
throw new AssertionError(e.getCause());
}
}
}; if (Util.isDefault(method)) {
return hystrixCommand.execute();
} else if (isReturnsHystrixCommand(method)) {
return hystrixCommand;
} else if (isReturnsObservable(method)) {
// Create a cold Observable
return hystrixCommand.toObservable();
} else if (isReturnsSingle(method)) {
// Create a cold Observable as a Single
return hystrixCommand.toObservable().toSingle();
} else if (isReturnsCompletable(method)) {
return hystrixCommand.toObservable().toCompletable();
} else if (isReturnsCompletableFuture(method)) {
return new ObservableCompletableFuture<>(hystrixCommand);
}
return hystrixCommand.execute(); //最终调用了命令行的execute方法
}
小结:这里创建一个command命令对象,里面实现了2个方法,一个是执行我们目标方法的run()方法,一个是执行目标方法失败后,调用我们的服务降级方法getFallcack(),直接调用了命令对象的execute方法:1.4 我们跟进命令对象的创建看看有哪些属性:
这里一大堆初始化属性,我们主要关注2个:HystrixCommandProperties.Setter 和 HystrixThreadPoolProperties.Setter,以及线程池的初始化
我们看到线程池的初始化,有个threadPoolkey,该值为服务的名称,也就是@FeignClient注解的name 值,初始化时,同时传入了一个线程池的默认配置:
那么我们先看HystrixThreadPoolProperties.Setter的属性有哪些值:
线程池属性初始化前缀是hystrix,通过上面我们可以知道,Hystrix有默认的线程参数,超过对应的线程参数就会被拒绝,那么我们就可以用该特征来做服务限流了,真对某个方法给其设置相应的最大队列属性;
直接我们查看HystrixCommandProperties的属性值:
命令创建分析完后,我们跟踪执行逻辑:hystrixCommand.execute();
这里用到了大量的RXJava相关知识,从上面代码可以知道,该方法是阻塞的:
跟进toObservable(),我们找到核心代码:
private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {
// mark that we're starting execution on the ExecutionHook
// if this hook throws an exception, then a fast-fail occurs with no fallback. No state is left inconsistent
executionHook.onStart(_cmd); /* determine if we're allowed to execute */
if (circuitBreaker.allowRequest()) { //这里判断是否允许执行目标方法,如果不允许,那就执行fallbcak方法
final TryableSemaphore executionSemaphore = getExecutionSemaphore();
final AtomicBoolean semaphoreHasBeenReleased = new AtomicBoolean(false);
final Action0 singleSemaphoreRelease = new Action0() {
@Override
public void call() {
if (semaphoreHasBeenReleased.compareAndSet(false, true)) {
executionSemaphore.release();
}
}
}; final Action1<Throwable> markExceptionThrown = new Action1<Throwable>() {
@Override
public void call(Throwable t) {
eventNotifier.markEvent(HystrixEventType.EXCEPTION_THROWN, commandKey);
}
}; if (executionSemaphore.tryAcquire()) {
try {
/* used to track userThreadExecutionTime */
executionResult = executionResult.setInvocationStartTime(System.currentTimeMillis());
return executeCommandAndObserve(_cmd)//这里是执行目标方法
.doOnError(markExceptionThrown)
.doOnTerminate(singleSemaphoreRelease)
.doOnUnsubscribe(singleSemaphoreRelease);
} catch (RuntimeException e) {
return Observable.error(e);
}
} else {
return handleSemaphoreRejectionViaFallback();//处理fallback方法
}
} else {
return handleShortCircuitViaFallback();//处理fallback方法
}
}
重点三个方法:circuitBreaker.allowRequest() /executeCommandAndObserve(_cmd) / handleSemaphoreRejectionViaFallback()
先看熔断器核心业务方法:circuitBreaker.allowRequest()
我们看看isOpen()方法的逻辑:
@Override
public boolean isOpen() {
if (circuitOpen.get()) { //如果已经是打开的,那就返回true
// if we're open we immediately return true and don't bother attempting to 'close' ourself as that is left to allowSingleTest and a subsequent successful test to close
return true;
} // we're closed, so let's see if errors have made us so we should trip the circuit open
HealthCounts health = metrics.getHealthCounts(); //获取统计对象,每个command对象的key值就会有一个统计对象,也就是每个请求方法对应一个对象 // check if we are past the statisticalWindowVolumeThreshold
if (health.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) { //在一个时间窗口内,默认10s,请求总次数要达到配置的最小请求数(默认20次)
// we are not past the minimum volume threshold for the statisticalWindow so we'll return false immediately and not calculate anything
return false;
} if (health.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) { /在一个时间窗口内,默认10s,请求失败率要达到最小配置百分比(默认50%)
return false;
} else {
// our failure rate is too high, trip the circuit //执行到这里说明失败率过高
if (circuitOpen.compareAndSet(false, true)) {
// if the previousValue was false then we want to set the currentTime //即使之前是关闭状态也要将其改为打开状态
circuitOpenedOrLastTestedTime.set(System.currentTimeMillis());//设置最近打开时间,用于半打开状态的设置
return true;
} else {
// How could previousValue be true? If another thread was going through this code at the same time a race-condition could have
// caused another thread to set it to true already even though we were in the process of doing the same
// In this case, we know the circuit is open, so let the other thread set the currentTime and report back that the circuit is open
return true;
}
}
} }
小结:isOpen()在默认情况下,如果10s内请求次数达到20次,失败率达到百分之50,则熔断器将会打开
接着我们分析半打开方法:allowSingleTest();
public boolean allowSingleTest() {
long timeCircuitOpenedOrWasLastTested = circuitOpenedOrLastTestedTime.get(); //上次最近打开熔断器的时间
// 1) if the circuit is open
// 2) and it's been longer than 'sleepWindow' since we opened the circuit
//熔断器如果是打开状态,并且最近打开时间距离当前时间已经超过了设置的时间(默认5s)
if (circuitOpen.get() && System.currentTimeMillis() > timeCircuitOpenedOrWasLastTested + properties.circuitBreakerSleepWindowInMilliseconds().get()) {
// We push the 'circuitOpenedTime' ahead by 'sleepWindow' since we have allowed one request to try.
// If it succeeds the circuit will be closed, otherwise another singleTest will be allowed at the end of the 'sleepWindow'.
if (circuitOpenedOrLastTestedTime.compareAndSet(timeCircuitOpenedOrWasLastTested, System.currentTimeMillis())) { //更新最近打开时间,方便下一个5s进行判断
// if this returns true that means we set the time so we'll return true to allow the singleTest
// if it returned false it means another thread raced us and allowed the singleTest before we did
return true;
}
}
return false;
}
一张图总结:
至此,熔断器核心逻辑分析完毕了,我们回到之前目标方法的逻辑,继续跟踪executeCommandAndObserve(_cmd);
前面我们分析过,创建Command对象时,实现了run方法,和getFallback()方法,上图可见,run方法被调用了,而run方法里面有我们的目标方法
接着我们分析执行目标方法失败后,是如何调用getFallback方法的: handleSemaphoreRejectionViaFallback():
由此可见,这里最终调用了Command对象的getFallback()方法;
执行失败的原因有:执行异常,超时,熔断器打开,线程数超过最大限制,请求异常
1.4 如何修改默认的参数配置:如,我想更改线程池的配置,或者滑动窗口时间,@FeignClient注解里面并没有更改hystrix的相关配置,那么我们回顾下ProductService接口的创建过程:
我们创建命令对象时:
由此可知,我们创建的HystrixCommand时,要用到一个属性配置对象,该对象其实就是创建代理对象时,由HystrixFeign提供,但该属性是直接new出来的,所以我们唯有替换掉HystrixFeign
我们发现,容器中没有HystrixFeign.Builder对象时就才会创建Feign.Builder,那么我们就给他注入一股Feign.Builder,在里面给它设置setterFactory属性:
package com.yang.xiao.hui.order.controller; import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import feign.Feign;
import feign.Target;
import feign.hystrix.HystrixFeign;
import feign.hystrix.SetterFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope; import java.lang.reflect.Method; @Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
public class FeignConfig { @Bean
@Scope("prototype")
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
HystrixFeign.Builder builder = HystrixFeign.builder();
SetterFactory setterFactory= new SetterFactory(){ @Override
public HystrixCommand.Setter create(Target<?> target, Method method) {
String groupKey = target.name();
String commandKey = Feign.configKey(target.type(), method);
return HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(100).withMaxQueueSize(200)); }
};
builder.setterFactory(setterFactory); return builder;
}
}
//需要更改任何属性就在HystrixCommand.Setter对象加,我们这里更改了线程池的核心线程数为100,最大队列数为200,启动order服务,重新调用方法:
已经变更了;
思考: 上面的配置是对所有的服务都起效的,如果我们需要对不同的服务,如库存服务,客户服务,等有不同的配置,如何处理:
方案一,在主启动类中,将上面的配置类的扫描排除,然后在@FeignClient注解中添加该配置,原理在feign源码里说过,在创建feign对象时,如果子容器不存在某个bean,会到父,容器中找;那么我们的思路是让父容器扫描不到:
此时再次测试:
方案二:在配置类那里根据不同服务返回不同设置,甚至可以根据不同方法返回不同设置,因为Feign.Builder对象是多例的:
package com.yang.xiao.hui.order.controller; import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import feign.Feign;
import feign.Target;
import feign.hystrix.HystrixFeign;
import feign.hystrix.SetterFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope; import java.lang.reflect.Method; @Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
public class FeignConfig { @Bean
@Scope("prototype")
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
HystrixFeign.Builder builder = HystrixFeign.builder();
SetterFactory setterFactory= new SetterFactory(){ @Override
public HystrixCommand.Setter create(Target<?> target, Method method) {
String groupKey = target.name();
String commandKey = Feign.configKey(target.type(), method);
HystrixThreadPoolProperties.Setter setter = HystrixThreadPoolProperties.Setter().withCoreSize(100).withMaxQueueSize(200);
if("product".equals(groupKey)){ //如果是商品服务就用下面配置
setter=HystrixThreadPoolProperties.Setter().withCoreSize(50).withMaxQueueSize(50);
}
return HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
.andThreadPoolPropertiesDefaults(setter); }
};
builder.setterFactory(setterFactory); return builder;
}
}
二.hystix用于controller层
前面分析的是feign与hystrix结合的源码,hystix还可以用于controller层:
2.1 添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.2 修改controller
2.3.主启动类添加@EnableHystrix或@EnableCircuitBreaker @EnableHystrix注解里面包含了@EnableCircuitBreaker所以他们功能一致
2.4 源码分析:
从EnableHystrix注解开始:
根据上面分析,springboot会加载类路径下,META-INF/spring.factories文件,找到key为org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker的类
由此可见org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration这个是我们要找的配置类
跟踪到此,我们看到了最终会注入一个切面类HystrixCommandAspect,我们根进看看
调用contorller方法,debug跟踪:
进入create方法:
继续跟进HystrixCommandFactory.getInstance().create(metaHolder);
由此可见,最终也是将一个方法封装成了一个Command对象,跟前面分析feign的调用逻辑基本一致了
至此,调用逻辑跟之前feign结合hystrix时已经一致了,因此不再重复分分析
既然controller可以使用hystrix,如果feign调用过程也使用了hystirx,那么我们可以通过在application.yml配置文件中设置feign.hystrix.enabled=false,保留controller层的hystrix逻辑
总结: Hystix 可以实现熔断,服务降级,限流三大功能;
1.熔断的理解:就是前面判断断路器是否开还是关,跟保险丝作用一样
2. 服务降级的理解: 就是服务熔断后,执行fallback方法,这就是服务降级了
3. 限流的理解:就是里面的线程池最大队列数
=
hystrix 源码分析以及属性的配置的更多相关文章
- Spring IOC 容器源码分析 - 填充属性到 bean 原始对象
1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的.我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反 ...
- 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入
使用react全家桶制作博客后台管理系统 前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...
- mybatis源码分析--如何加载配置及初始化
简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...
- 5.2 spring5源码--spring AOP源码分析二--切面的配置方式
目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...
- 【源码分析】FastJson全局配置日期格式导致@JSONField(format = "yyyy-MM-dd")注解失效
出现的问题 我全局配置的时间格式是:yyyy-MM-dd HH:mm:ss @JSONField注解配置的时间格式是:yyyy-MM-dd 最终的返回结果是:yyyy-MM-dd HH:mm:ss 问 ...
- Spring源码分析-从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(一)?
阅前提醒 全文较长,建议沉下心来慢慢阅读,最好是打开Idea,点开Spring源码,跟着下文一步一步阅读,更加便于理解.由于笔者水平优先,编写时间仓促,文中难免会出现一些错误或者不准确的地方,恳请各位 ...
- [dev][ipsec][dpdk] strongswan/dpdk源码分析之ipsec算法配置过程
1 简述 storngswan的配置里用一种固定格式的字符串设置了用于协商的预定义算法.在包协商过程中strongswan将字符串转换为固定的枚举值封在数据包里用于传输. 协商成功之后,这组被协商选中 ...
- dubbo源码分析7——dubbo的配置解析_与spring的整合
dubbo的配置其实就是建立在spring的命名空间的配置机制之上的.在dubbo的jar包的META-INF目录下会有spring.handlers这个文件,用来配置spring的命名空间和解析类的 ...
- STL源码分析之第二级配置器
前言 第一级是直接调用malloc分配空间, 调用free释放空间, 第二级三就是建立一个内存池, 小于128字节的申请都直接在内存池申请, 不直接调用malloc和free. 本节分析第二级空间配置 ...
随机推荐
- Manico--自定义应用快速切换
快速切换应用的app,使用非常频繁,奈何还是没有钱! 这玩意儿虽然免费,但是时不时跳一个弹框让你购买,也是够烦的,然后我们正好利用逆向工具,对着玩意儿进行破解,让它不再弹框! 下载安装Hopper D ...
- Jenkins匿名用户设置
最近自己安装配置jenkins,但是跑任务时,发现是匿名账户登录,可以在系统设置中点击如下: 2.勾选“启用安全”,“访问控制”>“安全域”选择“Jenkins专有用户数据库”,并勾选“允许用户 ...
- mysql建数据库的字符集与排序规则说明
本文转自https://blog.csdn.net/qq_38224812/article/details/80745868,感谢作者,自己留存已备他日使用 1.字符集说明: 一般选择utf8.下面介 ...
- Python开发的入门教程(七)-切片
介绍 本文主要介绍Python中切片的基本知识和使用 对list进行切片 取一个list的部分元素是非常常见的操作.比如,一个list如下: >>> L = ['Adam', 'Li ...
- Codeforces 1321D Navigation System
题意 有个人要从\(s\)走到\(t\),经过的路径给定.导航系统每次会显示当前节点到\(t\)的最短路,有多条就显示其中之一.这个人如果按照导航走,那么啥都没变.如果没有按导航走导航就会重新导航.问 ...
- vue问题整理
生命周期面试题 1.什么是 vue 生命周期 vue 实例从创建到销毁的过程就是生命周期. 也就是从开始创建.初始化数据.编译模板.挂在 dom -> 渲染.更新 -> 渲染.卸载等一系列 ...
- 阿里面试:dubbo的服务引用过程
点赞再看,养成习惯,微信搜一搜[三太子敖丙]关注这个喜欢写情怀的程序员. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的系 ...
- 转载:记录一次MySQL两千万数据的大表优化解决过程
地址:https://database.51cto.com/art/201902/592522.htm 虽然是广告文,但整体可读性尚可.
- JAVA实现汉字转拼音
两个工具包都可以实现:pinyin4j/JPinyin pinyin4j 第一个是使用pinyin4j的jar,此jar对多音字语句的处理不太理想 package edu.ws; import net ...
- hyperledger fabric 智能合约开发
开发步奏: 1.创建教育联盟 2.区块链服务平台自动生成通道id 3.区块链网络服务人员通过命令行在区块链网络中创建对应通道 4.创建相关教育组织 5.邀请相关组织加入联盟 6.区块链网络管理人员通过 ...