Spring Cloud教程 第五弹 Hystrix熔断器原理篇

写在前面的话

读者您好!更多Spring与微服务相关的教程请戳这里 火力全开系列 Spring与微服务教程合集 持续更新

另外,最近我要经营我个人的微信公众号了,以后CSDN和公众号两者兼顾,希望小伙伴们能够关注一下,为我涨涨人气,我需要大家的支持,这也是我持续不懈写文章的动力。

可以扫码关注,或者微信直接搜索“波波Tea”,带哪吒头像的那个就是我,谢谢!

Spring Cloud教程 第五弹 Hystrix熔断器原理篇

 

1、RxJava

参考:https://www.jianshu.com/p/a406b94f3188

github源码链接:https://github.com/ReactiveX/RxJava/

 

1.1、概述

Rx是Reactive Extensions的缩写,定义为一个异步和基于事件的函数库。

RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM(译文:RxJava 是一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库

  • 关于版本:

       Rxjava 2.0只是在Rxjava 1.0上增加了一些新特性,本质原理 & 使用基本相同

  • 特点:

       由于 RxJava的使用方式是:基于事件流的链式调用,所以使得 RxJava:

       逻辑简洁

       实现优雅

       使用简单

更重要的是,随着程序逻辑的复杂性提高,它依然能够保持简洁 & 优雅

 

1.2、原理

1.2.1、生活例子引入

 

顾客到饭店吃饭

 

示意图

Spring Cloud教程 第五弹 Hystrix熔断器原理篇

流程图

Spring Cloud教程 第五弹 Hystrix熔断器原理篇

 

1.2.2、RxJava原理介绍

  • Rxjava原理基于 一种扩展的观察者模式

  • Rxjava的扩展观察者模式中有4个角色:

角色 作用 类比
被观察者(Observable) 产生事件 顾客
观察者(Observer) 接收事件,并给出响应动作 厨房
订阅(Subscribe) 连接 被观察者 & 观察者 服务员
事件(Event) 被观察者 & 观察者 沟通的载体 菜式

 

  • 具体原理

示意图

Spring Cloud教程 第五弹 Hystrix熔断器原理篇

 

流程图

Spring Cloud教程 第五弹 Hystrix熔断器原理篇

 

RxJava原理可总结为:被观察者 (Observable) 通过 订阅(Subscribe) 按顺序发送事件 给观察者 (Observer), 观察者(Observer) 按顺序接收事件 & 作出对应的响应动作。具体如下图:

Spring Cloud教程 第五弹 Hystrix熔断器原理篇

 

 

1.3、基本使用

Rxjava的使用方式有两种:

  • 分步骤实现:该方法主要为了深入说明Rxjava的原理 & 使用,主要用于演示说明
  • 基于事件流的链式调用:主要用于实际使用

 

1.3.1、分步骤实现

Spring Cloud教程 第五弹 Hystrix熔断器原理篇

 

 

2、Hystrix资源隔离技术

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

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

 

2.1、信号量隔离

信号量隔离就是hystrix的限流功能。虽然名字叫隔离,实际上它是通过信号量来实现的,而信号量说白了就是个计数器,当计数器计算达到设定的阈值,直接就做异常处理。

而hystrix信号量隔离限制的是tomcat等Web容器线程数,一段时间仅仅能支持这么多,多余的请求再来请求线程资源,就被拒绝了。由于实际是通过隔离了部分容器线程资源,也算是一种隔离方式

信号量隔离是同步的,所以不支持计算超时时间(需要自己手动实现)

信号量隔离只是起了个限制作用,它的保护能力有限,如果下游服务有问题,长时间不返回结果,那也只能等着。因为本身信号量隔离对单个请求是起不到任何作用的,它只能限制请求过多问题(请求过多则拒绝,不让整个服务挂)

 

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

 

2.2、线程池隔离

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

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

Spring Cloud教程 第五弹 Hystrix熔断器原理篇

 

 

  • 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

 

2.3、Command模式

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

 

基本实现原理:

  • 线程池的创建和管理:Hystrix的线程池在HystrixConcurrencyStrategy初始化,线程池是由ThreadPoolExecutor实现的。每个线程池默认初始化10个线程。Hystrix有个静态类Factory,创建的线程池会被存储在Factory中的ConcurrentHashMap中
  • ConcurrentHashMap的Key则是上文说到的CommandGroupKey或者指定的ThreadPoolKey。每次命令执行的时候,都会根据ThreadPoolKey去找到对应的线程池
  • 线程池拥有一个继承于rxjava中Scheduler的HystrixContextScheduler,用于在执行命令的时候,把命令在这个线程池上调度执行。

 

命令的执行:

  • 以execute()方法为例,Hystrix通过toObservable()来构造命令,构造过程中,定义了整个命令执行过程中的stage(未开始、执行中、完成执行、执行异常等等)的回调和处理方法
  • 在executeCommandWithSpecifiedIsolation()方法中,使用exjava的subscribeOn方法,传入上文提到的HystrixContextScheduler对象,通过HystrixContextScheduler的ThreadPoolScheduler把命令submit到ThreadPoolExecutor中去执行

 

四种调用方式:

    同步:

  •         execute()
  •         toFuture().get()

    异步

  •         queue()
  •         toFuture()

 

2.4、如何选择隔离策略

 

信号量隔离:

  • 对于那些本来延迟就比较小的请求(例如访问本地缓存成功率很高的请求)来说,线程池带来的开销是非常高的
  • 适用于不是对外部依赖的访问,
  • 针对超大并发量的场景下,此时用线程池的话,可能撑不住那么高的并发,如果硬撑住,可能要耗费大量的线程资源,那么就用信号量,来进行限流保护