Hystrix 熔断器属于⼀种容错机制
微服务中的雪崩效应
什么是微服务中的雪崩效应呢?
微服务中,⼀个请求可能需要多个微服务接⼝才能实现,会形成复杂的调⽤链路。
扇⼊:代表着该微服务被调⽤的次数,扇⼊⼤,说明该模块复⽤性好
扇出:该微服务调⽤其他微服务的个数,扇出⼤,说明业务逻辑复杂
扇⼊⼤是⼀个好事,扇出⼤不⼀定是好事
在微服务架构中,⼀个应⽤可能会有多个微服务组成,微服务之间的数据交互通过 RPC 或者 HTTP 远程调⽤,一般调用链路上都会设置调用超时、失败重试等机制来确保服务的成功执行,看上去很美,如果不考虑服务的熔断和限流,就是雪崩的源头。
假设微服务A调⽤微服务B,微服务B调用微服务C,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调⽤响应时间过⻓或者不可⽤,对微服务A的调⽤就会占⽤越来越多的系统资源,进⽽引起系统崩溃,所谓的“雪崩效应”。
如图中所示,最下游 微服务C 响应时间过⻓,⼤量请求阻塞,⼤量线程不会释放,会导致服务器资源耗尽,最终导致上游服务甚⾄整个系统瘫痪。
雪崩效应解决方案
从可⽤性可靠性着想,为防⽌系统的整体缓慢甚⾄崩溃,采⽤的技术⼿段;
通常有下面三种技术⼿段应对微服务中的雪崩效应,这三种⼿段都是从系统可⽤性、可靠性⻆度出发,尽量防⽌系统整体缓慢甚⾄瘫痪。
服务熔断
熔断机制是应对雪崩效应的⼀种微服务链路保护机制。我们在各种场景下都会接触到熔断这两个字。⾼压电路中,如果某局部位置的电压过⾼,熔断器就会熔断,对电路进⾏保护。同样,在微服务架构中,熔断机制也是起着类似的作⽤。当扇出链路的某个微服务不可⽤或者响应时间太⻓时,熔断对该节点微服务的调⽤,进⾏服务的降级,快速返回错误的响应信息。当检测到该节点微服务调⽤响应正常后,恢复调⽤链路。
注意: 服务熔断重点在“断”,
切断对下游服务的调用
服务熔断和服务降级往往是⼀起使⽤的,Hystrix 就是这样
服务降级
通俗讲就是整体资源不够⽤了,先将⼀些不影响主业务的流程服务停掉(调⽤我的时候,给你返回⼀个预留的值,也叫做兜底数据),待调用⾼峰过去,再把那些服务打开。
服务降级⼀般是从整体考虑,就是当某个服务熔断之后,服务器将不再被调⽤,此刻客户端可以⾃⼰准备⼀个本地的 fallback 回调,返回⼀个缺省值,这样做,虽然服务⽔平下降,但好⽍可⽤,比直接挂掉要强。
服务限流
服务降级是当服务出问题或者影响到核⼼流程的性能时,暂时将服务屏蔽掉,待⾼峰或者问题解决后再打开;但是有些场景并不能⽤服务降级来解决,⽐如秒杀业务这样的核⼼功能,这个时候可以结合服务限流来限制这些场景的并发/请求量
限流措施也很多,比如
- 限制总并发数(⽐如数据库连接池、线程池)
- 限制瞬时并发数(如nginx限制瞬时并发连接数)
- 限制时间窗⼝内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率)
- 限制远程接⼝调⽤速率、限制MQ的消费速率等
Hystrix 简介
[来⾃官⽹] Hystrix(豪猪 >刺),宣⾔“defend your app”是由Netflix开源的⼀个延迟和容错库,⽤于隔离访问远程系统、服务或者第三⽅库,防⽌级联失败,从⽽提升系统的可⽤性与容错性。
Hystrix主要通过以下⼏点实现延迟和容错。
包裹请求:使⽤ HystrixCommand 包裹对依赖的调⽤逻辑。微服务A(@HystrixCommand 添加Hystrix控制) --> 微服务B,其他调用链路同理。
跳闸机制:当某服务的错误率超过⼀定的阈值时,Hystrix可以跳闸,停⽌请求该服务⼀段时间。 资源隔离:Hystrix
为每个依赖都维护了⼀个⼩型的线程池(舱壁模式)(或者信号量)。如果该线程池已满,发往该依赖的请求就被⽴即拒绝,⽽不是排队等待,从⽽加速失败判定。
监控:Hystrix可以近乎实时地监控运⾏指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执⾏回退逻辑。回退逻辑由开发⼈员⾃⾏提供,例如返回⼀个缺省值。
⾃我修复:断路器打开⼀段时间后,会⾃动进⼊“半开”状态。
Hystrix熔断应用
⽬的:微服务C⻓时间没有响应,服务消费者—>微服务B快速失败给⽤户提示
- 服务消费者⼯程中引⼊ Hystrix 依赖坐标(也可以添加在⽗⼯程中)
<!--熔断器Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 服务消费者⼯程的启动类中添加熔断器开启注解 @EnableCircuitBreaker
1)服务提供者处理超时,熔断,返回错误信息
2)有可能服务提供者出现异常直接抛出异常信息>
以上信息,都会返回到消费者这里,很多时候消费者服务不希望把收到异常/错误信息再抛到它的上游去
用户微服务 — 注册微服务 — 优惠券微服务
1、登记注册
2、分发优惠券(并不是核心步骤),这里如果调用优惠券微服务返回了异常信息或者是熔断后的错误信息,这些信息如果抛给用户很不友好,此时,我们可以返回一个兜底数据,预设的默认值(服务降级)
- 定义服务降级处理⽅法,并在业务⽅法上使⽤@HystrixCommand的fallbackMethod属性关联到服务降级处理⽅法
/**
* 提供者模拟处理超时,调用方法添加 Hystrix 控制
* 使用 @HystrixCommand 注解进行熔断控制
* @param userId
* @return
*/
@GetMapping("/hystrixCommandTestFallback/{id}")
@HystrixCommand(
// Hystrix 舱壁模式(线程池隔离策略)
// 线程池标识,要保持唯一,不唯一的话就共用了
threadPoolKey = "hystrixCommandTestTimeout",
// 线程池细节属性配置
threadPoolProperties = {
@HystrixProperty(name="coreSize", value = "1"), // 线程数
@HystrixProperty(name="maxQueueSize", value="20") // 等待队列长度
},
// commandProperties熔断的一些细节属性配置
commandProperties = {
// 每一个属性都是一个 HystrixProperty
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="2000") ,
// hystrix高级配置,定制工作过程细节
// 统计时间窗口定义
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "8000"),
// 统计时间窗口内的最小请求数
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "2"),
// 统计时间窗口内的错误数量百分比阈值
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 自我修复时的活动窗口长度
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "3000")
},
fallbackMethod = "myFallBack" // 回退方法
)
public Integer hystrixCommandTestTimeout(@PathVariable Long id) {
// 使用 ribbon 不需要我们自己获取服务实例然后选择一个那么去访问了(自己的负载均衡)
String url = "http://hystrixCommandTest/test/" + id; // 指定服务名
Integer forObject = restTemplate.getForObject(url, Integer.class);
return forObject;
}
/*
* 定义回退方法,返回预设默认值
* 注意:该方法形参和返回值与原始方法保持一致
*/
public Integer myFallBack(Long userId) {
return -999999; // 兜底数据
}
注意
降级(兜底)⽅法必须和被降级⽅法相同的⽅法签名(相同参数列表、相同返回值)
可以在类上使⽤ @DefaultProperties 注解统⼀指定整个类中共⽤的降级(兜底)⽅法
Hystrix 舱壁模式(线程池隔离策略)
默认 Hystrix 内部维护了一个线程池(默认配置10线程),如果不进⾏其他设置,所有熔断⽅法都使⽤这个线程池,在高并发场景下同一时刻的A请求超过了10个,B请求将没有 Hystrix 线程连接可用,其他请求都没法去访问,因为 Hystrix 中没有线程可⽤,并不是B服务不可⽤。
为了避免上面的问题,局部服务请求过多导致其他正常服务⽆法访问,Hystrix 不是采⽤增加线程数,而是单独的为每⼀个控制⽅法创建⼀个线程池的⽅式,这种模式叫做“舱壁模式",也是线程隔离的⼿段。
我们可以使用一些手段查看线程情况
发起请求,可以使用 PostMan 模拟批量请求
Hystrix 舱壁模式程序修改
通过jstack命令查看线程情况,和我们程序设置相符合
Hystrix工作流程与高级应用
Hystrix工作流程
- 当调⽤出现问题时,开启⼀个时间窗(10s)
- 在这个时间窗内,统计调⽤次数是否达到最⼩请求数? 如果没有达到,则重置统计信息,回到第1步 如果达到了,则统计失败的请求数占所有请求数的百分⽐,是否达到阈值? 如果达到,则跳闸(不再请求对应服务)
如果没有达到,则重置统计信息,回到第1步- 如果跳闸,则会开启⼀个活动窗⼝(默认5s),每隔5s,Hystrix会让⼀个请求通过,到达那个问题服务,看是否调⽤成功,如果成功,重置断路器回到第1步,如果失败,回到第3步
/**
* 8秒钟内,请求次数达到2个,并且失败率在 50% 以上,就跳闸
* 跳闸后活动窗⼝设置为3s
*/ @HystrixCommand(
commandProperties = {
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
@HystrixProperty(name ="circuitBreaker.sleepWindowInMilliseconds",value = "3000")
}
我们上述通过注解进⾏的配置也可以配置在配置⽂件中,也可以在类上使用 @defaultPropreties 注解统一指定整个类公用的降级方法
# 配置熔断策略:
hystrix:
command:
default:
circuitBreaker:
# 强制打开熔断器,如果该属性设置为true,强制断路器进⼊打开状态,将会拒绝所有的请求。 默认false关闭的
forceOpen: false
# 触发熔断错误⽐例阈值,默认值50%
errorThresholdPercentage: 50
# 熔断后休眠时⻓,默认值5秒
sleepWindowInMilliseconds: 3000
# 熔断触发最⼩请求次数,默认值是20
requestVolumeThreshold: 2
execution:
isolation:
thread:
# 熔断超时设置,默认为1秒
timeoutInMilliseconds: 2000
基于 springboot 的健康检查观察跳闸状态
# springboot 中暴露健康检查等断点接口
management:
endpoints:
web:
exposure:
include: "*"
# 暴露健康接口的细节
endpoint:
health:
show-details: always
访问健康检查接⼝:http://localhost:8090/actuator/health
hystrix 正常⼯作状态
跳闸状态
活动窗⼝内⾃我修复
Hystrix Dashboard 断路监控仪表盘
正常状态是 UP,跳闸是⼀种状态 CIRCUIT_OPEN,可以通过 /health 查看,前提是⼯程中需要引⼊SpringBoot的actuator(健康监控),它提供了很多监控所需的接⼝,可以对应⽤系统进⾏配置查看、相关功能统计等。
统⼀添加依赖到⽗⼯程中
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
如果我们想看到 Hystrix 相关数据,⽐如 有多少请求、多少成功、多少失败、多少降级等,那么引⼊ SpringBoot 健康监控之后,访问 /actuator/hystrix.stream 接⼝可以获取到监控的⽂字信息,但是不直观,所以 Hystrix 官⽅还提供了基于图形化的 DashBoard(仪表板)监控平台。Hystrix 仪表板可以显示每个断路器(被 @HystrixCommand 注解的⽅法)的状态。
1)新建⼀个监控服务⼯程,导⼊依赖
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--hystrix 仪表盘-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2)启动类添加 @EnableHystrixDashboard 激活仪表盘
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrixDashboard // 开启 hystrix dashboard
public class HystrixDashboard9000 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboard9000.class,args);
}
}
3) application.yml
server:
port: 9000
Spring:
application:
name: cloud-hystrix-dashboard
eureka:
client:
serviceUrl: # eureka server的路径
# 把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表
defaultZone: http://cloudeurekaservera:8761/eureka/
instance:
# 使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
prefer-ip-address: true
# 自定义实例显示格式,加上版本号,便于多版本管理,注意是 ip-address,早期版本是 ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
4)在被监测的微服务中注册监控 servlet
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean;
}
被监控微服务发布之后,可以直接访问监控servlet,但是得到的数据并不直观,后期可以结合仪表盘更友好的展示
监控servlet url:http://localhost:8090/actuator/hystrix.stream
访问测试:http://localhost:9000/hystrix
输⼊监控的微服务端点地址,展示监控的详细数据,⽐如监控服务消费者 http://localhost:8090/actuator/hystrix.stream
百分⽐:10s内错误请求百分⽐
实⼼圆:
- ⼤⼩:代表请求流量的⼤⼩,流量越⼤球越⼤
- 颜⾊:代表请求处理的健康状态,从绿⾊到红⾊递减,绿⾊代表健康,红⾊就代表很不健康
曲线波动图:记录了2分钟内该⽅法上流量的变化波动图,判断流量上升或者下降的趋势
Hystrix Turbine 聚合监控
之前,我们针对的是⼀个微服务实例的 Hystrix 数据查询分析,在微服务架构下,⼀个微服务的实例往往是多个(集群化)
假如某一个微服务A 有三个实例
实例1(hystrix) ip1:port1/actuator/hystrix.stream
实例2(hystrix) ip2:port2/actuator/hystrix.stream
实例3(hystrix) ip3:port3/actuator/hystrix.stream
按照已有的⽅法,我们就可以结合 dashboard 仪表盘每次输⼊⼀个监控数据流 url,进去查看。⼿⼯操作能否被⾃动功能替代?
Hystrix Turbine聚合(聚合各个实例上的 hystrix 监控数据)监控
Turbine(涡轮)
思考:微服务架构下,⼀个微服务往往部署多个实例,如果每次只能查看单个实例的监控,就需要经常切换很不⽅便,在这样的场景下,我们可以使⽤ Hystrix Turbine 进⾏聚合监控,它可以把相关微服务的监控数据聚合在⼀起,便于查看。
Turbine 服务搭建
1)新建项⽬ cloud-hystrix-turbine-9001,引⼊依赖坐标
<!--hystrix turbine聚合监控-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<!--
引入 eureka 客户端的两个原因
1、微服务架构下的服务都尽量注册到服务中心去,便于统一管理
2、后续在当前 turbine 项目中我们需要配置 turbine 聚合的服务。
比如,我们希望聚合 micro-service-A 这个服务的各个实例的 hystrix 数据流,
那随后我们就需要在 application.yml 文件中配置这个服务名,
那么 turbine 获取服务下具体实例的数据流的时候需要ip和端口等实例信息,
也是从eureka服务注册中心获取的
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2)将需要进⾏ Hystrix 监控的多个微服务配置起来,在⼯程 application.yml 中开启 Turbine 及进⾏相关配置
# turbine 配置
turbine:
# appCofing配置需要聚合的服务名称,比如这里聚合 micro-service-A,micro-service-B 微服务的 hystrix 监控数据
# 如果要聚合多个微服务的监控数据,那么可以使用英文逗号拼接,比如 a,b,c
appConfig: micro-service-A,micro-service-B
clusterNameExpression: "'default'" # 集群默认名称
3)在当前项⽬启动类上添加注解 @EnableTurbine,开启仪表盘以及 Turbine 聚合
@SpringBootApplication
@EnableDiscoveryClient
@EnableTurbine // 开启Turbine聚合功能
public class HystrixTurbineApplication9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixTurbineApplication9001.class,args);
}
}
浏览器访问 Turbine 项⽬,http://localhost:9001/turbine.stream,就可以看到监控数据了
我们通过 dashboard 的⻚⾯查看数据更直观,把刚才的地址输⼊ dashboard 地址栏
监控⻚⾯
Hystrix 核心源码剖析
涉及的技术栈背景:springboot 装配、⾯向切⾯编程、RxJava 响应式编程的知识等等,剖析主体脉络。
1)分析入口:@EnableCircuitBreaker 注解激活了熔断功能,那么该注解就是 Hystrix 源码追踪的入口
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 这里导入了一个 Selector
@Import(EnableCircuitBreakerImportSelector.class)
public @interface EnableCircuitBreaker {
}
2)查看 EnableCircuitBreakerImportSelector 类
@Order(Ordered.LOWEST_PRECEDENCE - 100)
// 这里需要关注父类 SpringFactoryImportSelector,传入的泛型类型是 EnableCircuitBreaker
public class EnableCircuitBreakerImportSelector extends SpringFactoryImportSelector<EnableCircuitBreaker> {
@Override
protected boolean isEnabled() {
// 获取断路器开关配置
return getEnvironment().getProperty("spring.cloud.circuit.breaker.enabled", Boolean.class, Boolean.TRUE);
}
}
3)继续关注父类 SpringFactoryImportSelector
- annotationClass:获取到的子类传递到父类的泛型,就是 EnableCircuitBreaker 注解类
- selectImports 的最终目的是要根据传入的泛型全限定类名作为 key 去 spring.factories文件查找对应的配置类,并注入到容器,这里不是直接装配 spring.factories 整个文件内容
protected SpringFactoryImportSelector() {
// annotationClass:获取到的子类传递到父类的泛型,就是 EnableCircuitBreaker 注解类
this.annotationClass = (Class<T>) GenericTypeResolver
.resolveTypeArgument(this.getClass(), SpringFactoryImportSelector.class);
}
/**
* selectImports 的最终目的是要根据传入的泛型全限定类名作为 key 去 spring.factories 文件查找对应的配置类,并注入到容器
* 这里不是直接装配 spring.factories 整个文件内容
*/
@Override
public String[] selectImports(AnnotationMetadata metadata) {
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() + "?");
// Find all possible auto configuration classes, filtering duplicates
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) {
// there should only ever be one DiscoveryClient, but there might be more than
// one factory
log.warn("More than one implementation " + "of @" + getSimpleName()
+ " (now relying on @Conditionals to pick one): " + factories);
}
return factories.toArray(new String[factories.size()]);
}
spring.factories⽂件内容如下
会注⼊ org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration
在这个配置类中 装配了 HystrixCommandAspect,这是 HystrixCommand 的切面类
// 注入了切面 HystrixCommandAspect
@Bean
public HystrixCommandAspect hystrixCommandAspect() {
return new HystrixCommandAspect();
}
4)关注切⾯:com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect
重点分析环绕通知⽅法
// 切面注解
@Aspect
public class HystrixCommandAspect {
// 切入点定义;@HystrixCommand 注解,即被 @HystrixCommand 修饰的方法都将被枳入
@Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)")
// 环绕通知方法【重点关注】,这里面的逻辑就是 Hystrix 的主要处理逻辑
@Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
// 获取目标方法
Method method = getMethodFromTarget(joinPoint);
Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint);
if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) {
throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " +
"annotations at the same time");
}
// 根据传入的 Hystrix 的切面类型,获取元数据工厂
MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method));
// 获取封装元数据
MetaHolder metaHolder = metaHolderFactory.create(joinPoint);
// 根据切面方法的元数据获取 可执行器对象,这里就是 GenericCommand 对象
HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
// 获取执行方法:同步、异步、Observable,我们这里都是同步的
ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?
metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();
Object result;
try {
if (!metaHolder.isObservable()) {
// 非 Observable 响应式类型,使用 GenericCommand 执行操作,我们这里走这个分支
result = CommandExecutor.execute(invokable, executionType, metaHolder);
} else {
result = executeObservable(invokable, executionType, metaHolder);
}
} catch (HystrixBadRequestException e) {
throw e.getCause();
} catch (HystrixRuntimeException e) {
throw hystrixRuntimeExceptionToThrowable(metaHolder, e);
}
return result;
}
}
4.1)跟进 HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
核心方法 com.netflix.hystrix.contrib.javanica.command.HystrixCommandFactory#create,这里根据入参 metaHolder 返回不同的对象,我们这里返回的是 GenericCommand 对象
public class HystrixCommandFactory {
private static final HystrixCommandFactory INSTANCE = new HystrixCommandFactory();
private HystrixCommandFactory() {
}
public static HystrixCommandFactory getInstance() {
return INSTANCE;
}
// 这里根据入参 metaHolder 返回不同的对象,我们这里返回的是 GenericCommand 对象
public HystrixInvokable create(MetaHolder metaHolder) {
HystrixInvokable executable;
if (metaHolder.isCollapserAnnotationPresent()) {
executable = new CommandCollapser(metaHolder);
} else if (metaHolder.isObservable()) {
executable = new GenericObservableCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder));
} else {
executable = new GenericCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder));
}
return executable;
}
}
4.1.1)进入 GenericCommand 类
GenericCommand 中根据元数据信息等重写了 run 方法(对目标方法的调用),getFallback 方法(对回退方法的调用),在 RxJava 处理过程中会完成对这两个方法的调用。
@Override
protected Object run() throws Exception {
LOGGER.debug("execute command: {}", getCommandKey().name());
return process(new Action() {
@Override
Object execute() {
return getCommandAction().execute(getExecutionType());
}
});
}
@Override
protected Object getFallback() {
final CommandAction commandAction = getFallbackAction();
if (commandAction != null) {
try {
return process(new Action() {
@Override
Object execute() {
MetaHolder metaHolder = commandAction.getMetaHolder();
Object[] args = createArgsForFallback(metaHolder, getExecutionException());
return commandAction.executeWithArgs(metaHolder.getFallbackExecutionType(), args);
}
});
} catch (Throwable e) {
LOGGER.error(FallbackErrorMessageBuilder.create()
.append(commandAction, e).build());
throw new FallbackInvocationException(unwrapCause(e));
}
} else {
return super.getFallback();
}
}
另外,在 GenericCommand 的上层类构造函数中会完成资源的初始化,⽐如线程池 等
继承关系:GenericCommand —> AbstractHystrixCommand —> HystrixCommand —> AbstractCommand
在 AbstractCommand 中初始化了线程池、断路器、数据记录组件、properties 配置信息 等资源
protected AbstractCommand(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool,
HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults,
HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore,
HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) {
this.commandGroup = initGroupKey(group);
this.commandKey = initCommandKey(key, getClass());
// 初始化 properties 配置信息
this.properties = initCommandProperties(this.commandKey, propertiesStrategy, commandPropertiesDefaults);
this.threadPoolKey = initThreadPoolKey(threadPoolKey, this.commandGroup, this.properties.executionIsolationThreadPoolKeyOverride().get());
// 数据记录组件
this.metrics = initMetrics(metrics, this.commandGroup, this.threadPoolKey, this.commandKey, this.properties);
// 初始化断路器
this.circuitBreaker = initCircuitBreaker(this.properties.circuitBreakerEnabled().get(), circuitBreaker, this.commandGroup, this.commandKey, this.properties, this.metrics);
// 初始化线程池
this.threadPool = initThreadPool(threadPool, this.threadPoolKey, threadPoolPropertiesDefaults);
//Strategies from plugins
this.eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
this.concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
HystrixMetricsPublisherFactory.createOrRetrievePublisherForCommand(this.commandKey, this.commandGroup, this.metrics, this.circuitBreaker, this.properties);
this.executionHook = initExecutionHook(executionHook);
this.requestCache = HystrixRequestCache.getInstance(this.commandKey, this.concurrencyStrategy);
this.currentRequestLog = initRequestLog(this.properties.requestLogEnabled().get(), this.concurrencyStrategy);
/* fallback semaphore override if applicable */
this.fallbackSemaphoreOverride = fallbackSemaphore;
/* execution semaphore override if applicable */
this.executionSemaphoreOverride = executionSemaphore;
}
// 初始化线程池方法
private static HystrixThreadPool initThreadPool(HystrixThreadPool fromConstructor, HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults) {
if (fromConstructor == null) {
// get the default implementation of HystrixThreadPool
return HystrixThreadPool.Factory.getInstance(threadPoolKey, threadPoolPropertiesDefaults);
} else {
return fromConstructor;
}
}
继续深入 HystrixThreadPool.Factory.getInstance(threadPoolKey, threadPoolPropertiesDefaults);
从源码可以看到线程池的缓存容器是 ConcurrentHashMap
// 线程池的缓存容器是 ConcurrentHashMap
/* package */final static ConcurrentHashMap<String, HystrixThreadPool> threadPools = new ConcurrentHashMap<String, HystrixThreadPool>();
/* package */static HystrixThreadPool getInstance(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesBuilder) {
// get the key to use instead of using the object itself so that if people forget to implement equals/hashcode things will still work
String key = threadPoolKey.name();
// this should find it for all but the first time
HystrixThreadPool previouslyCached = threadPools.get(key);
if (previouslyCached != null) {
return previouslyCached;
}
// if we get here this is the first time so we need to initialize
// 翻译上面的注释:如果我们到达这里,这是第一次,所以我们需要初始化,即缓存中没有就创建
synchronized (HystrixThreadPool.class) {
if (!threadPools.containsKey(key)) {
threadPools.put(key, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder));
}
}
return threadPools.get(key);
}
继续深入到 com.netflix.hystrix.HystrixThreadPool.HystrixThreadPoolDefault
构造器中的 com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy#getThreadPool(HystrixThreadPoolKey, HystrixThreadPoolProperties) 方法,最终根据属性创建了 ThreadPoolExecutor
public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) {
final ThreadFactory threadFactory = getThreadFactory(threadPoolKey);
final boolean allowMaximumSizeToDivergeFromCoreSize = threadPoolProperties.getAllowMaximumSizeToDivergeFromCoreSize().get();
final int dynamicCoreSize = threadPoolProperties.coreSize().get();
final int keepAliveTime = threadPoolProperties.keepAliveTimeMinutes().get();
final int maxQueueSize = threadPoolProperties.maxQueueSize().get();
final BlockingQueue<Runnable> workQueue = getBlockingQueue(maxQueueSize);
if (allowMaximumSizeToDivergeFromCoreSize) {
final int dynamicMaximumSize = threadPoolProperties.maximumSize().get();
if (dynamicCoreSize > dynamicMaximumSize) {
logger.error("Hystrix ThreadPool configuration at startup for : " + threadPoolKey.name() + " is trying to set coreSize = " +
dynamicCoreSize + " and maximumSize = " + dynamicMaximumSize + ". Maximum size will be set to " +
dynamicCoreSize + ", the coreSize value, since it must be equal to or greater than the coreSize value");
return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
} else {
return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
}
} else {
return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
}
}
接下来回到环绕通知方法进入 execute 执行这里
com.netflix.hystrix.contrib.javanica.command.CommandExecutor#execute
public static Object execute(HystrixInvokable invokable, ExecutionType executionType, MetaHolder metaHolder) throws RuntimeException {
Validate.notNull(invokable);
Validate.notNull(metaHolder);
switch (executionType) {
// 同步执行类型
case SYNCHRONOUS: {
return castToExecutable(invokable, executionType).execute();
}
case ASYNCHRONOUS: {
HystrixExecutable executable = castToExecutable(invokable, executionType);
if (metaHolder.hasFallbackMethodCommand()
&& ExecutionType.ASYNCHRONOUS == metaHolder.getFallbackExecutionType()) {
return new FutureDecorator(executable.queue());
}
return executable.queue();
}
case OBSERVABLE: {
HystrixObservable observable = castToObservable(invokable);
return ObservableExecutionMode.EAGER == metaHolder.getObservableExecutionMode() ? observable.observe() : observable.toObservable();
}
default:
throw new RuntimeException("unsupported execution type: " + executionType);
}
}
进入 castToExecutable(invokable, executionType).execute()
com.netflix.hystrix.HystrixCommand#execute
queue() 方法方法会返回 Fetrue 对象(封装异步处理结果)
public R execute() {
try {
// queue() 方法方法会返回 Fetrue 对象(封装异步处理结果)
return queue().get();
} catch (Exception e) {
throw Exceptions.sneakyThrow(decomposeException(e));
}
}
public Future<R> queue() {
/*
* The Future returned by Observable.toBlocking().toFuture() does not implement the
* interruption of the execution thread when the "mayInterrupt" flag of Future.cancel(boolean) is set to true;
* thus, to comply with the contract of Future, we must wrap around it.
* Future 的获取、因为逻辑的执行、异常后对回退方法的调用,一系列的处理都使用了 RxJava 响应式编程的内容,暂时了解到这里
*/
final Future<R> delegate = toObservable().toBlocking().toFuture();
}