Hystrix底层核心原理

Hystrix是一个用于处理分布式系统的延迟和容错的库,主要通过线程池隔离和信号量隔离实现服务保护。线程池隔离适合处理延迟较大的服务,而信号量隔离适用于限制并发量。Hystrix使用Command模式对依赖进行封装,并通过断路器机制防止服务雪崩。此外,还介绍了Hystrix的工作流程,包括构造Command对象、执行命令、缓存检查、断路器状态判断等步骤。
摘要由CSDN通过智能技术生成

1、Hystrix资源隔离技术

hystrix github 官方文档:Home · Netflix/Hystrix Wiki · GitHub

hystrix可以完成隔离、限流、熔断、降级这些常用保护功能。

hystrix的隔离分为线程池隔离和信号量隔离

1.1、信号量隔离

  • 信号量隔离就是hystrix的限流功能。虽然名字叫隔离,实际上它是通过信号量来实现的,而信号量说白了就是个计数器,当计数器计算达到设定的阈值,直接就做异常处理。
  • 而hystrix信号量隔离限制的是tomcat等Web容器线程数,一段时间仅仅能支持这么多,多余的请求再来请求线程资源,就被拒绝了。由于实际是通过隔离了部分容器线程资源,也算是一种隔离方式
  • 信号量隔离是同步的,所以不支持计算超时时间(需要自己手动实现)
  • 信号量隔离只是起了个限制作用,它的保护能力有限,如果下游服务有问题,长时间不返回结果,那也只能等着。因为本身信号量隔离对单个请求是起不到任何作用的,它只能限制请求过多问题(请求过多则拒绝,不让整个服务挂)

为了解决这个问题,hystrix又产生了线程池隔离。这种隔离方式是通过引入额外线程(这里的额外是相对于Tomcat的线程来说的,引入额外线程会造成额外开销)的方式。

1.2、线程池隔离

Hystrix采用额外的线程来对原来的web容器线程做管理控制,如果一个线程超时未返回,则熔断。既然引入额外的线程就涉及到线程池管理、线程的上下文切换这些额外的开销,所以相比信号量隔离,线程池隔离成本更高。

Hystrix可以为每一个依赖建立一个线程池,使之和其他依赖的使用资源隔离,同时限制他们的并发访问和阻塞扩张。每个依赖可以根据权重分配资源(这里主要是线程),每一部分的依赖出现了问题,也不会影响其他依赖的使用资源。

1

  • Hystrix使用Command模式对依赖调用进行封装,每一个Command就是一个上面所说的依赖,Command可以指定CommandKey
  • Command Group可以把一组Command归为一组,默认情况下,Hystrix会为每一个Command Group建立一个线程池
  • 每一个线程池都有一个Key,这个线程池就是线程隔离的关键,所有的监控、缓存、调用等等都来自于这个线程池,默认的Thread Pool key就是command group名称
  • 默认情况下,每一个Command Group会自动创建一个线程池,尽管如此,还是会有这样的情况:当有一些依赖在一个Command Group中,但是又有隔离的必要的时候(比如一个依赖的超时会用满所有的线程池线程,这样就会影响到其他的依赖),可以为组里的Command指定Thread Pool Key

1.3、Command模式

Command模式:Hystrix中大量使用rxjava来实现Command模式。所有自定义的Command,不管继承于HystrixObservableCommand还是HystrixCommand,最终都继承于AbstractCommand。

关于Command的初始化过程,后面会通过源码讲到。

1.4、如何选择隔离策略

信号量隔离:

  • 对于那些本来延迟就比较小的请求来说,线程池带来的开销是非常高的,因此用信号量隔离更好
  • 适用于不是对外部依赖的访问,因为对外部依赖的访问时长难以控制
  • 针对超大并发量的场景下,此时用线程池的话,可能撑不住那么高的并发,如果硬撑住,可能要耗费大量的线程资源,那么就用信号量,来进行限流保护

2、Hystrix 工作原理

2.1、流程

2.1.1、构造HystrixCommand或HystrixObservableCommand对象

返回单个响应,则:

HystrixCommand command = new HystrixCommand(arg1, arg2);

 
 

返回一个发出响应的可观测对象:

HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);

 
 

2.1.2、执行Command

有四种方式:

  • execute():阻塞,返回单个响应(或者在错误的情况下抛出一个异常)
  • queue():返回一个包含单个响应的Future
  • observe():订阅可返回响应的可观察对象,并且返回一个Observable(复制源Observable)
  • toObservable():返回一个可观察对象,当你订阅它时,它将执行Hystrix命令并发出它的响应

注意:

第一种方式和第二种方式只适用于HystrixCommand,不适用于HystrixObservableCommand

execute()实际上是调用queue().get()。而queue()实际上是调用toObservable(). toblocking (). tofuture()。也就是说,最终每个HystrixCommand都有一个可观察的实现支持,即使是那些打算返回单个简单值的命令


 
 
  1. K value = command.execute();
  2. Future<K> fValue = command.queue();
  3. Observable<K> ohValue = command.observe(); //热observable
  4. Observable<K> ocValue = command.toObservable(); //冷observable

2.1.3、是否缓存了响应

如果这个命令启用了请求缓存,并且缓存中有对请求的响应,那么这个缓存的响应将立即以可观察对象的形式返回

请求缓存位于线程执行之前,即construct()或run()方法调用的前面

如果Hystrix没有实现请求缓存,则我们需要在construct()或run()方法里面手动实现请求缓存,即线程执行之后

2.1.4、断路器是否打开

