一、Hystrix简介
1.1、分布式系统面临的问题
复杂的分布式系统的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败。
对于高并发的系统,若某个依赖系统出问题会导致系统瞬时饱和,也会导致服务间的延迟增加,队列、线程和其他系统资源紧张,从而导致系统发生更多级联故障。
上述问题表示需要对系统的故障进行隔离,以达到单个依赖关系失败,不影响整个系统的目的。
1.2、Hystrix是什么
Hystrix是一个用于处理分布式系统的延迟和容错的开源库。
在分布式系统里,许多依赖不可避免的会调用失败,如超时、异常等,Hystrix能够保证在一个依赖出现问题的时候,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
1.3、Hystrix能干什么
Hystrix能干的事情主要有:
- 服务隔离、降级、熔断、限流、快速失败
- 请求合并、请求缓存
- 接近实时的监控
1.4、Hystrix的设计原则
- 防止任何单独的依赖使用容器(如Tomcat)所有的用户线程。
- 切断负载并快速失败,而不是排队。
- 尽可能提供回退以保护用户受故障。
- 使用隔离技术(如舱壁、泳道、断路器模式)来限制任何一个依赖的影响。
- 通过接近实时的指标,监控和报警,优化发现时间。
- 通过配置更改的低延迟传播,优化恢复时间。
- 防止各种客户端执行失败,而不仅仅是网络通信。
二、java项目直接使用Hystrix
2.1、加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
官方示例:https://github.com/Netflix/Hystrix/wiki/How-To-Use
2.2、基本使用(直接在java中引入jar)
2.2.1、HystrixCommand和HystrixObservableCommand
1、前者的命令逻辑写在run(); 后者的命令逻辑写在construct() 。
2、前者的run()是由新创建的线程执行; 后者的construct()是由调用程序线程执行。
3、前者一个实例只能向调用程序发送单条数据,比如上面例子中run()只能返回一个String结果; 后者一个实例可以顺序发送多条数据,可以顺序调用多个onNext(),便实现了向调用程序发送多条数据。
2.2.2、命令执行方法
execute(), queue(), observe(), toObservable() 这四个方法来触发执行 run()/construct(), 一个实例只能执行一次这4个方法,注意:HystrixObservableCommand没有execute()和queue()。
1、execute():以同步阻塞的方式执行run()。调用exectue()后,Hystrix先创建一个新线程运行run(),接着调用程序要在execute()调用处一致阻塞着,直到run()运行完成。
2、queue():以异步非阻塞方式执行run()。一调用queue(),就直接返回一个future对象,同时Hystrix创建一个新线程运行run(), 调用程序通过Future.get()拿到run()返回结果,而Future.get()是阻塞执行的。
3、observe():事件注册前执行run()/construct():
第一步是事件注册前,先调用observe()自动触发执行run()/construct():如果继承的时HystrixCommand, hystrix将创建新线程非阻塞执行run();如果继承的时HystrixObservableCOmmand,将以调用程序线程阻塞执行construct()。
第二部是从observe()返回迪欧澳洋程序调用subscribe()完成事件注册,如果run()/construct()执行成本,则触发onNext()和onCompleted(),如果执行异常则触发onError()。
4、toObservable():事件注册后执行run()/construct():
第一步是事件注册前,一调用toObservable()方法就直接返回一个Observable对象
第二部调用subscribe()完成事件注册后自动触发执行run()/construct(): 如果继承的时HystrixCommand, hystrix将创建新线程非阻塞执行run(), 调用程序不必等待run(); 如果继承的时HystrixObservableCommand, 将以调用程序线程阻塞执行construct(), 调用程序等待construct()执行完才能继续往下走,如果run()/construct()执行成功,则触发onNext()和onCompleted(), 如果执行异常则触发onError()。
2.2.3、命令名称
默认情况下,命令名是从类名派生的:getClass(), getSimpleName(): 要明确地定义名字,通过HystrixCommand或HystrixObservableCommand构造函数传入,例如:
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorld1"))
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(HelloWorldPool")));
1、GroupKey: 是HystrixCommand不可缺少的配置,其他配置均为可选,例如:
super(HystrixCommandGroupKey.Factory.asKey("HelloWorld1");
HystrixCommandGroupKey的作用有2个:
- 起到分组监控、报警的作用
- 是在不配置HystrixThreadPoolKey的情况下,起到分组线程池的作用。即默认使用HystrixCommandGroupKey去命名线程池。使用同一个HystrixCommandGroupKey且没有自定义HystrixThreadPoolKey的HystrixCommand将使用同一个线程池。
2、commandKey:命令的标识名称
3、ThreadPoolKey:线程池的标识名称。
2.2.4、Command Thread Pool 设置
虽然HystrixCommandGroupKey可以起到隔离线程池的作用,但无法起到对线程池进行精细配置的作用。这里就需要线程池进行配置,例如:
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorld1")) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("MyThreadPool")) .andThreadPoolPropertiesDefaults(
HystrixThreadPoolProperties.Setter() .withCoreSize(20) .withKeepAliveTimeMinutes(1)
.withMaxQueueSize(-1)
)
.andCommandPropertiesDefaults( HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(100)
) );
1、andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(“MyThreadPool”)) 这是配置ThreadPoolKey。如果需要在同一个GroupKey下面配置不同的ThreadPool,就需要这个配置。
2、andThreadPoolPropertiesDefaults 表示设置线程池默认的属性值,包括:
1)withCoreSize(20):用来配置线程池大小,不配置的话使用的默认值是10。
2)withKeepAliveTimeMinutes(1):用来配置核心线程数空闲时keep alive的时长,默认 1 mins,一般不需要修改。
3)withMaxQueueSize(-1):配置线程池任务队列的大小,默认值为-1,表示SynchronousQueue将被使用,即意味着其实这个队列只是一个交换器,任务将直接交给工作线程处理。如果工作线程不足,那任务将被拒绝;如果使用任务正整数,LinkedBlockingQueue将被使用。
2.2.5、使用注意事项
编写完自己的Command智慧,使用的时候,每次都需要new一个新对象,再调用execute()方法。注意:不要调用run()方法,否则熔断、隔离等功能是不生效的。
2.2.6、错误传播
run()里面抛出的HystrixBadRequestException只用于计数,方法抛出的所有其他异常都作为失败,会触发getFallBack()和断路器逻辑。
你可以包装你想要抛出的异常,HystrixBadRequestException适用的情况,如举报非法参数或非系统故障,不会计入失败的指标,不会触发回退逻辑。
2.2.7、快速失败
快速失败是指没有重写getFallBack,遇到异常后直接抛出,程序停止运行。
2.2.8、回退/降级
所谓降级,指在Hystrix执行非核心链路功能失败的情况下,我们如何处理,比如我们返回默认值等。触发时,会调用fallback设置降级方法,在降级方法中,可以设置默认的降级返回数据。
使用fallback机制很简单,继承HystrixCommand只需要重新getFallback(),继承HystrixObservableCommand只需要重新resumeWithFallback().
如下情况将会触发回退:
- 非HystrixBadRequestException异常
- run()/construct()异常
- 熔断器启动
- 线程池/信号量已满
2.2.9、请求缓存
Hystrix支持将一个请求结果缓存起来,下一个具有相同key的请求将直接从缓存中取出结果,减少请求开销。使用方式如下:
- 通过getCacheKey()在一个HystrixCommand或一个HystrixObservableCommand对象上实现该方法来启动请求缓存。
- 另外要求这多个请求必须在同一个上下文。通过HystrixRequestContext.initialContext()和context.shutdown()可以创建一个context,这两条语句间的所有请求都处于同一个context。
- 通过isResponseFromCache()可检测返回结果是否来自缓存。
2.2.10、合并请求
Hystrix支持n个请求自动合并为一个请求,这将是多次网络交互变为一次,极大节省开销。注意一点,两个请求能自动合并的前提是两者足够”近“,即两者启动执行的间隔时长要足够小,默认为10ms,即超过10ms将不自动合并。
Hystrix支持2中请求合并方式:请求范围和全局范围。这是在collapser构造中配置的,默认为request-scoped。请求范围是指在一个HystrixRequestContexts上下文中的请求;全局范围指跨HystrixRequestContexts的请求。
三、SpringCloud中使用Hystrix
3.1、加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
3.2、启动类上添加
@SpringBootApplication
@EnableHystrixDashboard
@EnableCircuitBreaker
@EnableHystrix
3.3、在Controller的方法上添加Hystrix配置
@HystrixCommand(fallbackMethod = "error", commandProperties = {
@HystrixProperty(name="execution.isolation.strategy", value = "THREAD"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "4000"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = ”50") }, threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "1"),
@HystrixProperty(name = "maxQueueSize", value = "10"),
@HystrixProperty(name = "keepAliveTimeMinutes", value = "1000"), @HystrixProperty(name = "queueSizeRejectionThreshold", value = "8"), @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"), @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1500")
})
Hystrix支持两种方式定义HystrixCommand,一种是将类继承自HystrixCommand类,并重写run方法;另一种是在方法头上写注解的方式,使用注解的方式代码会比较清晰,将Hystrix代码和业务代码隔离开。
3.4、Hystrix的Dashboard
3.4.1、概述
Hystrix自带了Dashboard,如果监控单个实例,可以很方便的通过Hystrix的dashboard进行查看运行情况,直接进入http://localhost:8080/hystrix
Hystrix Dashboard共支持三种不同的监控方式,依次为:
- 默认的集群监控:通过 http://turbine-hostname:port/turbine.stream开启,实现对默认集群的监控。
- 指定的集群监控:通过 http://turbine- hostname:port/turbine.stream?cluster=[clusterName] 开启,实现对clusterName集群的监控。
- 单体应用的监控:通过 http://hystrix-app:port/hystrix.stream 开启,实现对具体某个服务实例的监控。
前两者都对集群的监控,需要整合Turbine才能实现。
例子如下:
说明如下:
1、delay:该参数用来控制服务器上轮询监控信息的延迟时间,默认为2000毫秒,可以通过配置该属性来降低客户端的网络和CPU消耗。
2、title:该参数对应了头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的url,可以通过配置该信息来展示更合适的标题。
3、监控信息的左上部分找到两个重要的图形信息:一个实心圆和一条曲线:
- 实心圆:共有两种含义:它通过颜色的变化代表了实例的健康程度,它的监控度从绿色、黄色、橙色、红色递减。该实心圆出了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。
- 曲线:用来记录2分钟内的流量相对变化,可以通过它来观察到流量的上升和下降趋势。
3.5、Hystrix的参数配置
3.5.1、属性配置
Hystrix使用Netflix的配置管理库Archaius作为配置属性的默认实现。官方配置文档:https://github.com/Netflix/Hystrix/wiki/Configuration
每个属性有四个优先级,依次增大:
1、代码的全局默认值
2、动态全局默认属性:可以使用全局属性文件来更改全局默认属性
3、代码示例默认:定义特定于实例的默认值,比如在HystrixCommand构造函数中设置的值。
4、动态实例属性:可以动态设置实例特定的值,从而覆盖前面三个默认级别,格式是:hystrix.command.命令key.属性名称=值
3.5.2、请求上下文
1、requestCache.enabled:设置是否开启请求的缓存功能,默认为true
2、requestLog.enabled:设置是否开启请求的日志功能,默认为true
3.5.3、命令执行
1、execution.isolation.strategy
指示HystrixCommand.run()执行哪个隔离策略,选项:
- THREAD:它在单独的线程上执行,并发请求受线程池中的线程数限制
- SEMAPHORE:它在调用线程上执行,并发请求受信号计数的限制
说明:
- 官方推荐使用线程隔离策略,默认也是按照线程隔离进行处理
- 信号量隔离的方式是限制了总的并发数,每一次请求过了,请求线程和调用依赖服务的线程是同一个线程,那么如果不设计远程RPC调用(没有网络开销)则使用信号量来隔离,更为轻量,开销更小。
- 信号量的大小可以动态调整,线程池的大小不可用动态调整
- 配置示例:
@HystrixCommand(fallbackMethod = "error", commandProperties = {
@HystrixProperty(name="execution.isolation.strategy", value = "THREAD") })
2、execution.isolation.thread.timeoutInMilliseconds
表示请求线程总超时时间,如果超过这个设置的时间,hystrix就会调用fallback方法。value为毫秒,默认为1000ms。
示例:
@HystrixCommand(fallbackMethod = "error",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
value = "4000") })
3、execution.timeout.enabled
表示当超时后是否会触发fallback方法,默认为true
4、execution.isolation.thread.interrupteOnTimeout
表示HystrixCommand.run()在发生超时时是否应该中断执行,默认为true。
5、execution.isolation.thread.interruptOnCance
表示HystrixCommand.run()在发生取消时,是否应该中断执行,默认为false
6、execution.isolation.semaphore.maxConcurrentRequests
当隔离策略使用semaphore时,最大的兵法请求量,如果请求超过这个最大值将拒绝后续的请求,默认值为10。
3.5.4、回退
1、fallback.isolation.semaphore.maxConcurrentRequests
设置HystrixCommand.getFallback()方法允许从调用线程进行请求的最大数量,默认10;如果达到最大并发限制,则随后的请求将被拒绝,并抛出异常。
2、fallback.enable
开启fallback功能,默认为true
3.5.5、断路器
1、circuitBreaker.enabled
设置是否将使用断路器来跟踪健康状况,并且如果断路器跳闸,则将其断路。默认为true。
2、circuitBreaker.requestVolumeThreshold
设置滚动窗口中将使电路跳闸的最小请求数量,默认为20.
熔断器在整个统计时间内是否开启的阈值,每个熔断器默认维护10个bucket,每秒一个bucket,每个bucket记录成功、失败、超时、拒绝的状态,该阈值默认为20次。也就是一个统计窗口时间内(10秒钟)至少请求20次,熔断器才会启动。
3、circuitBreaker.sleepWindowInMilliseconds
熔断器默认工作时间,默认为5秒,熔断器中断请求5秒后会进入半打开状态,放部分流量过去重试,如果重试成功则会恢复正常请求。
4、circuitBreaker.errorThresholdPercentage
熔断器错误百分比阈值,默认为50%。挡在一个时间窗口内出错率超过50%后,熔断器会自动启动。熔断器启动后会自动转发到配置的fallbackMethod,进行降级处理。
5、circuitBreaker.forceOpen
断路器强制开关,如果设置为true, 则表示强制打开熔断器,所有请求都会拒绝,默认为false
6、circuitBreaker.forceClose
断路器强制开关,如果设置为true, 则表示强制关闭熔断器,所有请求都会允许,默认false。
3.5.6、度量指标
1、metrics.rollingStats.timeInMilliseconds
设置统计滚动窗口的持续时间,以毫秒为单位,默认10秒。
2、metrics.rollingStats.numBuckets
该属性设置滚动统计窗口分词的桶的数量,默认10
3、metrics.rollingPercentile.enable
表示执行延迟是否应该跟踪和计算为百分比。如果被禁用,则所有汇总统计返回为-1。默认为true。
4、metrics.rollingPercentile.timeInMilliseconds
设置滚动窗口的持续时间,在该窗口中保留执行时间以允许百分数计算(单位毫秒),默认一分钟。
5、metrics.rollingPercentile.numBuckets
设置rollingPercentile窗口被分成的桶的数量,默认为6
6、metrics.rollingPercentile.bucketSize
设置每个存储桶的最大执行次数,如果在执行多次,将在桶的开头重写,默认100
例如,如果存储区大小设置为100,并表示10秒的存储区窗口,但在此期间发生500次执行,则只有最后100次执行将保留在该10秒存储区中。
7、metrics.healthSnapshot.intervalInMilliseconds
设置允许执行健康快照之间等待的时间(以毫秒为单位),默认500
3.5.7、ThreadPool配置
1、coreSize
线程池核心线程数,默认10
2、MAXIMUMSIZE
设置线程池大小,默认10
3、maxQueueSize
配置线程池任务队列的大小,默认值为-1。当使用-1时,SynchronousQueue将被使用,即意味着其实这个队列只是一个交换器,任务将被直接交给工作线程处理。如果工作线程不足,那任务将被拒绝;如果使用任何正整数,LinkedBlockingQueue将被使用。
4、queueSizeRejectionThreshold
表示等待队列超过阈值后,开始拒绝线程请求,默认值为5,如果maxQueueSize为-1,则该属性失效。
5、keepAliveTimeMinutes
设置活动保持时间,以分钟为单位,默认1分钟
6、allowMaxinumSizeToDivergeFromCoreSize
设置maxinumSize剩下。这个值可以等于或高于coreSize,默认为false。
四、Hystrix的工作流程
4.1、Hystrix的工作流程图
4.2、工作流程说明
1、创建一个HystrixCommand或HystrixObservableCommand实例来想其他组件发出操作请求,通过构造方法来创建实例。
2、执行命令
3、缓存判断:检查缓存内是否有对应指令的结果,如果有,则将缓存结果直接以Observable对象的形式返回。
4、断路器判断:检查Circuit Breaker的状态,如果状态为开启,Hystrix将不会执行对应指令,而是直接进入失败处理状态;如果状态为关闭,Hystrix会继续执行。
5、线程池、任务队列、信号量的检查:确认是否有足够资源执行操作指令。当线程池和队列(或者信号量,当不使用线程池隔离模式的时候)资源满的时候,hystrix将不会执行对应指令,并且会直接进入失败的处理状态。
6、HystrixObservableCommand.construct()和HystrixCommand.run():如果资源充足,Hystrix将会执行操作指令,调用最终都会到这两个方法:HystrixObservableCommand.construct()或HystrixCommand.run()。如果执行指令的时间超时,执行线程会抛出TimeoutException异常。Hystrix会抛弃结果,并进入失败处理状态。如果指令执行成本,Hystrix会进行一系列的数据记录,然后返回执行的结果。
7、统计断路器的健康情况:Hystrix会根据记录的数据来计算失败比率,一旦失败比率达到某一阈值,将自动开启Circuit Breaker。
8、降级:如果在Command中实现了HystrixCommand.getFallback()或HystrixObservableCommand.resumeWithFallback()方法,Hystrix会返回对应方法的结果。如果没有实现这些方法的话,仍然Hystrix会返回一个空的Observable对象,并且可以通过onError来终止并处理错误。
调用不同的方法返回不同的结果:
- execute():将会抛出异常
- queue():将会返回一个Future对象,如果调用它的get()方法将会抛出异常。
- observe()和toObservable():都会返回上述的Observable对象
9、返回成功:如果Hystrix执行成功,返回的响应取决于步骤2中调用命令。
execute():阻塞型方法,返回单个结果(或者抛出异常)
queue():异步方法,返回一个Future对象,可以从中取出单个结果(或者抛出异常)
observe():返回Observable对象
toObservable():返回Observable对象。
五、熔断机制
5.1、雪崩效应概述
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用了其他的微服务,这就是所谓的“扇出”。如果扇出的链路上,某个微服务的调用响应时间过长或不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,这就是所谓的“雪崩效应”。
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务不可用或相应时间太长,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。
5.2、熔断类型
在Hystrix里,熔断又分为三种情况:半熔断、熔断打开、熔断关闭。
1、熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟,则进入半熔断状态。
2、半熔断:部分请求根据规则调用当前服务,如果请求成功,且符合规则,则认为当前服务恢复正常,关闭熔断。
3、熔断关闭:熔断关闭不会对服务进行熔断。
5.3、断路器原理
5.3.1、断路器在上面情况下开始起作用
涉及到断路器的三个重要参数:快照时间窗、请求总数阈值、错误百分比阈值。
1、快照时间窗:段利器确定是否打开,需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认是最近10秒。
2、请求总数阈值:在快照时间窗内,必须满足请求总数阈值才有资格熔断。默认为20,意味着在10秒内,如果该Hystrix命令的调用次数不足20次,及时所有的请求都超时或其他原因失败,断路器都不会打开。
3、错误百分比一致:当请求综述在快照时间窗内超过了阈值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阈值情况下,这时就会将断路器打开。
5.3.2、断路器开启或关闭的条件
1、当满足一定阈值的时候(默认10秒内超过20个请求次数)
2、当失败率达到一定的时候(默认10秒内超过50%的请求失败)
3、到达以上阈值,断路器将会开启
4、当开启的时候,所有请求都不会进行转发
5、一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发:如果成功,断路器会关闭;若失败,继续开启,重复4和5步骤。
5.3.3、断路器打开之后
1、在用请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误,并将降级逻辑切换为主逻辑,减少响应延迟的效果。
2、原来的主逻辑要如何恢复呢?
对于这一问题,hystrix也为我们实现了自动恢复的功能。
当断路器打开,对主逻辑进行熔断后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
六、线程隔离机制
6.1、线程隔离示意图
6.2、依赖隔离机制
Hystrix提供了两种隔离策略:线程池隔离和信号量隔离,默认采用线程池隔离。
6.2.1、线程池隔离
Hystrix使用舱壁模式来实现线程池的隔离。他会为每一个Hystrix命令创建一个独立的线程池,不同服务通过使用不同线程池,彼此间将不受影响,这样就算某个Hystrix命令包装下的依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的服务。
这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗;通过线程池大小可以控制并发量,当线程池饱和时,可以提前拒绝服务,防止依赖问题扩散。建议线程池不要设置过大,否则大量阻塞线程有可能会拖慢服务器。
6.2.1.1、线程池隔离的好处
1、应用自身得到了完全的保护,不会受不可控的依赖服务影响。
2、可以有效的降低接入新服务的风险。
3、当依赖的服务从失效恢复正常后,它的线程池会被清理,并能够马上恢复健康的服务,相比之下,容器级别的清理恢复速度要慢得多。
4、当依赖的服务出现配置错误的时候,线程池会快速的反应出此问题(通过失败次数、延迟、超时、拒绝等指标的增减情况)。同时,可以在不影响应用功能的情况下,通过实时的动态属性刷新来处理它。
5、当依赖的服务因失血机制调整等原因,造成其性能出现很大变化时,此时线程池的监控指标信息会反映出这样的变化。同时,可以通过实时动态刷新自身应用对依赖服务的阈值进行调整以适应依赖方的改变。
6.2.2、信号量隔离
线程隔离会带来线程开销,有些成绩(比如无网络请求场景)可能会因为用开销换隔离得不偿失,为此hystrix提供了信号量隔离,当服务的并发数大于信号量阈值时,将进入fallback。
实现方式是使用一个原子计数器(或信号量)来记录当前有多少个线程在允许,请求过来了,先判断计数器的数值,若超过设置的最大线程个数,则丢弃该类型的新请求,若不超过,则执行技术操作请求来计数器+1。
信号隔离与线程隔离最大不同在于执行依赖代码的线程依然是请求线程;而线程池方式下业务请求线程和执行依赖的服务的线程,不是同一个线程。
重学Hystrix原理
一、背景
微服务架构下,多个服务经常相互依赖,一个系统故障,会导致依赖他的系统出现“雪崩”。
为了解决不同服务间依赖导致雪崩问题,就需要做“故障隔离”,即当你依赖的服务不可用是,你的服务能自动开启自我保护功能。这就是Hystrix的初衷。
二、Hystrix简介
Hystrix 中文含义是豪猪,因为背上长满刺,可以用自我保护的能力。
Hystrix是Netflix开源的容错框架,使服务有自我保护的能力。
Hystrix用来解决的问题:
- 自动发现依赖服务故障–提供近实时的监控
- 提供优雅降级机制
- 可快速失败
Hystrix设计的原则:
- 防止依赖的资源故障而hang住主线程从而导致资源耗尽
- 失败达到阈值后,用户请求直接拒绝
- 提供回退接口给使用方来处理依赖故障
- 故障恢复时,可快速发现并恢复
Hystrix如何实现的这些设计目标
- 使用命令模型,把所有对外部服务的依赖调用封装到了HystrixCommand或HystrixObservableCommand对象中,并把对象放到独立的线程中
- 每个依赖都单独有个线程池,若线程池资源耗尽,则拒绝请求
- 记录下请求成功、失败、超时、线程拒绝
- 当服务错误百分比超过阈值,熔断器开关自动打开,一段时间内停止对该服务的所有请求
- 当请求失败(包括超时、被熔断等),可执行降级逻辑
- 可近实时地配置监控指标
2.1、Hystrix的工作流程
1、构造一个HystrixCommand和HystrixObservableCommand对象,用于封装请求,并在构造方法配置请求被执行需要的参数
2、执行命令,Hystrix提供了4种执行命令的方法
3、判断是否使用缓存响应请求,如果用了缓存,且缓存可用,则直接使用缓存响应请求。Hystrix支持缓存请求,但需要用户自定义启动。-- 这个功能用的少
4、判断熔断器是否打开,若打开,则跳到第八步
5、判断线程池、队列、信号量是否已满,已满则跳到第八步
6、执行HystrixCommand.run()或HystrixObservalbeCommand.construct(), 如果执行失败或超时,跳到第八步;否则,跳到第九步
7、统计熔断器监控指标
8、走Fallback备用逻辑
9、返回请求响应。
从流程图上可知,第五步线程池、队列、信号量如已满,则会执行第七步逻辑,更新熔断器统计信息。
2.1.1、执行命令的4种方法
1、execute():相当于异步串行。
以同步阻塞的方式执行run(),支持接收一个值对象,hystrix会从线程池中取一个线程来执行,并等待返回值。
2、queue():
以异步非阻塞方式执行run(), 支持接收一个值对象,调用queue()直接返回一个Future对象,可通过Future.get()拿到run()的返回结果。
3、observe()
事件注册前执行run()/construct(),支持接收多个值对象,取决于发射源。调用observe()会自动触发run()/construct(), 无论是否存在订阅者
observe()的特点:
1)调用observe()会返回一个Observable对象
2)调用这个Observable对象的subscribe()方法完成事件注册,从而获取结果。
2.2、Hystrix容错
Hystrix主要提供下面的几种容错方法:
1)资源隔离
2)熔断
3)降级
2.2.1、资源隔离
资源隔离,主要指调用依赖服务的请求线程与请求主线程的隔离。Hystrix提供了两种线程隔离方式:线程池和信号量
线程隔离-- 线程池
Hystrix通过命令模式,把发送请求的线程,和调用依赖服务的线程,分成2个线程,并为每个依赖服务即每一种command创建一个线程池。这样就可以通过线程池,一是做异步,避免级联雪崩故障;二是通过线程池的饱和策略来做拒绝服务。
线程池的缺点:增加了计算开销,即新启动线程增加了排队、调度、上下文切换等开销
线程隔离 – 信号量
上面说了线程池的缺点,对于延迟极低(即响应速度快)的依赖服务调用时,线程池方式代理的开销超过了它带来的好处。所以这样的场景适合用信号量来隔离。
信号量隔离:发起请求的线程 和 调用依赖服务的线程 是同一个线程。
信号量的方式,当向依赖服务发起请求时,首先要获取一个信号量,然后才能发起请求,但信号量有限,当并发请求超过了信号量个数时,后续的请求都会被拒绝,进入fallback流程。
信号量隔离方式主要通过控制并发请求里,防止请求线程大面积阻塞。
信号量方式由于不必新开线程,开销小于线程池方式。
Hystrix默认是线程池方式做隔离。
2.2.2、熔断
熔断的比喻:就好比是保险丝,当电流达到阈值时,保险丝会断,保证家里电器不会被损坏。这就是熔断。
Hystrix的熔断器(Circuit Breaker)也是类似作用:Hystrix在运行过程中会向每个commandKey对应的熔断器报告成功、失败、超时、拒绝的状态,熔断器统计这些信息来决策是否触发熔断。
熔断打开后,后续请求会快速返回。然后隔一段时间(默认5s)后熔断器尝试半开,放入一部分流量尽量,相当于对依赖服务做健康检查,如果发现依赖服务恢复了,则关闭熔断器。
熔断器的工作原理:
熔断器的工作详细流程:
第一步:调用allowRequest()判断是否允许将请求发到线程池。即看熔断器是否强制打开或强制关闭:circuitBreaker.forceOpen
第二步:调用isOpen() 判断熔断器开关是否打开,如关闭则拒绝,如打开,则判断是否达到设置的阈值
第三步:调用allowSingleTest() 判断是否允许单个请求同行,检查依赖服务是否恢复
2.2.3、回退降级
Hystrix的降级,主要指当依赖服务故障时,保证当前服务仍可运行,从而提高当前服务的健壮性。
要支持回退或降级处理,可以重新HystrixCommand的getFallBack方法或HystrixObservableCommand的resumeWithFallback方法
Hystrix在以下几种情况下会走降级逻辑:
1)执行construct() 或 run() 抛出异常
2)熔断器打开
3)命令的线程池和队列或信号量的容量超阈值,命令被拒绝
4)命令执行超时
总之,回退策略,不单是指在熔断器开启后会执行,在调用依赖服务失败后都会执行。
降级回退的方式
1)Fail Fast 快速失败
即没有重新降级逻辑,直接抛出异常
2)Fail Silent 无声失败
指不抛出异常,而是在降级方法中返回null, 空Map,空List等
3)Fallback:Static
在降级方法中返回静态默认值
4)Fallback:Stubbed
当命令返回一个包含多个字段的复合对象时,适合以Stubbed方式回退
5)Fallback:Cache via Network
有时调用依赖服务失败,可以从缓存服务(如reids)中查询旧数据版本。由于又会发起远程调用,所以建议重新封装一个Command , 使用不同的ThreadPoolKey,与主线程池进行隔离。
6)Primary+Secondary with Fallback
有时系统有两种行为:主要和次要,或主要和故障转移
更多详见:https://blog.csdn.net/loushuiyifan/article/details/82702522