深入理解hystrix的短路器执行原理以及模拟接口异常时的短路实验

深入理解hystrix的短路器执行原理以及模拟接口异常时的短路实验

短路器深入的工作原理

1、如果经过短路器的流量超过了一定的阈值,HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()

举个例子,可能看起来是这样子的,要求在10s内,经过短路器的流量必须达到20个;在10s内,经过短路器的流量才10个,那么根本不会去判断要不要短路

2、如果断路器统计到的异常调用的占比超过了一定的阈值,HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()

如果达到了上面的要求,比如说在10s内,经过短路器的流量(你,只要执行一个command,这个请求就一定会经过短路器),达到了30个;同时其中异常的访问数量,占到了一定的比例,比如说60%的请求都是异常(报错,timeout,reject),会开启短路

3、然后断路器从close状态转换到open状态

4、断路器打开的时候,所有经过该断路器的请求全部被短路,不调用后端服务,直接走fallback降级

5、经过了一段时间之后,HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds(),会half-open,让一条请求经过短路器,看能不能正常调用。如果调用成功了,那么就自动恢复,转到close状态

短路器,会自动恢复的,half-open,半开状态

6、circuit breaker短路器的配置

(1)circuitBreaker.enabled

控制短路器是否允许工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发短路,默认是true

HystrixCommandProperties.Setter()
.withCircuitBreakerEnabled(boolean value)

(2)circuitBreaker.requestVolumeThreshold

设置一个rolling window,滑动窗口中,最少要有多少个请求时,才触发开启短路

举例来说,如果设置为20(默认值),那么在一个10秒的滑动窗口内,如果只有19个请求,即使这19个请求都是异常的,也是不会触发开启短路器的

HystrixCommandProperties.Setter()
.withCircuitBreakerRequestVolumeThreshold(int value)

(3)circuitBreaker.sleepWindowInMilliseconds

设置在短路之后,需要在多长时间内直接reject请求,然后在这段时间之后,再重新导holf-open状态,尝试允许请求通过以及自动恢复,默认值是5000毫秒

HystrixCommandProperties.Setter()
.withCircuitBreakerSleepWindowInMilliseconds(int value)

(4)circuitBreaker.errorThresholdPercentage

设置异常请求量的百分比,当异常请求达到这个百分比时,就触发打开短路器,默认是50,也就是50%

HystrixCommandProperties.Setter()
.withCircuitBreakerErrorThresholdPercentage(int value)

(5)circuitBreaker.forceOpen

如果设置为true的话,直接强迫打开短路器,相当于是手动短路了,手动降级,默认false

HystrixCommandProperties.Setter()
.withCircuitBreakerForceOpen(boolean value)

(6)circuitBreaker.forceClosed

如果设置为ture的话,直接强迫关闭短路器,相当于是手动停止短路了,手动升级,默认false

HystrixCommandProperties.Setter()
.withCircuitBreakerForceClosed(boolean value)

7、实战演练

配置一个断路器,流量要求是20,异常比例是50%,短路时间是5s

在command内加入一个判断,如果是productId=-1,那么就直接报错,触发异常执行

写一个client测试程序,写入50个请求,前20个是正常的,但是后30个是productId=-1,然后继续请求,会发现

深入理解线程池隔离技术的设计原则以及动手实战接口限流实验

1、command的创建和执行:资源隔离
2、request cache:请求缓存
3、fallback:优雅降级
4、circuit breaker:短路器,快速熔断(一旦后端服务故障,立刻熔断,阻止对其的访问)

把一个分布式系统中的某一个服务,打造成一个高可用的服务

资源隔离,优雅降级,熔断

5、判断,线程池或者信号量的容量是否已满,reject,限流

限流,限制对后端的服务的访问量,比如说你对mysql,redis,zookeeper,各种后端的中间件的资源,访问,其实为了避免过大的流浪打死后端的服务,线程池,信号量,限流

限制服务对后端的资源的访问

1、线程池隔离技术的设计原则

Hystrix采取了bulkhead舱壁隔离技术,来将外部依赖进行资源隔离,进而避免任何外部依赖的故障导致本服务崩溃

线程池隔离,学术名称:bulkhead,舱壁隔离

外部依赖的调用在单独的线程中执行,这样就能跟调用线程隔离开来,避免外部依赖调用timeout耗时过长,导致调用线程被卡死

Hystrix对每个外部依赖用一个单独的线程池,这样的话,如果对那个外部依赖调用延迟很严重,最多就是耗尽那个依赖自己的线程池而已,不会影响其他的依赖调用

Hystrix选择用线程池机制来进行资源隔离,要面对的场景如下:

(1)每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的
(2)每个后端依赖服务都会提供它自己的client调用库,比如说用thrift的话,就会提供对应的thrift依赖
(3)client调用库随时会变更
(4)client调用库随时可能会增加新的网络请求的逻辑
(5)client调用库可能会包含诸如自动重试,数据解析,内存中缓存等逻辑
(6)client调用库一般都对调用者来说是个黑盒,包括实现细节,网络访问,默认配置,等等
(7)在真实的生产环境中,经常会出现调用者,突然间惊讶的发现,client调用库发生了某些变化
(8)即使client调用库没有改变,依赖服务本身可能有会发生逻辑上的变化
(9)有些依赖的client调用库可能还会拉取其他的依赖库,而且可能那些依赖库配置的不正确
(10)大多数网络请求都是同步调用的
(11)调用失败和延迟,也有可能会发生在client调用库本身的代码中,不一定就是发生在网络请求中

简单来说,就是你必须默认client调用库就很不靠谱,而且随时可能各种变化,所以就要用强制隔离的方式来确保任何服务的故障不能影响当前服务

