背景
对于 Hystrix 的配置,我们在平时使用过程经常会出现这样的问题,以为理所应当的配置,其实际上并不会起作用。通过复制粘贴的代码,如果没有经过斟酌,可能永远都没有起到其应该起到的作用。这就是我们为何要将 Hystrix 配置搞清楚的原因。
HystrixCommand 注解属性
@HystrixCommand
注解上的属性。对于 HystrixCommand 注解属性,一般有两种方式,一种是给某个类添加全局配置,一种是针对各个方法添加。
全局配置一般需要借助 @DefaultProperties
注解。除了仅针对方法级别的属性以外(commandKey/observableExecutionMode/fallbackMethod
),其他的都可以通过该注解来配置。当一个类中有多个 Hystrix 方法时,这种方式可以减少各个方法上都是相同配置的冗余代码。
局部配置一般是通过在方法上添加 @HystrixCommand
并配置注解参数来实现。这种配置的方式的优先级高于全局配置注解 @DefaultProperties
。
commandKey
唯一标识一个 Hystrix 方法。默认会取被 @HystrixCommand
注解的方法名。建议要起一个全局独一无二的名称,防止多个方法因为 commandKey
名称重复而相互影响。
groupKey
为 Hystrix 方法的分组标识。默认取当前运行时的方法的类名。
threadPoolKey
用来标识一个线程池的 key。默认取 groupKey 的值。一般来说同一个类中共用同一个线程池。若共用同一个线程池的不同的方法,配置了相同的线程池属性,在第一个方法被执行后线程池的属性就已经被固定,因此会以第一个被执行的方法上的配置为主。
fallbackMethod
方法执行时,出现熔断、超时、异常等错误时会触发该属性配置的回退方法。该方法需要保持与 Hystrix 方法的签名和返回值一致。
ignoreExceptions
指定哪些异常需要被 Hystrix 忽略,忽略的异常会被直接抛到方法上层,不会触发 fallbackMethod
回退方法,也不会参与到失败率的统计中而影响熔断的状态。
observableExecutionMode
TODO 搞清楚Observable调用逻辑。
当 Hystrix 命令被包装成 RxJava 的 Observer 异步执行时,此配置指定了 Observable 被执行的模式,默认是 ObservableExecutionMode.EAGER,Observable 会在被创建后立刻执行,而 ObservableExecutionMode.EAGER模式下,则会产生一个 Observable 被 subscribe 后执行。
当 @HystrixCommand
注解的方法返回值为 Observable
类型或其子类时生效。默认值为 ObservableExecutionMode.EAGER
。我们常见的命令都是同步执行的,此配置项可以不配置。
raiseHystrixExceptions
当配置项中包含 HystrixException.RUNTIME_EXCEPTION
时,所有未被忽略的异常统一包装成 HystrixRuntimeException
,方便将异常统一处理。默认值为 {}
。
defaultFallback
默认回退方法,当配置 fallbackMethod
项时此项不会生效,另外,默认回退方法不能有参数,返回值要与 Hystrix方法的返回值一致。
commandProperties
与 Hystrix 命令属性相关的配置。下文会有详细解释。
threadPoolProperties
与 Hystrix 命令执行线程池相关的属性。下文会有详细解释。
命令属性(commandProperties)
commandProperties
的命令属性是由 @HystrixProperty
数组配置组成。其中 @HystrixProperty
是由 name
和 value
两个属性构成,并且这两个属性都是字符串类型。其属性都是以 hystrix.command.*
开头,全局默认属性是 hystrix.command.default.*
, 具体方法的属性 hystrix.command.HystrixCommandKey(具体CommandKey名).*
。具体的配置如下。
执行属性(Execution)
接下来介绍的属性,是控制 HystrixCommand.run()
方法如何执行的属性。
execution.isolation.strategy
该属性表示指定当前 Hystrix 方法的请求隔离策略。也就是 HystrixCommand.run()
方法执行时采用的隔离策略。有两种隔离方式:线程隔离(THREAD
) 和信号量隔离(SEMAPHORE
)。默认为线程隔离方式。
线程池隔离 — 表示在隔离的线程中执行,并且并发请求的总数受限于当前线程池中线程的总数。
信号量隔离 — 表示在当前调用的线程中执行,并且并发请求受限于设置的信号量的总数。
HystrixCommand
默认使用线程池隔离(THREAD
),HystrixObservableCommand
默认使用信号量隔离(SEMAPHORE
)。
通常只有当调用量非常高以至于线程隔离开销太高时,采用信号量隔离的方式。信号量隔离方式通常应用于非网络调用场景。
execution.isolation.thread.timeoutInMilliseconds
该属性设置以毫秒为单位的时间,超过该时间后,调用者监听到超时并不再继续执行当前方法命令的执行。默认值为 1000
。Hystrix 标记当前 HystrixCommand
为超时状态,并且执行回退逻辑。不过需要注意的是,只有 command.timeout.enabled
开关开启,该配置才会生效。
execution.timeout.enabled
该属性代表着是否开启HystrixCommand.run()
执行时超时。如果开启则到达 execution.isolation.thread.timeoutInMilliseconds
配置的时间后执行超时回退逻辑。默认值为 ture
。
execution.isolation.thread.interruptOnTimeout
该属性表示 HystrixCommand.run()
方法执行出现超时情况时是否要中断执行。默认值为 true
。
execution.isolation.thread.interruptOnCancel
该属性表示 HystrixCommand.run()
方法执行被取消时是否要中断执行。默认值为 false
。
execution.isolation.semaphore.maxConcurrentRequests
该属性是在信号量隔离模式下,允许 HystrixCommand.run()
执行的最大并发数。设置的值只有当 execution.isolation.strategy
设置为 SEMAPHORE
才会生效。超过此并发数的请求会被拒绝。默认值为10
。
回退属性(Fallback)
回退相关属性是控制 HystrixCommand.getFallback()
方法执行的配置。这些属性会对线程隔离(THREAD
)和信号量隔离(SEMAPHORE
)都会生效。
fallback.isolation.semaphore.maxConcurrentRequests
回退方法执行的最大并发数。默认值为 10
。超过此最大并发数后会抛出 REJECTED_SEMAPHORE_FALLBACK
异常。
fallback.enabled
是否开启方法回退功能。当调用失败或者拒绝发生时是否调用回退方法。默认为 true
。
断路器属性(CircuitBreaker)
断路器属性是控制 Hystrix 中 HystrixCircuitBreaker
的行为。
circuitBreaker.enabled
断路器开关是否开启。默认值为 true
。如果开启,断路器将用于跟踪运行状态,并在断路器跳闸时短路请求。
circuitBreaker.errorThresholdPercentage
该属性设置请求失败错误百分比。到该值后会跳闸电路,将请求断路并执行回退逻辑。默认值 50
。
在通过滑动窗口获取到当前时间段内 Hystrix 方法执行的失败率后,就需要根据此配置来判断是否要将熔断器打开了。 此配置项默认值是 50
,即窗口时间内超过 50%
的请求失败后会打开熔断器将后续请求快速失败。
circuitBreaker.requestVolumeThreshold
在一个滚动窗口中使断路器跳闸的最小请求数。默认值为 20
。例如,如果设置值为20,此时如果仅有19个请求进入滚动窗口(假设一个时间窗口为10s),就算是这19个请求全部失败,电路也不会跳闸。
试想如果没有这么一个限制,我们配置了 50%
的请求失败会打开熔断器,窗口时间内只有 3 条请求,恰巧两条都失败了,那么熔断器就被打开了,5s 内的请求都被快速失败。此配置项的值需要根据接口的 QPS 进行计算,值太小会有误打开熔断器的可能,值太大超出了时间窗口内的总请求数,则熔断永远也不会被触发。建议设置为 QPS * 窗口秒数 * 60%
TODO 这里的时间窗口和哪个指标窗口有什么关系?
circuitBreaker.sleepWindowInMilliseconds
断路器跳闸后,所有的请求都会快速失败,但何时服务恢复正常就是下一个要面对的问题。跳闸后,Hystrix 会在经过 circuitBreaker.sleepWindowInMilliseconds
配置的时间后就放行一条请求,如果这条请求执行成功了,说明此时服务很可能已经恢复了正常,那么断路器关闭。如果此请求执行失败,则认为服务依然不可用,断路器继续保持跳闸状态。此配置项指定了断路器打开后经过多长时间允许一次请求尝试执行,默认值是 5000
。
circuitBreaker.forceOpen
该属性设置为true
后,断路器将置于强制开启状态,并且所有的请求将会被拒绝。默认值为 false
。
该属性优先于下面的 circuitBreaker.forceClosed
。
circuitBreaker.forceClosed
该属性设置为 true
后,断路器将置于强制关闭状态,并且无论错误请求百分比如何,都允许请求。默认值为 false
。
circuitBreaker.forceOpen
属性的优先级高于该属性。因此如果设置了 circuitBreaker.forceOpen
属性为 true
,circuitBreaker.forceClosed
属性什么都不会做。
指标统计属性(Metrics)
指标属性和 Hystrix 方法执行时捕获的指标相关。
metrics.rollingStats.timeInMilliseconds
设置统计滚动窗口的持续时长,单位为毫秒。这是 Hystrix 保存统计指标供断路器使用和发布的时间。默认值为 10000
。即一个滚动窗口默认统计的是 10s 内的请求数据。
滚动窗口被划分为多个桶,并会“滑过”这些桶。例如,假设当前滚动窗口时间为 10s,拆分成 10 个 1秒的桶,下面的图表示的是如何滚动新桶,并关闭旧桶。
metrics.rollingStats.numBuckets
该属性设置滚动统计窗口被划分的桶数。默认值为 10
。
需要注意的是,metrics.rollingStats.timeInMilliseconds % metrics.rollingStats.numBuckets == 0
这个表达式必须要为 true
,否则会抛异常。也就是说要求这两个指标需要能整除。
metrics.rollingPercentile.enabled
该属性表示方法执行延迟是否应该被跟踪并将其计算为百分位数。如果不计算方法执行延迟,则所有的汇总统计(平均数、百分位数)都返回-1。默认值为 true
。
metrics.rollingPercentile.timeInMilliseconds
统计方法的执行延迟的滚动窗口的时长。必须要开启 metrics.rollingPercentile.enabled
配置。单位为毫秒。默认值为 60000
。
和 metrics.rollingStats.timeInMilliseconds
类似,该滚动窗口也被划分为多个桶,并会“滑过”这些桶。
metrics.rollingPercentile.numBuckets
该属性设置方法执行延迟滚动窗口被划分的桶数。默认值为 6
。
需要注意的是,metrics.rollingPercentile.timeInMilliseconds % metrics.rollingPercentile.numBuckets == 0
这个表达式必须要为 true
,否则会抛异常。也就是说要求这两个指标需要能整除。
metrics.rollingPercentile.bucketSize
该属性设置每个桶被保留的最大执行请求次数。如果在此期间超过了最大执行数后,他们将会从桶的开头进行覆盖。默认值为 100
。
在默认的情况下,每个桶的时间长度为 10s = 60000ms / 6
,但这 10s 内只保留最近的 100 条请求的数据。
如果增加此大小,这也会增加存储值所需的内存量,并增加对列表进行排序以进行百分位计算所需的时间。
metrics.healthSnapshot.intervalInMilliseconds
设置每个拍摄健康快照的间隔时间,允许拍摄健康快照以计算成功和失败百分比以及影响断路器状态。单位为毫秒。默认值为 500
。
在大容量电路中,连续计算错误百分比可能会占用大量 CPU,因此此属性可用来控制计算频率。
请求上下文属性(Request Context)
请求上下文属性涉及在 HystrixCommand
中使用的 HystrixRequestContext
的功能。
requestCache.enabled
是否开启请求结果缓存功能。但它并不意味着我们的每个请求都会被缓存。缓存请求结果和从缓存中获取结果都需要我们配置 @CacheKey
,并且在方法上使用 @CacheResult
注解声明一个缓存上下文。默认值为 true
。
requestLog.enabled
是否开启请求日志。默认值为 true
。
线程池属性(threadPoolProperties)
线程池属性是控制 Hystrix 请求执行的线程池的行为。注意这些名称和 JAVA 中的线程池是匹配的。
线程池属性都是以 hystrix.threadpool.*
开头,全局默认属性是 hystrix.threadpool.default.*
, 具体方法的属性 hystrix.threadpool.HystrixCommandKey(具体CommandKey名).*
。
绝大多数情况默认值 10 个线程就可以了,一般可以设置得更小。如果要调整线程池的大小,有个基本的计算公式:
健康时每秒峰值请求数 * 第99个百分位延迟秒数 + 喘息空间线程数
(TODO)
下面用一个示例说明公式的使用。
基本的原则是尽可能让线程池保持最小,因为这是减轻负载和阻止资源在发生延迟时而被阻塞的主要方法。
Netflix API has 30+ of its threadpools set at 10, two at 20, and one at 25.
上图显示了一个示例配置,其中依赖项没有理由达到第 99.5 个百分位,因此它在网络超时层将其缩短并立即重试,并期望它在大多数时间获得中等延迟,并且将能够在 300 毫秒线程超时内完成这一切。
如果依赖项有正当理由有时会达到第 99.5 个百分位数(例如延迟生成的缓存未命中),则网络超时将需要设置得比它高,例如 325 毫秒,重试 0 或 1 次,线程超时设置得更高(350ms+ )
线程池大小为 10 以处理突发的 99% 请求,但当一切正常时,此线程池通常在任何给定时间只有 1 或 2 个线程处于活动状态,以服务大多数 40 毫秒的中值调用。
当你正确配置它时,HystrixCommand 层的超时应该会很少见,但保护是存在的,以防网络延迟以外的其他因素影响时间,或者在最坏的情况下连接+读取+重试+连接+读取的组合仍然存在超过配置的整体超时时间。
当性能特征发生变化或发现问题时,您可以根据需要实时更改配置,在出现问题或配置错误时,所有这些都没有关闭整个应用程序的风险。
TODO 这里需要读源码确认
coreSize
核心线程池大小。默认值为 10
。TODO,计算公式有点疑问。
maximumSize
最大线程池大小。默认值为 10
。该属性只有设置了 allowMaximumSizeToDivergeFromCoreSize
属性后才会生效。
maxQueueSize
线程池最大线程排队数。默认值为 -1
。如果值为 -1
,会使用 SynchronousQueue
,此时队列大小为 0,Hystrix 不会向队列内排队线程;如果值为正数则会使用 LinkedBlockingQueue
,这时候 size 是一个固定大小的队列,当核心线程忙碌时,会将作业暂时存放在此队列。
queueSizeRejectionThreshold
该属性设置队列大小拒绝阈值。人为控制最大队列大小,即使未达到 maxQueueSize
也会发生拒绝。存在此属性是因为 BlockingQueue
的 maxQueueSize
无法动态更改。默认值为5
。所以有时候只设置了 maxQueueSize
也不会起作用。注意,当 maxQueueSize==-1
时,该属性不生效。
keepAliveTimeMinutes
如果 coreSize < maximumSize
,则此属性控制线程在被释放之前将闲置多长时间,单位分钟。默认值为 1
。
allowMaximumSizeToDivergeFromCoreSize
只有配置了该属性, maximumSize
才会生效。值的设置可以比 coreSize
相等或者大。类似于 JAVA 的线程池,只有在队列满后才会启用最大线程数,之后线程不活动后会定时回收线程(根据 keepAliveTimeInMinutes
配置的时间)。默认值为 false
。
metrics.rollingStats.timeInMilliseconds
设置统计滚动窗口的持续时长,单位为毫秒。默认值为 10000
。这是线程池的统计指标会被保留的时间。和上面的 command 配置类似,该窗口也会被拆分成多个桶。
metrics.rollingStats.numBuckets
该属性设置滚动统计窗口被划分的桶数。默认值为 10
。
需要注意的是,metrics.rollingStats.timeInMilliseconds % metrics.rollingStats.numBuckets == 0
这个表达式必须要为 true
,否则会抛异常。也就是说要求这两个指标需要能整除。
合并请求属性(CollapserProperties)
合并请求属性是用来控制 HystrixCollapser
的行为的。其属性都是以 hystrix.collapser.*
开头,全局默认属性是 hystrix.collapser.default.*
, 具体方法的属性 hystrix.collapser.HystrixCommandKey(具体CommandKey名).*
。具体的配置如下。
maxRequestsInBatch
该属性设置允许在触发批处理执行前批处理中允许的最大请求数。默认值为 Integer.MAX_VALUE
。
timerDelayInMilliseconds
此属性设置创建批处理后触发其执行的毫秒数。默认值为 10
。
requestCache.enabled
在合并请求中决定 HystrixCollapser.execute()
和 HystrixCollapser.queue()
方法调用中请求缓存是否生效。
疑问
- 验证是否方法的返回值类型,决定当前的方法是属于异步、同步、观察者模式。
- 确定根据方法返回类型判断当前是否是同步、异步、观察者模式。
- Future 请求的原理。
- 多个事情同时开始,最终需要等待所有事情完成后在做多个相互依赖的事情。比如炒菜。
- 理清楚 commandKey、groupKey、threadPoolKey之间的关系。
- groupKey 会作为线程池key名称的备选。也就是当不配置threadPoolKey名称的时候,会赋值 groupKey 给线程名称。
- commandKey 只会使用方法名或者指定名称,一次要保证唯一性,否则会有问题。
- RxJava编码风格,要求可以看懂 Hystrix 代码逻辑。
- 断路器的滚动窗口在什么地方统计,其时间计算方式是什么?
- 两个滚动窗口,这些作用是什么?分别是用来做什么的?
- 方法执行延迟的滚动窗口?
- 线程池的滚动窗口又是具体干啥的?