简介
组/前缀 | 配置项 | 默认值 | 说明 |
---|---|---|---|
Execution | execution.isolation.strategy | THREAD | 二选一 THREAD 或 SEMAPHORE |
hystrix.command.default/ hystrix.command.HystrixCommandKey | execution.isolation.thread.timeoutInMilliseconds | 1000 | 调用超时时间设置,超时后触发fallback |
execution.timeout.enabled | true | 是否启用超时机制 | |
execution.isolation.thread.interruptOnTimeout | true | 超时后是否中断执行(只在THREAD模式下有效) | |
execution.isolation.thread.interruptOnCancel | false | 取消时是否中断执行(只在THREAD模式下有效) | |
execution.isolation.semaphore.maxConcurrentRequests | 10 | 最大并发数,超过会被拒绝(只在SEMAPHORE模式下有效) | |
Fallback | fallback.isolation.semaphore.maxConcurrentRequests | 10 | fallback最大并发数(不论Execution是什么模式,fallback都是SEMAPHORE模式) |
hystrix.command.default/ hystrix.command.HystrixCommandKey | fallback.enabled | true | 是否开启fallback功能 |
Circuit Breaker | circuitBreaker.enabled | true | 是否开启断路器 |
hystrix.command.default/ hystrix.command.HystrixCommandKey | circuitBreaker.requestVolumeThreshold | 20 | 断路器开启的最小请求次数 |
circuitBreaker.sleepWindowInMilliseconds | 5000 | 断路器开启后的维持时间,到时间后会处于半开状态放一个请求进来 | |
circuitBreaker.errorThresholdPercentage | 50 | 执行失败比例超过多少后开启断路 | |
circuitBreaker.forceOpen | false | 是否强制开启断路器 | |
circuitBreaker.forceClosed | false | 是否强制关闭断路器 | |
Metrics | metrics.rollingStats.timeInMilliseconds | 10000 | 统计的时间窗口 |
hystrix.command.default/ hystrix.command.HystrixCommandKey | metrics.rollingStats.numBuckets | 10 | 统计时间窗口内的细分个数 |
metrics.rollingPercentile.enabled | true | 启用百分比直方图 | |
metrics.rollingPercentile.timeInMilliseconds | 60000 | 统计的时间窗口 | |
metrics.rollingPercentile.numBuckets | 6 | 统计时间窗口内的细分个数 | |
metrics.rollingPercentile.bucketSize | 100 | 没用。。 | |
metrics.healthSnapshot.intervalInMilliseconds | 500 | HealthCounts 专用统计窗口(对断路器起作用) | |
Request Context | requestCache.enabled | true | 是否启用RequestScope的缓存 |
hystrix.command.default/ hystrix.command.HystrixCommandKey | requestLog.enabled | true | 是否记录执行的细节日志 |
Collapser Properties | maxRequestsInBatch | Integer.MAX_VALUE | 一批的最大请求树 |
hystrix.collapser.default/ hystrix.collapser.HystrixCollapserKey | timerDelayInMilliseconds | 10 | 批量处理收集请求的时间窗口 |
requestCache.enabled | true | 启用requestscope缓存,同Command缓存,配置前缀为hystrix.collapser.XXX | |
ThreadPool Properties | coreSize | 10 | 核心线程数 |
hystrix.threadpool.default/ hystrix.threadpool.HystrixThreadPoolKey | maximumSize | 10 | 最大线程数 |
maxQueueSize | -1 | 等待队列最大长度 | |
queueSizeRejectionThreshold | 5 | 动态调整等待队列大小 | |
keepAliveTimeMinutes | 1 | 空闲线程回收时间 | |
allowMaximumSizeToDivergeFromCoreSize | false | 设为true之后最大线程数和核心线程数可以设不同的值 | |
metrics.rollingStats.timeInMilliseconds | 10000 | 线程池统计时间窗口 | |
metrics.rollingStats.numBuckets | 10 | 线程池统计滑动窗口数 |
详解
-
Execution
-
execution.isolation.strategy
- 两种主要模式:“线程池隔离”或“信号量隔离”
- com.netflix.hystrix.AbstractCommand#executeCommandWithSpecifiedIsolation
private Observable
executeCommandWithSpecifiedIsolation(final AbstractCommand _cmd) { if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.THREAD) { return Observable.defer(...) ... .subscribeOn(threadPool.getScheduler()) }else{ return Observable.defer(...); } - 这里需要一些RXJAVA的基础。 上面的逻辑 “subscribeOn(threadPool.getScheduler())” 在 “线程池隔离”模式下会让调用在线程池中执行。 而在“信号量隔离”模式下没有特殊设置,默认是在当前线程执行
-
execution.timeout.enabled
- com.netflix.hystrix.AbstractCommand#executeCommandAndObserve
private Observable
executeCommandAndObserve(final AbstractCommand _cmd) { ... if (properties.executionTimeoutEnabled().get()) { execution = executeCommandWithSpecifiedIsolation(_cmd) .lift(new HystrixObservableTimeoutOperator (_cmd)); } else { execution = executeCommandWithSpecifiedIsolation(_cmd); } ... } - lift是一个通用的Obserable操作,类似于代理,里面添加了超时的拦截逻辑。
- 内部会创建一个TimeListner在另外的线程中固定时间后调用,幷取消下游订阅,抛出超时异常等,详细可以查看TimerListener#tick功能
-
execution.isolation.thread.timeoutInMilliseconds
- 参考TimerListener#getIntervalTimeInMilliseconds
-
execution.isolation.thread.interruptOnTimeout
- 当超时发生时会调用TimerListener.tick方法,里面会调用unsubscribe
- com.netflix.hystrix.strategy.concurrency.HystrixContextScheduler$FutureCompleterWithConfigurableInterrupt#unsubscribe
private static class FutureCompleterWithConfigurableInterrupt implements Subscription { public void unsubscribe() { if (shouldInterruptThread.call()) { futureTask.cancel(true); }else{ futureTask.cancel(false); } } }
-
execution.isolation.thread.interruptOnCancel
- 当原始接口返回Future类型的时候,这时候任务可以被外面手动cancel。这个配置就有作用了。
- com.netflix.hystrix.HystrixCommand#queue
public Future
queue() { final Future f = new Future () { public boolean cancel(boolean mayInterruptIfRunning) { ... final boolean res = delegate.cancel(interruptOnFutureCancel.get()); if (!isExecutionComplete() && interruptOnFutureCancel.get()) { final Thread t = executionThread.get(); t.interrupt(); } ... } } } -
execution.isolation.semaphore.maxConcurrentRequests
- 指定了SEMAPHORE模式下的最大并发数
- AbstractCommand$TryableSemaphore接口和JDK的Semaphore功能类似,不过这个不会阻塞,并发性能更好。
- 使用方式参考AbstractCommand#applyHystrixSemantics
-
-
Fallback
- fallback.isolation.semaphore.maxConcurrentRequests
- fallback.enabled
- SEMAPHORE用法同EXECUTION, 无论EXECUTION是什么模式,fallback都是SEMAPHORE模式
-
Circuit Breaker
-
circuitBreaker.enabled
- AbstractCommand#initCircuitBreaker
if (enabled) { HystrixCircuitBreaker.Factory.getInstance(...); }else{ return new NoOpCircuitBreaker(); }
- AbstractCommand#applyHystrixSemantics
if (circuitBreaker.attemptExecution()) { ...//继续执行 }else{ return handleShortCircuitViaFallback();//直接调用fallback } } ```
circuitBreaker.requestVolumeThreshold
-
circuitBreaker.errorThresholdPercentage
- com.netflix.hystrix.HystrixCircuitBreaker$HystrixCircuitBreakerImpl#subscribeToStream
public void onNext(HealthCounts hc) { if (hc.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) { // we are not past the minimum volume threshold for the stat windo }else{ if (hc.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) { //we are not past the minimum error threshold for the stat window }else{ if (status.compareAndSet(Status.CLOSED, Status.OPEN)) { circuitOpened.set(System.currentTimeMillis()); } } } }
circuitBreaker.sleepWindowInMilliseconds
circuitBreaker.forceOpen
-
circuitBreaker.forceClosed
- com.netflix.hystrix.HystrixCircuitBreaker$HystrixCircuitBreakerImpl#allowRequest
public boolean allowRequest() { if (properties.circuitBreakerForceOpen().get()) { return false; } if (properties.circuitBreakerForceClosed().get()) { return true; } if (circuitOpened.get() == -1) { return true; }else{ if (status.get().equals(Status.HALF_OPEN)) { return false; }else{ return isAfterSleepWindow(); } } } private boolean isAfterSleepWindow() { final long circuitOpenTime = circuitOpened.get(); final long currentTime = System.currentTimeMillis(); final long sleepWindowTime = properties.circuitBreakerSleepWindowInMilliseconds().get(); return currentTime > circuitOpenTime + sleepWindowTime; }
-
-
Metrics
- metrics.rollingStats.timeInMilliseconds
- metrics.rollingStats.numBuckets
- metrics.rollingPercentile.enabled
- metrics.rollingPercentile.timeInMilliseconds
- metrics.rollingPercentile.numBuckets
- metrics.rollingPercentile.bucketSize
- metrics.healthSnapshot.intervalInMilliseconds
- 大致类结构是这样的
- BucketedCounterStream 桶计算基类
- BucketedCumulativeCounterStream 累计桶计算基类
- CumulativeCollapserEventCounterStream 累计计算Collapser事件
- CumulativeCommandEventCounterStream 累计计算Command执行事件
- CumulativeThreadPoolEventCounterStream 累计计算线程池事件
- BucketedRollingCounterStream 滚动桶计算基类
- HealthCountsStream 健康状态统计(用于断路器)
- RollingCollapserEventCounterStream 滚动计算Collapser事件
- RollingCommandEventCounterStream 滚动计算Command执行事件
- RollingThreadPoolEventCounterStream 滚动计算线程池事件
- BucketedCumulativeCounterStream 累计桶计算基类
- RollingDistributionStream 直方图基类(百分比)
- RollingCollapserBatchSizeDistributionStream 统计Collapser批大小
- RollingCommandLatencyDistributionStream 统计Command执行延迟
- RollingCommandUserLatencyDistributionStream 统计用户线程执行延迟
- metrics.rollingStats.* 对大多数桶计算实现有效
- metrics.rollingPercentile.* 对所有直方图统计有效
- metrics.healthSnapshot.intervalInMilliseconds特殊,只用于HealthCountsStream, 断路器会使用这个统计数据来执行断路判断
- BucketedCounterStream 桶计算基类
- 整个metric都是类似的套路,统计滑动时间窗口内的数据。主要是用到了Rxjava的window方法
- 我们以com.netflix.hystrix.metric.consumer.RollingDistributionStream为例
rollingDistributionStream = stream .observe() .window(bucketSizeInMs, TimeUnit.MILLISECONDS) //按时间窗口分组事件 .flatMap(reduceBucketToSingleDistribution) //把事件转换成数据 .startWith(emptyDistributionsToStart) //数据初始结构 .window(numBuckets, 1) //切分成numBuckets份,每次滑动一份大小的窗口 .flatMap(reduceWindowToSingleDistribution) //统计每个窗口numBuckets份的数据 .map(cacheHistogramValues) //其他逻辑 .share() //缓存计算 .onBackpressureDrop(); //下游计算跟不上上游发送时,丢弃数据
-
Request Context
-
requestCache.enabled
- 开启HTTP request scope的缓存执行,同请求在线程间共享
- 需要设置缓存的key, 可以使用@CacheResult/@CacheKey/实现AbstractCommand#getCacheKey其中一种来实现
- 需要一个servletFilter来开启和结束上下文
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HystrixRequestContext context = HystrixRequestContext.initializeContext(); try { chain.doFilter(request, response); } finally { context.shutdown(); } }
- 用的人不是很多,可以用SpringCache + @RequestScope实现
-
requestLog.enabled
- 貌似只是记录了所有命令的执行情况,幷没有实际的打印动作。可以自己实现
- 参考HystrixRequestLog#getExecutedCommandsAsString
-
-
Collapser Properties
-
maxRequestsInBatch
- com.netflix.hystrix.collapser.RequestBatch#offer
public Observable
offer(RequestArgumentType arg) { ... if (argumentMap.size() >= maxBatchSize) { return null;//超过批量大小,外层拿到null之后会新建一个batch }else{ CollapsedRequestSubject<> collapsedRequest = new CollapsedRequestSubject<>(arg, this); //放入缓存 CollapsedRequestSubject<> existing = argumentMap.putIfAbsent(arg, collapsedRequest); if (existing != null) { return existing.toObservable(); }else{ return collapsedRequest.toObservable(); } } } -
timerDelayInMilliseconds
- com.netflix.hystrix.collapser.RequestCollapser#submitRequest
public Observable
submitRequest(final RequestArgumentType arg) { if (!timerListenerRegistered.get() && timerListenerRegistered.compareAndSet(false, true)) { /* schedule the collapsing task to be executed every x milliseconds (x defined inside CollapsedTask) */ //设置定时任务,timerDelayInMilliseconds后运行 timerListenerReference.set(timer.addListener(new CollapsedTask())); } } private class CollapsedTask implements TimerListener{ public int getIntervalTimeInMilliseconds() { return properties.timerDelayInMilliseconds().get(); } public void tick(){ RequestBatch<> currentBatch = batch.get(); if (currentBatch != null && currentBatch.getSize() > 0) { //新建一个batch,幷执行前一个batch createNewBatchAndExecutePreviousIfNeeded(currentBatch);//新建一个batch,幷执行前一个batch } } }
-
requestCache.enabled
- collapser用的缓存开关
-
-
Thread Pool Properties
- coreSize
- maximumSize
- maxQueueSize
- queueSizeRejectionThreshold
- keepAliveTimeMinutes
- allowMaximumSizeToDivergeFromCoreSize
- metrics.rollingStats.timeInMilliseconds
- metrics.rollingStats.numBuckets
- 基本都是ThreadPool的常规配置, 详见HystrixConcurrencyStrategy#getThreadPool
- 官方推荐线程数设置公式为 线程池大小 = 峰值每秒请求数 * 99%延迟大小 + 富余空间。 比如 30rps * 0.2延迟 = 6, 给一个富余比例可以设为10
其他
-
spring如何初始化hystrix?
@EnableCircuitBreaker
-
spring-cloud-netflix-core -> spring.factories
org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\ org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration
HystrixCircuitBreakerConfiguration
HystrixCommandAspect
HystrixCommandAspect#methodsAnnotatedWithHystrixCommand
-
spring的配置properties是如何注入的?
- spring-cloud-netflix-hystrix 会引入 spring-cloud-netflix-archaius
- 他的spring.factories中有 ArchaiusAutoConfiguration
- ArchaiusAutoConfiguration#configurableEnvironmentConfiguration
- ArchaiusAutoConfiguration#configureArchaius
- ArchaiusAutoConfiguration#addArchaiusConfiguration
- 以上三部将spring的properties配置转换成netflix的动态ConfigurationManager
-
hystrix属性是如何动态更新的?
-
ArchaiusAutoConfiguration$PropagateEventsConfiguration#onApplicationEvent监听spring的EnvironmentChangeEvent事件,幷转发给netflix的配置管理器
public void onApplicationEvent(EnvironmentChangeEvent event) { AbstractConfiguration manager = ConfigurationManager.getConfigInstance(); for (String key : event.getKeys()) { for (ConfigurationListener listener : manager .getConfigurationListeners()) { listener.configurationChanged(new ConfigurationEvent(source, type, key, value, beforeUpdate)); } } }
ExpandedConfigurationListenerAdapter#configurationChanged
-
DynamicProperty$DynamicPropertyListener#setProperty
private static boolean updateProperty(String propName, Object value) { DynamicProperty prop = ALL_PROPS.get(propName); if (prop != null && prop.updateValue(value)) { prop.notifyCallbacks(); return true; } return false; }
HystrixProperty -> HystrixDynamicProperty -> ArchaiusDynamicProperty -> PropertyWrapper -> DynamicProperty
DynamicProperty就是我们最终获得值和更新值的地方
-