我不知道在学习这个课程的学员里,有多少人,真正参与过一些复杂的分布式系统的开发,不是说一个team,你们五六个人,七八个人,去做的

在一些大公司里,做一些复杂的项目的话,广告计费系统,特别复杂,可能涉及多个团队,总共三四十个人,五六十个人,一起去开发一个系统,每个团队负责一块儿

每个团队里的每个人,负责一个服务,或者几个服务,比较常见的大公司的复杂分布式系统项目的分工合作的一个流程

线程池机制的优点如下:

(1)任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源填满了,也不会影响任何其他的服务调用
(2)服务可以随时引入一个新的依赖服务,因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用
(3)当一个故障的依赖服务重新变好的时候,可以通过清理掉线程池,瞬间恢复该服务的调用,而如果是tomcat线程池被占满,再恢复就很麻烦
(4)如果一个client调用库配置有问题,线程池的健康状况随时会报告,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机
(5)如果一个服务本身发生了修改,需要重新调整配置,此时线程池的健康状况也可以随时发现,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机
(6)基于线程池的异步本质,可以在同步的调用之上,构建一层异步调用层

简单来说,最大的好处,就是资源隔离,确保说,任何一个依赖服务故障,不会拖垮当前的这个服务

线程池机制的缺点:

(1)线程池机制最大的缺点就是增加了cpu的开销

除了tomcat本身的调用线程之外,还有hystrix自己管理的线程池

(2)每个command的执行都依托一个独立的线程,会进行排队,调度,还有上下文切换
(3)Hystrix官方自己做了一个多线程异步带来的额外开销,通过对比多线程异步调用+同步调用得出,Netflix API每天通过hystrix执行10亿次调用,每个服务实例有40个以上的线程池,每个线程池有10个左右的线程
(4)最后发现说,用hystrix的额外开销,就是给请求带来了3ms左右的延时,最多延时在10ms以内,相比于可用性和稳定性的提升,这是可以接受的

我们可以用hystrix semaphore技术来实现对某个依赖服务的并发访问量的限制,而不是通过线程池/队列的大小来限制流量

sempahore技术可以用来限流和削峰,但是不能用来对调研延迟的服务进行timeout和隔离

execution.isolation.strategy,设置为SEMAPHORE,那么hystrix就会用semaphore机制来替代线程池机制,来对依赖服务的访问进行限流

如果通过semaphore调用的时候,底层的网络调用延迟很严重,那么是无法timeout的,只能一直block住

一旦请求数量超过了semephore限定的数量之后,就会立即开启限流

2、接口限流实验

假设,一个线程池,大小是15个,队列大小是10个,timeout时长设置的长一些,5s

模拟发送请求,然后写死代码,在command内部做一个sleep,比如每次sleep 1s,10个请求发送过去以后,直接被hang死,线程池占满

再发送请求,就会堵塞在缓冲队列,queue,10个,20个,10个,后10个应该就直接reject,fallback逻辑

15 + 10 = 25个请求,15在执行,10个缓冲在队列里了,剩下的流量全部被reject,限流,降级

withCoreSize:设置你的线程池的大小
withMaxQueueSize:设置的是你的等待队列,缓冲队列的大小
withQueueSizeRejectionThreshold:如果withMaxQueueSize<withQueueSizeRejectionThreshold,那么取的是withMaxQueueSize,反之,取得是withQueueSizeRejectionThreshold

线程池本身的大小,如果你不设置另外两个queue相关的参数,等待队列是关闭的

queue大小,等待队列的大小,timeout时长

先进去线程池的是10个请求,然后有8个请求进入等待队列,线程池里有空闲,等待队列中的请求如果还没有timeout,那么就进去线程池去执行

10 + 8 = 18个请求之外,7个请求,直接会被reject掉,限流,fallback

withExecutionTimeoutInMilliseconds(20000):timeout也设置大一些,否则如果请求放等待队列中时间太长了,直接就会timeout,等不到去线程池里执行了
withFallbackIsolationSemaphoreMaxConcurrentRequests(30):fallback,sempahore限流,30个,避免太多的请求同时调用fallback被拒绝访问

基于timeout机制来为商品服务接口的调用超时提供安全保护

一般来说,在调用依赖服务的接口的时候,比较常见的一个问题,就是超时

超时是在一个复杂的分布式系统中,导致不稳定,或者系统抖动,或者出现说大量超时,线程资源hang死,吞吐量大幅度下降,甚至服务崩溃

超时最大的一个问题

你去调用各种各样的依赖服务,特别是在大公司,你甚至都不认识开发一个服务的人,你都不知道那个人的水平怎么样,不了解

比尔盖茨说过一句话,在互联网的另外一头,你都不知道甚至坐着一条狗

分布式系统,大公司,多个团队,大型协作,服务是谁的,不了解,很可能说那个哥儿们,实习生都有可能

在一个复杂的系统里,可能你的依赖接口的性能很不稳定,有时候2ms,200ms,2s

如果你不对各种依赖接口的调用,做超时的控制,来给你的服务提供安全保护措施,那么很可能你的服务就被各种垃圾的依赖服务的性能给拖死了

大量的接口调用很慢,大量线程就卡死了,资源隔离,线程池的线程卡死了,超时的控制

(1)execution.isolation.thread.timeoutInMilliseconds

手动设置timeout时长,一个command运行超出这个时间,就被认为是timeout,然后将hystrix command标识为timeout,同时执行fallback降级逻辑

默认是1000,也就是1000毫秒

HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(int value)

(2)execution.timeout.enabled

控制是否要打开timeout机制,默认是true

HystrixCommandProperties.Setter()
.withExecutionTimeoutEnabled(boolean value)

让一个command执行timeout,然后看是否会调用fallback降级