java_微服务08SpringCloud常见问题之Hystrix

我们知道,Eureka进行服务的注册和发现,使用Ribbon实现服务的负载均衡调用,还知道了使用Feign可以简化我们的编码。但是,这些还不足以实现一个高可用的微服务架构。
例如:当有一个服务出现了故障,而服务的调用方不知道服务出现故障,若此时调用放的请求不断的增加,最后就会等待出现故障的依赖方 相应形成任务的积压,最终导致自身服务的瘫痪。
Spring Cloud Hystrix正是为了解决这种情况的,防止对某一故障服务持续进行访问。Hystrix的含义是:断路器,断路器本身是一种开关装置,用于我们家庭的电路保护,防止电流的过载,当线路中有电器发生短路的时候,断路器能够及时切换故障的电器,防止发生过载、发热甚至起火等严重后果

Hystrix容错机制:
包裹请求:使用HystrixCommand包裹对依赖的调用逻辑,每个命令在独立线程中执行,这是用到了设计模式“命令模式”。
跳闸机制:当某服务的错误率超过一定阈值时,Hystrix可以自动或手动跳闸,停止请求该服务一段时间。
资源隔离:Hystrix为每个依赖都维护了一个小型的线程池,如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速判定失败。
监控:Hystrix可以近乎实时的监控运行指标和配置的变化。如成功、失败、超时、被拒绝的请求等。
回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可自定义。
自我修复:断路器打开一段时间后,会自动进入半开状态,断路器打开、关闭、半开的逻辑转换。

Ribbon/Feign整合Hystrix后首次请求失败

在某些场景下,Feign或Ribbon整合Hystrix后,会出现首次调用失败的问题
原因:Hystrix默认的超时时间是一秒,如果在1秒内得不到响应,就会进入fallback逻辑。由于Spring的懒加载机制,首次请求往往会比较慢,因此在某些机器(特别是配置低的机器)上,首次请求需要的时间可能就会大于1秒。
解决办法:(一)延长Hystrix的超时时间 (二):禁用Hystrix的超时

feign调用远程服务,并发数量达到一定时会出直接触发hystrix fallbanck方法,原因为hystrix线程池配置问题。

实战测试:https://www.cnblogs.com/seifon/p/9921774.html
参数详解:https://www.cnblogs.com/huangjuncong/p/9043844.html
修改coreSize,maxQueueSize,queueSizeRejectionThreshold(主要)参数
Hystrix中threadPoolProperties线程池各个属性举例测试:https://blog.csdn.net/nb7474/article/details/84440822

  1. 设置coresize=2,maximumSize=2,未定义降级方法
    发送三个请求:两个请求成功,第三个请求返回错误500 null。
  2. 设置coresize=2,maximumSize=2,定义降级方法:返回降级信息
    发送三个请求:两个请求成功,第三个请求返回降级信息
  3. 设置coresize=2,maximumSize=4,未定义降级方法
    三个请求是一起返回过来的。
    那这就说明:请求数小于最大线程数,却大于核心线程数的时候,会一起处理所有的请求,当所有请求处理完毕的时候,会将多余核心数量的线程释放。
  4. 设置coresize=2,maxQueueSize=2,maximumSize=4,未定义降级方法
    果然,有两个请求先返回回来,还有一个请求在5秒后返回。

测试结果总结

1
2
3
4
01,如果请求量少,达不到 coreSize,通常会使用核心线程来执行任务。  
02,如果设置了 maxQueueSize,当请求数超过了 coreSize, 通常会把请求放到 queue 里,待核心线程有空闲时消费。
03,如果 queue 长度无法存储请求,则会创建新线程执行直到达到 maximumSize 最大线程数,多出核心线程数的线程会在空闲时回收。
所以正确的配置方式是根据显示场景需要进行设置:coresize<maxQueueSize<maximumSize

需要注意的是threadPoolProperties还有两个属性:
queueSizeRejectionThreshold:由于 maxQueueSize 值在线程池被创建后就固定了大小,如果需要动态修改队列长度的话可以设置此值,即使队列未满,队列内作业达到此值时同样会拒绝请求。此值默认是 5,所以有时候只设置了 maxQueueSize 也不会起作用。
keepAliveTimeMinutes:由上面的 maximumSize,我们知道,线程池内核心线程数目都在忙碌,再有新的请求到达时,线程池容量可以被扩充为到最大数量,等到线程池空闲后,多于核心数量的线程还会被回收,此值指定了线程被回收前的存活时间,默认为 2,即两分钟。
在实际的使用过程中,还应该考虑最大超时时间timeoutInMilliseconds与keepAliveTimeMinutes属性的配置,一般线程被回收前的存活时间应该小于最大超时时间,即在请求时间超出超时时间之前,线程应该都处于存活,并处理完所有的请求。

1
2
3
coreSize: 200 #并发执行的最大线程数,默认10
maxQueueSize: 1000 #BlockingQueue的最大队列数,默认值-1
queueSizeRejectionThreshold: 800 #即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝,默认值5

Feign调用时Ribbon+Hystrix请求超时问题

SpringCloud Alibaba 微服务架构(十)- Feign调用时Ribbon+Hystrix请求超时问题(典型配置和超时状况):https://thinkingcao.blog.csdn.net/article/details/105860283

