熔断机制是解决微服务架构中因等待出现故障的依赖方响应而形成任务挤压,最终导致自身服务瘫痪的一种机制,它的功能类似电路的保险丝,其目的是为了阻断故障,从而保护系统稳定性。Hystrix作为Spring Cloud中实现了熔断机制的组件,具有服务容错保护功能。
Hystrix简介
什么是Hystrix?
概述:Hystrix是Netflix开源的一款针对分布式系统延迟和容错的库。
作用:通过添加延迟容忍和容错逻辑,从而控制分布式服务之间的交互。
为什么用Hystrix?
对于一个复杂的分布式系统,包含的应用可能多达数十个,这些应用有许多依赖项目,每个依赖项目在某个时刻不可避免会失败导致故障,如果不对这些故障进行隔离,整个分布式系统都可能会崩溃。
分布式系统中服务请求一切正常情况
分布式系统中服务有一个系统有延迟请求情况
当分布式系统中其中有一个系统有延迟时,它可能阻塞整个用户请求:
分布式系统中服务高流量下请求情况
在高流量情况下,一个后端的依赖延迟可能会导致所有服务的资源在数秒内变的饱和,这也就意味着,后续如果再有请求将无法提供服务,应用会出现故障。比故障更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,从而备份队列、线程和其他资源,从而导致整个系统出现更多级联故障.如图:
分布式系统中Hystrix解决服务请求堵塞情况
Hystrix的出现就是为了解决上述问题的,它封装了每个依赖项,每个依赖项彼此隔离,当延迟发生时,它会被限制在资源中,并包含回退逻辑,该逻辑决定在依赖发生任何类型故障时应作出何种响应。
简单创建Hystrix工程
工作架构图:
1.创建eureka-server项目
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xiaofen</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot with Eureka Server</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>
spring-cloud-starter-netflix-eureka-server
</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 如果需要,可以添加构建插件等配置 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 7000 # 服务器端口号7000
spring:
application: #端口号名称配置
name: eureka-server
eureka:
client:
fetch-registry: false # 表示是否向Eureka Server注册
register-with-eureka: false # 表示是否从Eureka Server获取注册信息
service-url:
defaultZone:
http://${eureka.instance.hostname}:${server.port}/eureka/ #设置服务注册中心地址
instance:
hostname: localhost
启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
2.创建eureka-hystrix-client项目
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.xiaofeng</groupId>
<artifactId>eureka-hystrix-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-hystrix-client</name>
<description>Demo project for Spring Boot</description>
<url/>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<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-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
application.yml
server:
port: 8764
spring:
application:
name: eureka-hystrix-client
eureka:
client:
service-url:
defaultZone: http://localhost:7000/eureka/
instance:
hostname: localhost
启动类添加这几个注解:
config:
@Configuration
public class HystrixConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
controller:
@RestController
public class HystrixController {
@Autowired
private HystrixService hystrixService;
@GetMapping("/hi")
public String hi(String id){
return hystrixService.hi(id);
}
}
service:
@Service
public class HystrixService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "hiError")
public String hi(String id){
return restTemplate.getForObject("http://hystrix-provider/hi?id="+id,String.class);
}
public String hiError(String id){
return "此次调用进行服务降级,当前的id为:"+id;
}
}
3.创建hystrix-provider项目
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xiaofeng</groupId>
<artifactId>hystrix-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>hystrix-provider</name>
<description>Demo project for Spring Boot</description>
<url/>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.1.7.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.7.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 如果需要,可以添加构建插件等配置 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动类:
@SpringBootApplication
@EnableEurekaClient
public class HystrixProviderApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixProviderApplication.class, args);
}
}
controller:
@RestController
public class HystrixController {
@RequestMapping("/hi")
public String hi(String id){
return "你好,访问接口成功,当前id为:"+id;
}
}
4.项目测试
成功:
失败:
在Feign中使用Hystrix熔断器
开启Hystrix熔断
Feign自带熔断功能,默认情况下,熔断功能是关闭的。如果要开启熔断,只需在配置文件中将hystrix.enabled设置为true即可。
开启Hystrix熔断功能。在eureka-hystrix-client项目的配置文件application.yml中添加开启熔断的配置
feign:
hystrix:
enabled: true
因为在Feign的起步依赖中引入了Hystrix依赖,所以在Feign中使用Hystrix不需要引入任何的依赖,只需要在配置文件中开启即可。
开启Feign Client功能
在eureka-hystrix-client的启动类中添加@EnableFeignClients开启Feign Client功能。
修改项目中接口
进入项目eureka-hystrix-client,在HystrixService接口上方的@FeignClient注解中,增加fallback属性配置,指定HystrixServiceImpl类为失败逻辑处理类
使用fallback属性指定的用于处理回退逻辑的类,必须实现@FeignClient注解修饰的接口。
FeignHystrixServiceImpl实现了FeignHystrixService接口,并定义了一个hi()方法用于编写处理熔断的具体逻辑。这里,我们使用@Component注解修饰类FeignHystrixServiceImpl,其目的是将该类交给Spring容器管理。
controller
运行测试:
成功:
失败:
Hystrix工作原理
Hystrix改为工作流程图
Hystrix的具体工作流程
1.实例化HystrixCommand或HystrixObservableCommand对象
第一步实例化HystrixCommand或HystrixObservableCommand对象,是因为它们封装了对外部依赖访问的逻辑。
2.调用Command方法触发操作指令
通过创建的HytrixCommand和HystrixObservableCommand实例调用相关方法执行操作指令。Hystrix API提供了4个触发流程的方法供开发者调用,具体如下:
1、execute()方法:该方法是同步的,从依赖请求中接收到单个响应(或者出错时抛出异常)。
2、queue()方法:调用外部依赖只返回一个值,返回一个Future对象。
3、observe()方法:订阅一个从依赖请求中返回的Observable对象,这个Observable对象包含了从依赖服务返回的结果。
4、toObservable()方法:返回一个Observable对象,只有当订阅一个从依赖请求中返回的Observable对象时,该方法才会执行Hystrix命令并返回结果。
3.根据依赖调用的缓存情况进行相应处理
执行操作指令时,Hystrix首先会检查缓存内是否有对应指令的结果,如果有的话,将缓存的结果直接以Observable对象的形式返回。
4.判断熔断器是否开启
没有对应的缓存,Hystrix会检查Circuit Breaker的状态。如果Circuit Breaker的状态为开启状态,Hystrix将不会执行对应指令,而是而是直接路由到第8步(Fallback),获取fallback方法,并执行fallback逻辑。
5.判断线程池/队列/信号资源是否已满
如果Circuit Breaker的状态为关闭状态,Hystrix会继续进行线程池、任务队列、信号量的检查(第5步),确认是否有足够的资源执行操作指令。如果资源满,Hystrix同样将不会执行对应指令,而是立即跳到第8步,执行fallback逻辑。
6.执行HystrixObservableCommand.construct()或HystrixCommand.run()
如果资源充足,Hystrix将会执行操作指令,操作指令的调用最终会用到两个方法:
1、HystrixCommand.run(): 返回单个响应或者抛出异常。
2、HystrixObservableCommand.construct():返回一个发射响应的Observable
或者发送一个onError()的通知。
如果执行上述方法的执行时间大于命令所设置的超时时间值,那么该线程将会抛出一个TimeoutException异常在这种情况下,Hystrix将会路由到第8步,执行fallback逻辑,并且如果上述方法没有被取消或者中断,会丢弃这上述两个方法最终返回的结果。
如果命令最终返回了响应并且没有抛出任何异常,Hystrix在返回响应后会返回一些日志信息。
(1)如果是调用run()方法,Hystrix会返回一个Observable。
(2)如果是调用construct()方法,Hystrix会通过construct()方法返回
相同的Observable对象。
7.计算熔断器执行数据
Hystrix会报告成功、失败、拒绝和超时的指标给熔断器,熔断器包含了一系列的滑动窗口数据,并通过该数据进行统计。Hystrix使用这些统计数据来决定熔断器是否应该熔断,如果需要熔断,将在一定的时间内不再请求依赖(这个时间可以通过配置指定),当再一次检查请求的健康的话会重新关闭熔断器。
8.调用降级方法或者返回依赖请求的真正结果
如果Hystrix命令执行成功,它将以Observable形式返回响应给调用者。根据第(2)步的调用方式不同,在返回Observable之前可能会做一些转换。
Dashboard监控熔断器状态
1.主要依赖pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.然后在原来项目基础上再添加开启DashBoard的注解:
@EnableHystrixDashboard
3.创建config包,在 config包下创建HystrixDashboardConfiguration类,加入Hystrix.stream的Servlet配置。
@Configuration
public class HystrixDashboardConfiguration {
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new
HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new
ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
4.测试
还有一个图形化界面http://localhost:8764/hystrix
点击按钮后进入这个界面:
① 标注的是服务的健康程度。
② 数字从上往下依次是消费者请求调用者的请求成功数、短路或熔断数和失败
的次数。
③ 数字从上往下依次是消费者请求调用者的超时数、线程池拒绝数和请求异常数。
④ 表示集群下的主机报告。
⑤ 表示消费者请求调用者的请求频率。