首页 > 其他分享 >【深入理解SpringCloud微服务】Hystrix作用与原理剖析

【深入理解SpringCloud微服务】Hystrix作用与原理剖析

时间:2024-10-26 11:18:09浏览次数:10  
标签:Hystrix SpringCloud 接口 剖析 断路器 线程 方法 HystrixCommand

【深入理解SpringCloud微服务】Hystrix作用与原理剖析

前面三篇文章,我们已经了解了微服务的熔断、限流、降级,并且也手写了一个熔断限流框架。从本篇文章开始,我们了解一下相关的开源框架Hystrix与Sentinel,本篇文章先解析Hystrix,后面的文章解析Sentinel。

Hystrix其实已经算是一个过期了的技术,说实话不学也没什么问题,毕竟现在都有Sentinel了,但是我们简单了解一下,有助于与Sentinel作对比。

Hystrix的作用

Hystrix是Netflix开发的用于服务容错保护的开源框架,然后Spring Cloud Netflix在此基础上做了些包装,添加了自动配置的功能,使得Hystrix的配置和使用变得更加简单。Hystrix提供了熔断、降级和隔离等功能。

在这里插入图片描述

熔断

熔断的作用在之前的文章已经介绍过了,它类似于电路中的保险丝。

熔断的功能由断路器实现,在Hystrix中断路器是HystrixCircuitBreaker。当断路器处于闭合状态时,请求可以被正常处理;如果断路器处于打开状态,请求将不会被正常处理,而是被降级处理或抛出异常(如果没有设置降级逻辑的话)。

在这里插入图片描述

除此以外,断路器还有一个半开状态。半开状态是指当断路器打开了一段时间后,会允许一个请求通过,测试一下服务是否恢复正常。如果服务恢复正常,那么断路器状态切换为闭合状态;否则再次切换为打开状态。

Hystrix中断路器的状态切换如下图:

在这里插入图片描述

降级

降级是指当断路器处于打开状态,或者接口的调用本身出现异常或超时时,转而执行的备用逻辑。Hystrix的降级处理也是如此。

在这里插入图片描述

只要我们的方法被 @HystrixCommand 注解修改,并在注解的 fallbackMethod 属性设置降级处理方法的方法名,当断路器打开、接口执行超时或失败等情况发生时,Hystrix就会执行我们指定的那个降级处理方法。

在这里插入图片描述

隔离

隔离是指不同接口间的资源隔离,这个被隔离的资源一般是线程资源。由于每个接口的请求都需要当前服务开辟一个线程去处理,如果接口间没有资源隔离的话,一旦某个接口由于某种原因导致请求处理长时间无响应,该接口就有可能吃光当前服务的所有线程资源(特别是接口并发量比较高时),进而影响到别的接口也无法处理请求,那么整个服务就瘫痪了。

在这里插入图片描述

解决这种问题的办法就是实现资源隔离。资源隔离使用的是“仓壁模式”,就像Docker那样,Docker通过舱壁模式,实现进程间的资源隔离,把每个进程包裹在一个容器里面,并为他配置一定的资源上限,使得进程与进程之间不会互相影响。

在这里插入图片描述

Hystrix则是通过线程池(默认)进行资源隔离。Hystrix为每一个受保护的接口或方法都配备一个独立的线程池,并限定池内的线程数。如此一来,即使一个接口有问题,它也只能耗光自己线程池内的所有线程资源,其他接口不会受影响。

在这里插入图片描述

当然,这种为每一个受保护的接口或方法单独分配一个线程池的方式,会给系统带来一定的负载和开销,但是这种消耗是微乎其微的。如果接口的延迟本身非常小,相对而言这部分的开销有点大,那么可以改用信号量隔离的方式,信号量默认值是10。

在这里插入图片描述

线程池隔离与信号量隔离有一个不同点在于,使用线程池隔离时,请求处理是在线程中异步执行的,而信号量隔离则是在当前线程

Hystrix有限流的功能吗?

Hystrix没有直接提供限流的功能,但是它通过线程池隔离或信号量隔离的方式间接实现了限流。

比如我们使用线程池隔离,每个为某个接口或方法而独立配备的线程池都有一个线程数的上限,一旦达到了这个线程数上限,请求就会被拒绝,这就达到了限流的效果。

比如现在有一个接口,我们设置了该接口的线程池最大线程数是3,那么该接口最多只能同时处理3个请求。

在这里插入图片描述

如果此时有第4个请求到达,那么该请求将会被拒绝处理,如此也就达到了限流的效果。

在这里插入图片描述

