你可能该来学习 Hystrix RPC 保护的原理,RPC 保护之熔断器模式了
[HystrixTimer-1] INFO c.c.d.h.CircuitBreakerDemo - fallback- req1:熔断器状态:false, 失败率:0%
[HystrixTimer-1] INFO c.c.d.h.CircuitBreakerDemo - fallback- req2:熔断器状态:false, 失败率:100%
[HystrixTimer-2] INFO c.c.d.h.CircuitBreakerDemo - fallback- req3:熔断器状态:false, 失败率:100%
[HystrixTimer-1] INFO c.c.d.h.CircuitBreakerDemo - fallback- req4:熔断器状态:true, 失败率:100%
[main] INFO c.c.d.h.CircuitBreakerDemo - ============ 熔断器打开了,等待休眠期(默认 5 秒)结束
[hystrix-threadPool-1-5] INFO c.c.d.h.CircuitBreakerDemo - succeed- req5:熔断器状态:true, 失败率:100%
[hystrix-threadPool-1-6] INFO c.c.d.h.CircuitBreakerDemo - succeed- req6:熔断器状态:false, 失败率:0%
[hystrix-threadPool-1-7] INFO c.c.d.h.CircuitBreakerDemo - succeed- req7:熔断器状态:false, 失败率:0%
[hystrix-threadPool-1-8] INFO c.c.d.h.CircuitBreakerDemo - succeed- req8:熔断器状态:false, 失败率:0%
[hystrix-threadPool-1-9] INFO c.c.d.h.CircuitBreakerDemo - succeed- req9:熔断器状态:false, 失败率:0%
[hystrix-threadPool-1-10] INFO c.c.d.h.CircuitBreakerDemo - succeed- req10:熔断器状态:false, 失败率:0%
从上面的执行结果可以看出,在第 4 次请求 req4 时,熔断器才达到熔断触发的次数阈值 3,由于前 3 次皆为超时失败,失败率同时也大于阈值 60%,因此第 4 次请求执行之后,熔断器状态为 open。
在命令的熔断器打开后,熔断器默认会有 5 秒的睡眠等待时间,在这段时间内的所有请求直接执行回退方法;5 秒之后,熔断器会进入 half-open 状态,尝试放行一次命令执行,如果成功就关闭熔断器,状态转成 closed,否则熔断器回到 open 状态。
在上面的程序中,在熔断器熔断之后,演示程序将命令的运行时间 takeTime 改成了 300 毫秒,小于命令的超时限制 500 毫秒。在等待 7 秒(相当于 7000 毫秒)之后,演示程序再一次发起请求,从运行结果可以看到,第 5 次请求 req5 执行成功了,这是一次 half-open 状态的尝试放行,请求成功之后,熔断器的状态转成了 open,后续请求将继续放行。注意,演示程序的第 5 次请求 req5 后的熔断器状态值反应在第 6 次请求 req6 的执行输出中。
熔断器和滑动窗口的配置属性
==============
熔断器的配置包含滑动窗口的配置和熔断器自身的配置。Hystrix 的健康统计是通过滑动窗口来完成的,其熔断器的状态变化也依据滑动窗口的统计数据,所以这里先介绍滑动窗口的配置。先来看两个概念:滑动窗口和时间桶(Bucket)。
1.滑动窗口
可以这么来理解滑动窗口:一位乘客坐在正在行驶的列车的靠窗座位上,列车行驶的公路两侧种着一排挺拔的白杨树,随着列车的前进,路边的白杨树迅速从窗口滑过,我们用每棵树来代表一个请求,用列车的行驶代表时间的流逝,列车上的这个窗口就是一个典型的滑动窗口,这个乘客能通过窗口看到的白杨树的数量就是滑动窗口要统计的数据。
2.时间桶
时间桶是统计滑动窗口数据时的最小单位。同样类比列车窗口,在列车速度非常快时,如果每掠过一棵树就统计一次窗口内树的数据,显然开销非常大,如果乘客将窗口分成 N 份,前进时列车每掠过窗口的 N 分之一就统计一次数据,开销就大大地减小了。简单来说,时间桶就是滑动窗口的 N 分之
一。
熔断器的设置,代码方式可以使用
HystrixCommandProperties.Setter()配置器来完成,参考 5.5.1 节的实例,把自定义的 TakeTimeDemoCommand 中的 Setter()配置器的相关参数配置如下:
/**
*命令参数配置
*/
HystrixCommandProperties.Setter propertiesSetter =
HystrixCommandProperties.Setter()
//至少有 3 个请求,熔断器才达到熔断触发的次数阈值
.withCircuitBreakerRequestVolumeThreshold(3)
//熔断器中断请求 5 秒后会进入 half-open 状态,尝试放行 .withCircuitBreakerSleepWindowInMilliseconds(5000)
//错误率超过 60%,快速失败
.withCircuitBreakerErrorThresholdPercentage(60)
//启用超时
.withExecutionTimeoutEnabled(true)
//执行的超时时间,默认为 1000 毫秒,这里设置为 500 毫秒
.withExecutionTimeoutInMilliseconds(500)
//可统计的滑动窗口内的时间桶数量,用于熔断器和指标发布
.withMetricsRollingStatisticalWindowBuckets(10)
//可统计的滑动窗口的时间长度
//这段时间内的执行数据用于熔断器和指标发布
.withMetricsRollingStatisticalWindowInMilliseconds(10000);
在以上配置中,与熔断器的滑动窗口相关的配置具体含义如下:
(1)在滑动窗口中,最少有 3 个请求才会触发断路,默认值为 20 个。
(2)错误率达到 60%时才可能触发断路,默认值为 50%。
(3)断路之后的 5000 毫秒内,所有请求都直接调用 getFallback()进行回退降级,不会调用 run()方法;5000 毫秒过后,熔断器变为 half-open 状态。
以上 TakeTimeDemoCommand 的熔断器滑动窗口的状态转换关系如图 5-11 所示。
图 5-11 TakeTimeDemoCommand 的熔断器健康统计滑动窗口的状态转换关系 图
大家已经知道,Hystrix 熔断器的配置除了代码方式外,还有 properties 文本属性配置的方式;另外,Hystrix 熔断器相关的滑动窗口不止一个基础的健康统计滑动窗口,还包含一个百分比命令执行时间统计滑动窗口,两个窗口都可以进行配置。
下面以文本属性配置方式为主,对 Hystrix 基础的健康统计滑动窗口的配置进行详细介绍。
(1)hystrix.command.default.metrics.rollingStats.timeInMilliseconds:
设置健康统计滑动窗口的持续时间(以毫秒为单位),默认值为 10 000 毫秒。熔断器的打开会根据一个滑动窗口的统计值来计算,若滑动窗口时间内的错误率超过阈值,则熔断器将进入 open 状态。滑动窗口将被进一步细分为时间桶,滑动窗口的统计值等于窗口内所有时间桶的统计信息的累加,每个时间桶的统计信息包含请求成功(success)、失败(failure)、超时(timeout)、被拒(rejection)的次数。
此选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withMetricsRollingStatisticalWindowInMilliseconds(int)
(2)
hystrix.command.default.metrics.rollingStats.numBuckets:设置健康统计滑动窗口被划分的时间桶的数量,默认值为 10。若滑动窗口的持续时间为默认的 10 000 毫秒,在默认情况下,一个时间桶的时间即 1 秒。若要做定制化的配置,则所设置的 numBuckets(时间桶数量)的值和 timeInMilliseconds(滑动窗口时长)的值有关联关系,必须符合 timeInMilliseconds%numberBuckets==0 的规则,否则会抛出异常。例如,二者的关联关系为 70 000(滑动窗口 70 秒)%700(桶数)==0 是可以的,但是 70 000(滑动窗口 70 秒)%600(桶数)==400 将抛出异常。
此选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withMetricsRollingStatisticalWindowBuckets(int)
(3)
hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds:设置健康统计滑动窗口拍摄运行状况统计指标的快照的时间间隔。
什么是拍摄运行状况统计指标的快照呢?就是计算成功和错误百分比这些影响熔断器状态的统计数据。
拍摄快照的时间间隔的单位为毫秒,默认值为 500 毫秒。由于统计指标的计算是一个消耗 CPU 的操作(即 CPU 密集型操作),也就是说,高频率地计算错误百分比等健康统计数据会占用很多 CPU 资源,因此在高并发 RPC 流量大的应用场景下可以适当调大拍摄快照的时间间隔。
此选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withMetricsHealthSnapshotIntervalInMilliseconds(int)Hystrix 熔断器相关的滑动窗口不止一个基础的健康统计滑动窗口,还包含一个百分比命令执行时间统计滑动窗口。什么是“百分比命令执行时间”统计滑动窗口呢?该滑动窗口主要用于统计 1%、10%、50%、90%、99%等一系列比例的命令执行平均耗时,主要用于生成统计图表。
带
hystrix.command.default.metrics.rollingPercentile 前缀的配置项专门用于配置百分比命令执行时间统计滑动窗口。下面以文本属性配置方式为主对 Hystrix“百分比命令执行时间”统计滑动窗口的配置进行详细介绍。
(1)
hystrix.command.default.metrics.rollingPercentile.enabled:该配置项用于设置百分比命令执行时间统计滑动窗口是否生效,命令的执行时间是否被跟踪,并且计算各个百分比(如 1%、10%、50%、90%、99.5%等)的平均时间。该配置项默认为 true。
(2)
hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds:设置百分比命令执行时间统计滑动窗口的持续时间(以毫秒为单位),默认值为 60 000 毫秒。当然,此滑动窗口进一步被细分为时间桶,以便提高统计的效率。
本选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withMetricsRollingPercentileWindowInMilliseconds(int)
(3)
hystrix.command.default.metrics.rollingPercentile.numBuckets:设置百分比命令执行时间统计滑动窗口被划分的时间桶的数量,默认值为 6。
此滑动窗口的默认持续时间为 60 000 毫秒,在默认情况下,一个时间桶的时间即 10 秒。若要做定制化的配置,则此窗口所设置的 numBuckets(时间桶数量)的值和 timeInMilliseconds(滑动窗口时长)的值有关联关系,必须符合 timeInMilliseconds(滑动窗口时长)%numberBuckets==0 的规则,否则将抛出异常。
本选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withMetricsRollingPercentileWindowBuckets(int)
(4)
hystrix.command.default.metrics.rollingPercentile.bucketSize:设置百分比命令执行时间统计滑动窗口的时间桶内最大的统计次数,若 bucketSize 为 100,而桶的时长为 1 秒,这 1 秒里有 500 次执行,则只有最后 100 次执行的信息会被统计到桶里。增加此配置项的值会导致内存开销及其他计算开销上升,该配置项的默认值为 100。
本选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withMetricsRollingPercentileBucketSize (int)
以上是 Hystrix 熔断器相关的滑动窗口的配置,接下来介绍熔断器本身的配置。
带
hystrix.command.default.circuitBreaker 前缀的配置项专门用于对熔断器本身进行配置。下面以文本属性配置方式为主,对 Hystrix 熔断器的配置进行详细介绍。
(1)
hystrix.command.default.circuitBreaker.enabled:该配置用来确定是否启用熔断器,默认值为 true。
本选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withCircuitBreakerEnabled (boolean)
(2)
hystrix.command.default.circuitBreaker.requestVolumeThreshold:该配置用于设置熔断器触发熔断的最少请求次数。如果设置为 20,那么当一个滑动窗口时间内(比如 10 秒)收到 19 个请求时,即使 19 个请求都失败,熔断器也不会打开变成 open 状态,默认值为 20。
此选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(int)
(3)
hystrix.command.default.circuitBreaker.errorThresholdPercentage:该配置用于设置错误率阈值,当健康统计滑动窗口的错误率超过此值时,熔断器进入 open 状态,所有请求都会触发失败回退(fallback),错误率阈值百分比的默认值为 50。
本选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withCircuitBreakerErrorThresholdPercentage(int)
(4)
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds:此配置项指定熔断器打开后经过多长时间允许一次请求尝试执行。熔断器打开时,Hystrix 会在经过一段时间后就放行一条请求,如果这条请求执行成功,就说明此时服务很可能已经恢复正常,会将熔断器关闭,如果这条请求执行失败,就认为目标服务依然不可用,熔断器继续保持打开状态。
该配置用于设置熔断器的睡眠窗口,具体指定熔断器打开之后过多长时间才允许一次请求尝试执行,默认值为 5000 毫秒,表示当熔断器开启后,5000 毫秒内会拒绝所有的请求,5000 毫秒之后,熔断器才会进入 halfopen 状态。
此选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withCircuitBreakerSleepWindowInMilliseconds (int)(5)hystrix.command.default.circuitBreaker.forceOpen:如果配置为 true,熔断器就会被强制打开,所有请求将被触发失败回退(fallback)。此配置的默认值为 false。
此选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withCircuitBreakerForceOpen (boolean)
下面是本书随书实例 demo-provider 中有关熔断器的配置,节选如下:
hystrix:
...
command:
...
default: #全局默认配置
circuitBreaker: #熔断器相关配置
enabled: true #是否启动熔断器,默认为 true
requestVolumeThreshold: 20 #启用熔断器功能窗口时间内的最小请求数
sleepWindowInMilliseconds: 5000 #指定熔断器打开后多长时间内允许一次请求尝试执行
errorThresholdPercentage:50 #窗口时间内超过 50%的请求失败后就会打开熔断器
metrics:
rollingStats:
timeInMilliseconds: 6000
numBuckets: 10
UserClient#detail(Long): #独立接口配置,格式为: 类名 #方法名(参数类型列表)
circuitBreaker: #熔断器相关配置
enabled: true #是否使用熔断器,默认为 true
requestVolumeThreshold: 20 #窗口时间内的最小请求数
sleepWindowInMilliseconds:5000 #打开后允许一次尝试的睡眠时间,默认配置为 5 秒
errorThresholdPercentage: 50 #窗口时间内熔断器开启的错误比例,默认配置为 50
metrics:
rollingStats:
timeInMilliseconds: 10000 #滑动窗口时间
numBuckets: 10 #滑动窗口的时间桶数
使用文本格式配置时,可以对熔断器的参数值进行默认配置,也可以对特定的 RPC 接口进行个性化配置。对熔断器的参数值进行配置时使用 hystrix.command.default 默认前缀,对特定的 RPC 接口进行个性化配置时使用
hystrix.command.FeignClient#Method 格式的前缀。在上面的演示例子中,对远程客户端 Feign 接口 UserClient 中的 detail(Long)方法做了个性化的熔断器配置,其配置项的前缀如下:hystrix.command.UserClient#detail(Long)
Hystrix 命令的执行流程
===============
在获取 HystrixCommand 命令的执行结果时,无论是使用 execute()、toObservable()方法,还是使用 observe()方法,最终都会通过执行
HystrixCommand.toObservable()订阅执行结果和返回。在 Hystrix 内部,调用 toObservable()方法返回一个观察的主题,当 Subscriber 订阅者订阅主题后,HystrixCommand 会弹射一个事件,然后通过一系列的判断,顺序依次是缓存是否命中、熔断器是否打开、线程池是否占满,开始执行实际的 HystrixCommand.run()方法。该方法的实现主要为异步处理的业务逻辑,如果在这其中任何一个环节出现错误或者抛出异常,就会回退到 getFallback()方法进行服务降级处理,当降级处理完成之后,会将结果返回给实际的调用者。
HystrixCommand 的工作流程总结起来大致如下:
(1)判断是否使用缓存响应请求,若启用了缓存,且缓存可用,则直接使用缓存响应请求。Hystrix 支持请求缓存,但需要用户自定义启动。
(2)判断熔断器是否开启,如果熔断器处于 open 状态,则跳到第(5)步。
(3)若使用线程池进行请求隔离,则判断线程池是否已占满,若已满则跳到第(5)步;若使用信号量进行请求隔离,则判断信号量是否耗尽,若耗尽则跳到第(5)步。
(4)使用 HystrixCommand.run()方法执行具体业务逻辑,如果执行失败或者超时,就跳到第(5)步,否则跳到第(6)步。
(5)执行
HystrixCommand.getFallback()服务降级处理逻辑。(6)返回请求响应。
以上流程如图 5-12 所示。
评论