目录
一、Hysrix简介
1.1、分布式系统面临的“雪崩”问题
多个微服务之间调用的时候,假如微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的"扇出"。
如果扇出的链路上某个微服务的调用响应的时间过长或者不可用,对微服A的调用就会占用越来越多的系统资源,进而引起系统崩溃,即"雪崩效应"。
对于高流量的应用来说,单一的后端依赖可能会导致所有的服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
而Hystrix可以简单的理解为一个类似电路的保险丝,其目的是阻断故障,保护系统的稳定性。
1.2、Hystrix概念
Hystrix作为Netflix公司推出的一款针对分布式系统延迟和容错的开源库,是实现了容错机制的组件,设计的目的是通过添加延迟容忍和容错逻辑,从而控制分布式服务之间的交互。
对于一个复杂的分布式系统,包含的应用可能有数十个,这些应用有许多依赖项目,每个依赖项目不可避免在某个时刻会失败,导致出现故障。如果不对这些故障进行隔离,整个系统就可能会面临崩溃
借助Hystrix官网的一个例子来说明:
要是某个应用程序依赖30个服务,每个服务正常运行的概率是99.99%,那么系统可用的概率就是
=99.7%,也就是说这个系统出现故障的概率的0.3%。10亿用户请求的故障就是3000000个。即使所有依赖具有出色的正常运行时间,每个月至少会有2个小时的时间中服务是不可用的。现实情况中,情况可能会更加严重。
1.3、Hystrix特性
(1)请求熔断:熔断机制是应对服务雪崩的一种链路保护机制,当服务出现故障时,服务会进行降级,熔断该服务节点,迅速返回错误响应信息。当检测到服务访问正常时,恢复其链路节点。
(2)服务降级:Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.告知后面的请求服务不可用了,不要再来了。
(3)依赖隔离:(采用舱壁模式,Docker就属于舱壁模式)在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池。
比如说,一个服务调用两外两个服务,你如果调用两个服务都用一个线程池,那么如果一个服务卡在哪里,资源没被释放后面的请求又来了,导致后面的请求都卡在哪里等待,导致你依赖的A服务把你卡在哪里,耗尽了资源,也导致了你另外一个B服务也不可用了。这时如果依赖隔离,某一个服务调用A B两个服务,如果这时我有100个线程可用,我给A服务分配50个,给B服务分配50个,这样就算A服务挂了,我的B服务依然可以用。
(4)请求缓存:比如一个请求过来请求我userId=1的数据,你后面的请求也过来请求同样的数据,这时我不会继续走原来的那条请求链路了,而是把第一次请求缓存过了,把第一次的请求结果返回给后面的请求。
(5)请求合并:请求合并:我依赖于某一个服务,我要调用N次,比如说查数据库的时候,我发了N条请求发了N条SQL然后拿到一堆结果,这时候我们可以把多个请求合并成一个请求,发送一个查询多条数据的SQL的请求,这样我们只需查询一次数据库,提升了效率。
1.4、Hystrix具体实现
通过上一段对Hystrix特性的说明,我们可以知道Hystrix的目标是阻止级联故障,对第三方客户端访问的依赖的延迟和故障进行保护和控制,根据其特性,具体的实现大致思路如下:
- 将外部依赖的访问请求封装在独立的线程中,进行资源隔离;
- 对于超出设定阈值的服务调用,直接进行超时处理,不允许其耗费过长时间阻塞线程;
- 每个依赖服务维护一个独立的线程池,一旦线程池满了,直接拒绝服务的调用;
- 统计依赖服务的成功次数,失败次数,拒绝次数,超时次数等结果
- 在一段时间内,如果服务调用的异常次数超过一定的阈值,就会触发熔断器,停止对特定服务的所有请求,在一定时间内对服务调用直接降级,一段时间后自动恢复;
- 如果某个服务出现调用失败,被拒绝,超时等异常情况,就会自动调用fallback降级机制;
- 实时监控指标和配置变化。
二、Hystrix入门
利用前面的东西,搭建一个如下图所示的案例架构:
1 搭建Eureka-server
这里我们无需再去进行Springboot项目eureka-server的搭建,直接使用搭建Eureka所用到的Eureka-server
2 创建Eureka-hystrix-client的SpringBoot项目
使用Spring Initializr的方式创建一个名为demo-hystrix-client的springBoot项目,添加Eureka Client,Web,Feign,Ribbon,Hystrix依赖,
在这里说明一下:老版本引入spring-cloud-starter-feign.jar后就集成了hystrix功能不需要额外引入jar包。但新版本引入的是spring-cloud-starter-openfeign.jar,如果不引入hysrtix的jar包,hystirx不生效,因此我们需要自己手动添加hystrix依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
创建后的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.7.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo.hystrix.client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo.hystrix.client</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.7</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-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.2.RELEASE</version>
</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>
3 启动类添加@EnableHystrix注解,启动熔断功能
在eureka-hystrix-client的启动类中添加@EnableHystrix注解,启动熔断功能
@EnableHystrix
4 创建config包,在包下创建HystrixConfig类
代码如下:
package com.example.demo.hystrix.client.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @Title: HystrixConfig
* @Description:
* @Auther:
* @Version: 1.0
* @create 2023/5/27 15:42
*/
@Configuration
public class HystrixConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
5 创建controller包,在包下创建类LocalItemController类并增加@RestController注解
package com.example.demo.hystrix.client.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Title: LocalItemController
* @Description:
* @Auther:
* @Version: 1.0
* @create 2023/5/27 15:45
*/
@RestController
public class LocalItemController {
@Autowired
LocalItemController localItemController;
@GetMapping("/hi")
public String hi(String id){
return localItemController.hi(id);
}
}
6 新建service包,包下新建LocalItemSerice类,在类中添加注解
package com.example.demo.hystrix.client.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* @Title: LocalItemService
* @Description:
* @Auther:
* @Version: 1.0
* @create 2023/5/27 15:53
*/
@Service
public class LocalItemService {
@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 String.format("hi,your message is:%s but requst bad.",id);
}
}
7 使用Spring Initialzr的方式创建一个名称为hystrix-provider的SpringBoot项目,添加Eureka client,test,web依赖
8. 在hystrix-provider中创建controller包。并在包下创建HystrixController类。在类中添加hi()方法
package com.example.hystrixprovider.controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @Title: HystrixController
* @Description:
* @Auther:
* @Version: 1.0
* @create 2023/5/27 16:09
*/
public class HystrixController {
@RequestMapping("/hi")
public String hi(String id){
return "Hello world,I'm from hystrix!"+id;
}
}
9. 启动服务并进行测试。
依次启动eureka-server,hystrix-provider,eureka-hystrix-client,在浏览器中输入http://localhost:8764!id=12
浏览器会显示出:
Hello World,I'm from hystrix!12
关闭服务提供者demo-provider,制造服务不可用的情形,再次进行浏览器请求,浏览器会显示:
Hi ,your message is: 12 but request bad.
由此可知,当服务提供者hystrix-rovider不可用的情况下,如果消费者demo-hystrix-client调用服务提供者hystrix-provider的“/hi”方法,就会失败,此时会开启熔断器。熔断器打开后,请求会直接执行fallbackMethod逻辑,通过处理快速失败情况,做到及时处理请求,避免线程阻塞。
三、hystrix结合Feign
Feign自带熔断功能,默认情况下,熔断功能是关闭的。如果要开启熔断器只需在配置文件中将hystrix.enabled设置为true即可,
在上一步的基础上对项目进行改造:
1 开启Hystrix熔断功能,在demo-hystrix-client项目的配置文件application.yml中添加如下,开启熔断的配置:
feign:
hystrix:
enabled: true
2 启动类中添加@EnableFeignClients注解,开启FeignClient功能
3 在LocalItemService接口上方的@FeignClient注解中增加fallback属性,指定LocalItemServiceImpl类为失败逻辑处理类
@FeignClient(value = "hystrix-provider",
configuration = HystrixCommand.class,
fallback = LocalItemServiceImpl.class)
public interface LocalItemService {
@GetMapping(value= "/hi",method=RequestMethod.GET)
public String hi(@RequestParam(value="id")String id);
4 在service包中新建失败逻辑处理类LocalItemServiceImpl,并实现@FeignClient注解修饰的LocalItemService接口
package com.example.demo.hystrix.client.service;
public class LocalItemServiceImpl extends LocalItemService{
@Override
public String hi(String id){
return String.format("Hi,your message is : %s but request");
}
}
5 启动服务并测试
依次启动eureka-server,hystrix-provider,eureka-hystrix-client,在浏览器中输入http://localhost:8764!id=hello
浏览器会显示出:
Hello World,I'm from hystrix!hello
关闭服务提供者demo-provider,制造服务不可用的情形,再次进行浏览器请求,浏览器会显示:
Hi ,your message is: hello but request bad.
四、hystrix结合Ribbon
五、Hystrix工作原理
针对hystrix的工作流程,大致可以分为8个步骤:
1.实例化HystrixCommand或HystrixObservableCommand对象;
构建一个HystrixCommand或HystrixObservableCommand实例来向其他的组件发出操作请求,通过构造方法创建实例。
HystrixCommand:返回一个单响应
HystrixObservableCommand: 返回一个观察者发出的响应
2.调用Command方法触发操作指令
execute():阻塞型方法,返回单个结果(或抛出异常)
queue(): 异步方法,返回一个Future对象,可以从中取出单个结果(或抛出异常)
observe(): 返回Observable对象
toObserve(): 返回Observable对象
3.根据依赖调用的结果缓存情况进行响相应处理
检查缓存内是否有对应指令的结果,如果有的话,将缓存的结果直接以Observable对象形式返回。
4.断路器判断是否开启
检查 Circuit Breakerr的状态如果 Circuit Breaker的状态为开启 Hystrix将不会执行对应指令,而是直接进入失败处理状态(图中 8)。如果Circuit Breaker的状态为关闭,Hystrix会继续执行
5. 线程池、任务队列、信号量的检查 (是否满了)
确认是否有足够的资源执行操作指令。当线程池和队列(或者是信号量不使用隔离模式的时候 资源满的时候 ,Hystrix将不会执行对应指令并且会直接进入失败处理状态
6. 执行HystrixObservableCommand.construct()和HystrixCommand.run()方法
如果资源充足,Hystrix将会执行操作指令,操作指令的调用最终都会到这两个方法:
HystrixCommand.run():返回一个响应或者抛出一个异常。
HystrixObservableCommand.construct(): 返回一个可观测的发出响应(s)或发送一个onError通知
如果执行指令的时间超时,执行线程会抛出TimeException异常。Hystrix会抛弃结果并直接进入失败状态。如果执行指令成功,Hystrix会进行一系列的数据记录,然后返回执行的结果。
Hystrix会根据记录的数据来计算失败比率,一旦失败比率达到某一阀值将自动开启Circuit Breaker
如果我们再Command中实现了HystrixCommand..getFallback方法(或HystrixObservableCommand.resumeWithFallback())方法,Hystrix会返回对应方法的结果,如果没有实现这些方法,Hystrix仍然会返回一个空的Observable对象,并且可以通过onError来终止并处理错误。
调用不同的方法放回不同的结果:
execute(): 将会抛出异常
queue(): 将会返回一个Future对象,如果调用它的get()方法将会抛出异常。
observe()和toObservable():都会返回上述的Observable对象