通过信号量隔离的方式,也可以实现限流,只要设置信号量的最大数量即可。

在这里插入图片描述

Hystrix的原理

@HystrixCommand注解是如何起作用的

Hystrix的使用就是在接口或方法上声明@HystrixCommand(当然还有引入maven依赖,在启动类上添加注解等前置工作),那这个@HystrixCommand是如何起作用的呢?

除了在接口上添加@HystrixCommand注解外,我们还有在启动类上添加 @EnableHystrix@EnableCircuitBreaker注解,实际上@EnableHystrix里面就是@EnableCircuitBreaker注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableCircuitBreaker
public @interface EnableHystrix {

}

那么这个@EnableCircuitBreaker注解有何作用呢?

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableCircuitBreakerImportSelector.class})
public @interface EnableCircuitBreaker {
}

@EnableCircuitBreaker注解通过@Import注解导入了一个EnableCircuitBreakerImportSelector

在这里插入图片描述

public class EnableCircuitBreakerImportSelector extends SpringFactoryImportSelector<EnableCircuitBreaker> {
    ...
}

然后EnableCircuitBreakerImportSelector又继承了Spring的SpringFactoryImportSelector,并且泛型指定为EnableCircuitBreaker。熟悉SpringBoot自动装配的都知道,SpringBoot启动时会去扫描所有的spring.factories文件并加载里面key的类型匹配的类。

因此我们看一下spring-cloud-netflix-hystrix工程的spring.factories文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration,\
org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration

org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\
org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration

EnableCircuitBreaker对应的类是HystrixCircuitBreakerConfiguration,于是HystrixCircuitBreakerConfiguration就会被Spring加载进来。

在这里插入图片描述

@Configuration
public class HystrixCircuitBreakerConfiguration {

	@Bean
	public HystrixCommandAspect hystrixCommandAspect() {
		return new HystrixCommandAspect();
	}

	...

}

然后HystrixCircuitBreakerConfiguration通过@Bean引入了一个HystrixCommandAspect

在这里插入图片描述

@Aspect
public class HystrixCommandAspect {
    private static final Map<HystrixCommandAspect.HystrixPointcutType, HystrixCommandAspect.MetaHolderFactory> META_HOLDER_FACTORY_MAP;

    public HystrixCommandAspect() {
    }

    @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)")
    public void hystrixCommandAnnotationPointcut() {
    }

    ...

    @Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
    public Object methodsAnnotatedWithHystrixCommand(ProceedingJoinPoint joinPoint) throws Throwable {
    	...
    }

	...

}

然后我们可以看到HystrixCommandAspect被@Aspect注解,因此HystrixCommandAspect是一个切面类,而HystrixCommandAspect通过 @Pointcut(“@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)”) 注解指定扫描被@HystrixCommand注解修饰的接口或方法进行代理增强。

然后通过 @Around 注解声明了环绕增加的处理逻辑是methodsAnnotatedWithHystrixCommand(ProceedingJoinPoint) 方法。

在这里插入图片描述

于是当我们调用被@HystrixCommand注解修饰的方法时,实际上调用的是代理对象,然后就会进入到methodsAnnotatedWithHystrixCommand(ProceedingJoinPoint)这个增强处理方法当中,而Hystrix的功能就在这里面实现。

在这里插入图片描述

工作流程

下面我们来了解一下,当一个请求被Hystrix的增强逻辑拦截到时,整个工程流程是怎么样的。

在这里插入图片描述

1、构建命令对象

在这里插入图片描述

Hystrix内部使用了命令模式,Hystrix接收到请求时,首先构建一个命令对象。命令对象的类型是HystrixCommandHystrixObservableCommand

如果我们的接口返回一个结果,那么构建的命令就是HystrixCommand;如果我们的结果返回多个结果,构建的命令就是HystrixObservableCommand。

这里说的多个结果不是指数组或List这种类型,而是Observable,我们一般不会返回这种东西,因此就不多研究了,我们只要知道,通过Observable,接口就可以给调用者返回多个结果。

2、执行命令

在这里插入图片描述

构建了命令对象之后,就要去执行该命令。根据命令类型的不同会调用不同方法。

如果是HystrixCommand,则调用HystrixCommand的execute()方法或queue()方法。execute()是用于执行同步请求的,而queue()本来是用于异步请求的,然后返回一个Future对象。

如果我们的接口或方法返回的类型是Future,那么这里就会调用HystrixCommand的queue()方法,否则就会调用execute()方法。

但实际上execute()方法内部也是调用了queue()方法,只是在queue()方法返回Future之后,立刻调用Future的get()方法进行异步转同步