当你执行命令时,Hystrix检查断路器,看看电路是否打开。如果电路打开(或触发),那么Hystrix将不会执行命令,但会将流路由到(8)获得回退。

如果电路关闭,则流继续流到(5),以检查是否有可用的容量来运行命令。

2.1.5、线程池、队列、信号量容量是否饱满

如果使用线程池隔离,则检查线程池、队列容量是否饱满

如果使用信号量隔离,则检查信号量是否饱满

2.1.6、执行请求

  • HystrixCommand.run() — 返回单个响应或者抛出异常,然后发出一个onCompleted通知
  • HystrixObservableCommand.construct() —返回一个可发出响应的可观测对象或者发出一个onError的事件

2.1.7、计算电路是否健康

Hystrix向断路器报告成功、失败、拒绝和超时,断路器维护一组滚动计数器来计算统计数据。

它使用这些统计数据来确定电路何时应该“跳闸”,在这一点上它会短路任何后续请求,直到恢复期结束,在恢复期结束后,它会在第一次检查后再次关闭电路。

2.1.8、获取回退方法

  • 在HystrixCommand的情况下,为了提供回退逻辑,你需要实现HystrixCommand. getfallback(),它返回一个回退值
  • 在HystrixObservableCommand的情况下,要提供回退逻辑,你需要实现HystrixObservableCommand. resumewithfallback(),它返回一个可以发出回退值的可观察对象
  • 如果没有为Hystrix命令实现回退方法,或者回退本身抛出异常,那么Hystrix仍然会返回一个可观察对象,但它不发出任何东西,并立即以onError通知结束

如果没有实现回退,则不同的执行方法会有不同的现象:

  • execute():抛出异常
  • queue():会成功返回Future,但是如果调用了它的get()方法,Future将抛出异常
  • observe() :返回一个可观察对象,当你订阅它时,它将通过调用订阅者的onError方法立即终止
  • toObservable() :返回一个可观察对象,当你订阅它时,它将通过调用订阅者的onError方法来终止

2.1.9、返回成功的响应

3、Hystrix API


 
 
  1. <dependency>
  2. <groupId>com.netflix.hystrix </groupId>
  3. <artifactId>hystrix-core </artifactId>
  4. <version>1.5.18 </version>
  5. </dependency>

因为HystrixCommand是一个抽象类,所以我们需要继承它并重写run方法


 
 
  1. import com.netflix.hystrix.HystrixCommand;
  2. import com.netflix.hystrix.HystrixCommandGroupKey;
  3. public class CommandHelloWorld extends HystrixCommand<String> {
  4. private final String name;
  5. public CommandHelloWorld (String name) {
  6. super(HystrixCommandGroupKey.Factory.asKey( "HelloGroup"));
  7. this.name = name;
  8. }
  9. @Override
  10. protected String run () {
  11. return "Hello " + name + "!";
  12. }
  13. public static void main (String[] args) {
  14. String s = new CommandHelloWorld( "bobo").execute();
  15. System.out.println(s);
  16. }
  17. }

更多API使用方法请戳这里:How To Use · Netflix/Hystrix Wiki · GitHub

4、源码解读

关于HystrixCommand,包括HystrixObservableCommand,都是继承AbstractCommand的,而AbstractCommand有一个最核心的构造方法,该构造方法包含了该command的所有配置


 
 
  1. protected AbstractCommand (HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool,
  2. HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults,
  3. HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore,
  4. HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) {
  5. this.commandGroup = initGroupKey(group);
  6. this.commandKey = initCommandKey(key, getClass());
  7. this.properties = initCommandProperties( this.commandKey, propertiesStrategy, commandPropertiesDefaults);
  8. this.threadPoolKey = initThreadPoolKey(threadPoolKey, this.commandGroup, this.properties.executionIsolationThreadPoolKeyOverride().get());
  9. this.metrics = initMetrics(metrics, this.commandGroup, this.threadPoolKey, this.commandKey, this.properties);
  10. this.circuitBreaker = initCircuitBreaker( this.properties.circuitBreakerEnabled().get(), circuitBreaker, this.commandGroup, this.commandKey, this.properties, this.metrics);
  11. this.threadPool = initThreadPool(threadPool, this.threadPoolKey, threadPoolPropertiesDefaults);
  12. //Strategies from plugins
  13. this.eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
  14. this.concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
  15. HystrixMetricsPublisherFactory.createOrRetrievePublisherForCommand( this.commandKey, this.commandGroup, this.metrics, this.circuitBreaker, this.properties);
  16. this.executionHook = initExecutionHook(executionHook);
  17. this.requestCache = HystrixRequestCache.getInstance( this.commandKey, this.concurrencyStrategy);
  18. this.currentRequestLog = initRequestLog( this.properties.requestLogEnabled().get(), this.concurrencyStrategy);
  19. /* fallback semaphore override if applicable */
  20. this.fallbackSemaphoreOverride = fallbackSemaphore;
  21. /* execution semaphore override if applicable */
  22. this.executionSemaphoreOverride = executionSemaphore;
  23. }

而前面说到,Thread Pool,Command Group,Command Key都在AbstractCommand这里实现

而正好上面的代码中,其中有一行是这样的:

this.threadPool = initThreadPool(threadPool, this.threadPoolKey, threadPoolPropertiesDefaults);
 
 

这一行代码表示加载线程池,点进去看看

再点进去看看

点进去HystrixThreadPoolDefault的构造方法,看看做了什么

再点进去看看,就能看到最终创建线程池的代码了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wis57

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值