1.使用Feign调用接口分为两层,Ribbon的调用和Hystrix的调用,所以Ribbon的超时时间和Hystrix的超时时间的结合就是Feign的超时时间。
2.一般情况下 都是 Ribbon 的超时时间(<)Hystrix的超时时间(因为涉及到Ribbon 的重试机制),如果Ribbon 的超时时间大于Hystrix的超时时间,对于Ribbon 的重试是没有意义的(Hystrix超时熔断了,Ribbon 无法重试)。
提示:
默认情况下,如果Ribbon没有配置重试时间和次数,默认是1S超时,默认会自动重试1次。
默认情况下,如果Hystrix没有配置重试时间和次数,默认是1S超时,默认会自动重试1次。
Feign中的Hystrix默认是未开启的,
开启Hystrix断路器功能之后就需要对Ribbon进行超时设置了,两者在碰到Feign时,需要协同作战,或者更粗暴点直接不开启Hystrix断路器功能,但是这种方式极不推荐,高并发的项目中服务保护必不可少,所以我们需要将Ribbon与Hystrix结合使用。

Hystrix的请求合并

Spring Cloud Hystrix的请求合并

1
2
3
@HystrixCollapser(batchMethod = "findAll", collapserProperties = {
@HystrixProperty(name="timerDelayInMilliseconds", value = "100")
})

请求合并的额外开销
虽然通过请求合并可以减少请求的数量以缓解依赖服务线程池的资源,但是在使用的时候也需要注意它所带来的额外开销:用于请求合并的延迟时间窗会使得依赖服务的请求延迟增高。比如:某个请求在不通过请求合并器访问的平均耗时为5ms,请求合并的延迟时间窗为10ms(默认值),那么当该请求的设置了请求合并器之后,最坏情况下(在延迟时间窗结束时才发起请求)该请求需要15ms才能完成。
由于请求合并器的延迟时间窗会带来额外开销,所以我们是否使用请求合并器需要根据依赖服务调用的实际情况来选择,主要考虑下面两个方面:
请求命令本身的延迟。如果依赖服务的请求命令本身是一个高延迟的命令,那么可以使用请求合并器,因为延迟时间窗的时间消耗就显得莫不足道了。
延迟时间窗内的并发量。如果一个时间窗内只有1-2个请求,那么这样的依赖服务不适合使用请求合并器,这种情况下不但不能提升系统性能,反而会成为系统瓶颈,因为每个请求都需要多消耗一个时间窗才响应。相反,如果一个时间窗内具有很高的并发量,并且服务提供方也实现了批量处理接口,那么使用请求合并器可以有效的减少网络连接数量并极大地提升系统吞吐量,此时延迟时间窗所增加的消耗就可以忽略不计了。

Hystrix 线程隔离导致ThreadLocal数据丢失

Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失
InheritableThreadLocal这个类继承了ThreadLocal,重写了3个方法,在当前线程上创建一个新的线程实例Thread时,会把这些线程变量从当前线程传递给新的线程实例。

阿里开源的transmittable-thread-local。GitHub地址:https://github.com/alibaba/transmittable-thread-local
主要功能就是解决在使用线程池等会缓存线程的组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。
JDK的InheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会缓存线程的组件的情况,线程由线程池创建好,并且线程是缓存起来反复使用的;这时父子线程关系的ThreadLocal值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal值传递到任务执行时
transmittable-thread-local使用方式分为三种,修饰Runnable和Callable,修饰线程池,Java Agent来修饰JDK线程池实现类

Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失(续)
改造线程方式
改造线程池方式
Hystrix默认提供了HystrixPlugins类,可以让用户自定义线程池,

dashbord信息解析


什么情况下会触发fallback方法?

名字描述触发fallback
EMIT值传递NO
SUCCESS执行完成,没有错误NO
FAILURE执行抛出异常YES
TIMEOUT执行开始,但没有在允许的时间内完成YES
BAD_REQUEST执行抛出HystrixBadRequestExceptionNO
SHORT_CIRCUITED断路器打开,不尝试执行YES
THREAD_POOL_REJECTED线程池拒绝,不尝试执行YES
SEMAPHORE_REJECTED信号量拒绝,不尝试执行YES

fallback方法在什么情况下会抛出异常

名字描述抛异常
FALLBACK_EMITFallback值传递NO
FALLBACK_SUCCESSFallback执行完成,没有错误NO
FALLBACK_FAILUREFallback执行抛出出错YES
FALLBACK_REJECTEDFallback信号量拒绝,不尝试执行YES
FALLBACK_MISSING没有Fallback实例YES

Hystrix 信号量和线程池隔离的差异

信号量线程池隔离差异
信号量隔离适应非网络请求,因为是同步的请求,无法支持超时,只能依靠协议本身
线程池隔离,即,每个实例都增加个线程池进行隔离

线程池隔离信号量隔离
是否支持熔断支持,当线程池到达MaxSize后,再请求会触发fallback接口进行熔断
是否支持超时支持,可直接返回
隔离原理每个服务单独用线程池
是否支持异步调用可以是异步,也可以是同步。看调用的方法
资源消耗大,大量线程的上下文切换,容易造成机器负载高

网关是通过线程池隔离,同步的路由方式,适当的线程池大小配置能够防止网关负载过大

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×