在这里插入图片描述

而queue()方法内部则是调用了HystrixObservableCommand的toObservable()方法返回一个Observable对象,然后立刻调用Observable的toBlocking()方法把该Observable转换成BlockingObservable对象,BlockingObservable与Observable的区别是当订阅BlockingObservable时当前线程会被阻塞。然后得到BlockingObservable后立刻调用toFuture()方法转成Future对象。

在这里插入图片描述

因此命令的执行最终都会走到HystrixObservableCommand的toObservable()方法。

3、检查缓存是否开启并且是否命中

在这里插入图片描述

Hystrix是可以开启缓存功能的,如果我们配置了该接口开启缓存,并且缓存命中,就会从缓存中返回Observable对象。

4、检查断路器是否打开

在这里插入图片描述

如果缓存没有开启,或者开启了但是没有命中。那么就检查断路器是否打开,如果打开了,那么执行降级逻辑;否则进行下一步处理。

5、检查线程池或信号量是否已满

在这里插入图片描述

如果断路器时闭合状态或半开状态,那么就会检查线程池或信号量是否已经满了,如果满了,那也执行降级逻辑;否则进行下一步处理。

6、执行目标方法

在这里插入图片描述

然后就是调用HystrixCommand的run()方法HystrixObservableCommand的construct()方法,这两个方法就会执行到我们的目标接口或方法。

如果我们的接口执行发生异常或超时,就会执行降级逻辑;否则就会返回执行的结果。

7、报告断路器

在这里插入图片描述

方法执行成功或失败(包括线程池或信号量已满)等信息报告给断路器,断路器要根据这些信息进行统计,计算QPS和失败率是否达到一定的阈值,如果是的话那么就要把断路器打开了。

8、降级处理

在这里插入图片描述

如果线程池或信号量已满、目标方法执行失败或异常等情况发生,Hystrix就会执行降级方法。

降级方法如果执行成功,那么以Observable的形式返回降级处理的结果,订阅该Observable将会获取到降级方法返回的结果;如果降级方法执行出现了异常,也是返回一个Observable,但是该Observable不会有结果,而是会调用订阅者onError()方法通知订阅者终止请求。

由于我们的方法一般都是同步处理并返回单一结果,因此Observable会被转成Future,然后调用Future的get()方法异步转同步,这里调用Future的get()方法就会抛出异常。

9、返回成功的结果

在这里插入图片描述

如果目标方法正常执行并且没有超时,那么就会返回正常的处理结果。

其实这里并不是直接返回目标方法执行的结果,而是返回一个Observable。但是由于我们的方法一般都是同步处理且返回单一结果,因此当返回Observable之后,就会被转成Future然后调用get()方法获取到目标方法处理的结果,因此返回给我们的就是目标方法返回的结果。

断路器原理

断路器的接口是HystrixCircuitBreaker,它有三个方法:

public interface HystrixCircuitBreaker {
    boolean allowRequest();

    boolean isOpen();

    void markSuccess();

	...
}

这三个方法的作用如下:

在这里插入图片描述

HystrixCircuitBreakerImpl是HystrixCircuitBreaker的实现类。HystrixCircuitBreakerImpl的 allowRequest() 方法首先会调用 this.isOpen() 方法判断断路器是否打开,如果是闭合状态,那么请求能正常通行;如果是打开状态,那么调用 this.allowSingleTest() 判断是否是半开状态,如果是半开状态,那么也允许当前请求通行;否则当前请求就不能走正常的逻辑了。

        public boolean allowRequest() {
            ...
            return !this.isOpen() || this.allowSingleTest();
        }

在这里插入图片描述

HystrixCircuitBreakerImpl的isOpen() 方法会判断HystrixCircuitBreakerImpl的断路器开关状态AtomicBoolean circuitOpen是否是true(打开),如果是那么返回true表示断路器打开;否则根据断路器根据度量指标进行统计计算,判断QPS是否达到阈值(默认20)并且错误率是否达到阈值(默认50%),如果是则切换断路器状态为打开(circuitOpen修改为true)并记录打开时的时间戳,否则返回false。

在这里插入图片描述

HystrixCircuitBreakerImpl的allowSingleTest()方法则是根据记录的最后一次打开时的时间戳判断断路器打开是否已经超过预设的时间。如果是,那么就允许这一次请求通行,这就是所谓的半开状态。

在这里插入图片描述

最后,还有一个静态内部工厂类 HystrixCircuitBreaker.Factory,用于创建断路器HystrixCircuitBreaker,并通过一个 ConcurrentHashMap<String, HystrixCircuitBreaker> 记录每个被@HystrixCommand注解修饰的接口与对应的断路器的映射关系。

