1. Hystrix简介
Hystrix是由Netflix开源的一个延迟和容错库,用 于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主 要通过以下几点实现延迟和容错。
- 包裹请求:使用HystrixCommand包裹对依赖的调用逻辑。 服务消费者方法 使用@HystrixCommand 添加Hystrix控制
- 跳闸机制:当某服务的错误率超过一定的阈值时,Hystrix可以跳闸,停止请求该服务一段时间。
- 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(舱壁模式)(或者信号量)。如果该线程 池已满, 发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定。
- 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝 的请求等。
- 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑由开发人员 自行提供,例如返回一个缺省值。
- 自我修复:断路器打开一段时间后,会自动进入“半开”状态。
2. Hystrix熔断器使用及配置
- 服务消费者工程中引入Hystrix依赖坐标(也可以添加在父工程中)
<!--熔断器Hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 服务消费者工程的启动类中添加熔断器开启注解@EnableCircuitBreaker
@SpringBootApplication
@EnableDiscoveryClient
//@EnableHystrix // 开启Hystrix功能
@EnableCircuitBreaker // 开启熔断器功能
//@SpringCloudApplication 综合性的注解 @SpringCloudApplication = @SpringBootApplication + @EnableDiscoveryClient + @EnableCircuitBreaker
public class AutodeliverApplication {
public static void main(String[] args) {
SpringApplication.run(AutodeliverApplication.class,args);
}
// 使用RestTemplate模板对象进行远程调用
@Bean
//Ribbon负载均衡
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
- 定义服务降级处理方法,并在业务方法上使用@HystrixCommand的fallbackMethod属性关联到服务降级处理方法
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/autodeliver")
public class AutodeliverController {
@Autowired
RestTemplate restTemplate;
/**
* 提供者模拟处理超时,调用方法添加Hystrix控制
*
* @param userId
* @return
*/
// 使用@HystrixCommand注解进行熔断控制,此处没有指明fallbackMethod方法
@HystrixCommand(
// 线程池标识,要保持唯一,不唯一的话就共用默认线程池
threadPoolKey = "findResumeOpenStateTimeout",
// 线程池细节属性配置
threadPoolProperties = {
// 线程数
@HystrixProperty(name = "coreSize", value = "1"),
// 等待队列长度
@HystrixProperty(name = "maxQueueSize", value = "20")
},
// commandProperties熔断的一些细节属性配置
commandProperties = {
// 每一个属性都是一个HystrixProperty
//超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
}
)
@GetMapping("/checkStateTimeout/{userId}")
public Integer findResumeOpenStateTimeout(@PathVariable Long userId) {
// 使用ribbon不需要我们自己获取服务实例然后选择一个那么去访问了(自己的负载均衡)
String url = "http://lagou-service-resume/resume/openstate/" + userId; // 指定服务名
Integer forObject = restTemplate.getForObject(url, Integer.class);
return forObject;
}
@GetMapping("/checkStateTimeoutFallback/{userId}")
@HystrixCommand(
// 线程池标识,要保持唯一,不唯一的话就共用了
threadPoolKey = "findResumeOpenStateTimeoutFallback",
// 线程池细节属性配置
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "2"), // 线程数
@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 findResumeOpenStateTimeoutFallback(@PathVariable Long userId) {
// 使用ribbon不需要我们自己获取服务实例然后选择一个那么去访问了(自己的负载均衡)
String url = "http://lagou-service-resume/resume/openstate/" + userId; // 指定服务名
Integer forObject = restTemplate.getForObject(url, Integer.class);
return forObject;
}
/**
* 定义回退方法,返回预设默认值
* 注意:该方法形参和返回值与原始方法保持一致
*
* @param userId
* @return
*/
public Integer myFallBack(Long userId) {
// 兜底数据
return -1;
}
}
参数配置及说明在上面的代码中已经注释了,示例是对每个方法单独配置,需要注意的是降级(兜底)方法必须和被降级方法相同的方法签名(相同参数列表、相同返回值)。如果想要对每个方法采用统一的配置可以在类上使用@DefaultProperties注解统一指定整个类中共用的降级(兜底)方法以及相关配置信息,如下
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DefaultProperties {
String groupKey() default "";
//线程池标识
String threadPoolKey() default "";
//熔断的一些细节属性配置
HystrixProperty[] commandProperties() default {};
//线程池细节属性配置
HystrixProperty[] threadPoolProperties() default {};
Class<? extends Throwable>[] ignoreExceptions() default {};
HystrixException[] raiseHystrixExceptions() default {};
//默认兜底方法
String defaultFallback() default "";
}
3. Hystrix舱壁模式(线程池隔离策略)
如上所示,如果不进行任何设置,所有熔断方法使用一个Hystrix线程池(10个线程),那么这样的话会导致问题,这个问题并不是扇出链路微服务不可用导致的,而是我们的线程机制导致的,如果方法A的请求把 10个线程都用了,方法2请求处理的时候压根都没法去访问B,因为没有线程可用,并不是B服务不可 用。
- 舱壁模式示意图
如上图所示,舱壁模式就如同船中间的隔板,没有中间的隔板,船上所有的食品都会受到影响破坏,有隔板的话,如果一侧出现漏洞,只会影响一部分食品。这种资源隔离的方式就是舱壁模式。
为了避免问题服务请求过多导致正常服务无法访问,Hystrix 不是采用增加线程数,而是单独的为每一 个控制方法创建一个线程池的方式,这种模式叫做“舱壁模式",也是线程隔离的手段。在上面的代码示例中通过threadPoolKey和threadPoolProperties对每个方法配置单独的线程池,使他们互相不受影响。注意:threadPoolKey必须唯一,如果一样就会共享。
4. 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")
} )
我们上述通过注解进行的配置也可以配置在配置文件中
# 配置熔断策略:
hystrix:
command:
default:
circuitBreaker:
# 强制打开熔断器,如果该属性设置为true,强制断路器进入打开状态,将会拒绝所有的请求。 默认false关闭的
forceOpen: false
# 统计时间窗口
timeInMilliseconds: 8000
# 触发熔断错误比例阈值,默认值50%
errorThresholdPercentage: 50
# 熔断后休眠时⻓,默认值5秒
sleepWindowInMilliseconds: 3000
# 熔断触发最小请求次数,默认值是20
requestVolumeThreshold: 2
execution:
isolation:
thread:
# 熔断超时设置,默认为1秒
timeoutInMilliseconds: 2000
通过以下配置,可以调用http://ip:port/actuator/health 这个接口访问服务健康状态
# springboot中暴露健康检查等断点接口
management:
endpoints:
web:
exposure:
include: "*"
# 暴露健康接口的细节
endpoint:
health:
show-details: always
能访问上个接口的前提是在项目用引入下面的依赖
<!-- Actuator可以帮助你监控和管理Spring Boot应用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>