HystrixCircuitBreaker.Factory的getInstance(key)方法用于创建HystrixCircuitBreaker,如果ConcurrentHashMap中已有对应的HystrixCircuitBreaker创建好,那么返回它;否则创建一个并缓存到ConcurrentHashMap中,然后返回。

在这里插入图片描述

标签:Hystrix,SpringCloud,接口,剖析,断路器,线程,方法,HystrixCommand
From: https://blog.csdn.net/weixin_43889578/article/details/137409692

相关文章

  • 骨传导耳机哪个品牌好?五大骨传导耳机开箱真实剖析推荐 !
    随着科技的不断进步,人们对健康生活方式的追求也在不断提升。在这样的背景下,骨传导耳机作为一种创新的音频设备,因其独特的听音方式和对听力健康的潜在益处而逐渐受到消费者的青睐。不同于传统的入耳式或头戴式耳机,骨传导耳机通过颞骨将声音直接传递至内耳,避免了耳道堵塞可能带来......
  • SpringCloud
    1.学习前言1.1学习前提熟练使用SpringBoot微服务快速开发框架了解过Dubbo+Zookeeper分布式基础电脑配置内存不低于8G(我自己的是16G)1.2文章大纲SpringCloud五大组件服务注册与发现——NetflixEureka负载均衡:客户端负载均衡——NetflixRibbon服务端负......
  • 十二、【智能体】深入剖析:大模型节点的全面解读,举例说明,教你如何在扣子中嵌入代码
    大模型节点大模型节点主要分为5部分:处理类型单次批处理模型类型:目前可以选择的模型有豆包、通义千问、智谱、MinMax和Kimi输入:此时的参数可以被下面的提示词所用提示词:给大模型使用的提示词输出:经过此大模型处理后的输出提示词的使用提示词是核心,他关系到大模型......
  • SpringCloud Alibaba-03 Sentinel服务熔断与限流
    与CircuitBreaker功能类似后台默认8719端口前台默认8080端口在jar包所在目录下打开CMD,执行java-jarsentinel-dashboard-1.8.6.jar命令即可网址http://localhost:8080账号密码都是sentinel1.Sentinel微服务8401纳入Sentinel监控1.1建Module,改pom<depen......
  • Springcloud家政服务系统-计算机设计毕业源码87907
    目 录摘要1绪论1.1研究背景1.2 研究意义1.3论文结构与章节安排2 系统分析2.1可行性分析2.2系统流程分析2.2.1数据新增流程2.2.2数据修改流程2.2.3数据删除流程2.3 系统功能分析2.3.1功能性分析2.3.1非功能性分析62.4 系统用例分......
  • Springcloud家政服务系统-计算机设计毕业源码87907
    目 录摘要1绪论1.1研究背景1.2 研究意义1.3论文结构与章节安排2 系统分析2.1可行性分析2.2系统流程分析2.2.1数据新增流程2.2.2数据修改流程2.2.3数据删除流程2.3 系统功能分析2.3.1功能性分析2.3.1非功能性分析62.4 系统用例分......
  • 【算法笔记】前缀和算法原理深度剖析(超全详细版)
    【算法笔记】前缀和算法原理深度剖析(超全详细版)......
  • 【C++指南】类和对象(四):类的默认成员函数——全面剖析 : 拷贝构造函数
     引言拷贝构造函数是C++中一个重要的特性,它允许一个对象通过另一个已创建好的同类型对象来初始化。了解拷贝构造函数的概念、作用、特点、规则、默认行为以及如何自定义实现,对于编写健壮和高效的C++程序至关重要。 C++类和对象系列文章,可点击下方链接阅读:【C++指南......
  • 剖析Air724UG的硬件设计,有大发现?02篇
    ​ 3.8I2C管脚名类型序号电压域描述I2C2_SCLIO32V_GLOBAL_1V8I2C2 时钟信号,用作I2C 时需外加1.8V 上拉I2C2_SDAIO31V_GLOBAL_1V8I2C2 数据信号,用作I2C 时需外加1.8V 上拉CAMI2C_SCL1IO51V_GLOBAL......
  • 剖析Air724UG的硬件设计,有大发现?04篇
    ​ 接下来分享第四部分。 5.4功耗5.4.1模块工作电流测试仪器:综测仪R&S CMW500,程控电源安捷伦66319D测试条件:VBAT=3.8V,环境温度25℃,插入白卡,连接综测仪CMW500参数测试条件最小典型最大单位               ......