本篇针对hystrix进行粗略的讲解
上篇对hystrix留了三个疑问
1、如何做到对不同的command对象使用相同的semaphore和断路器呢?
2、semaphore和断路器是作用于什么维度上的呢?是command key还是group key?也就是对每个方法进行限流和断路还是每个service呢?
3、每次都传入hystrix command properties,会修改之前的配置吗?比如第二次相同的group key/command key传入了不同的config,会改变第一次的值吗?
先来看看new hystrix command的源码
protected HystrixCommand(Setter setter) {
// use 'null' to specify use the default
//circuitBreaker和executionSemaphore都传null
this(setter.groupKey, setter.commandKey, setter.threadPoolKey, null, null, setter.commandPropertiesDefaults, setter.threadPoolPropertiesDefaults, null, null, null, null, null);
}
/* package for testing */HystrixCommand(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool,
HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults,
HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore,
HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) {
super(group, key, threadPoolKey, circuitBreaker, threadPool, commandPropertiesDefaults, threadPoolPropertiesDefaults, metrics, fallbackSemaphore, executionSemaphore, propertiesStrategy, executionHook);
}
此处super了AbstractCommand的构造器
protected AbstractCommand(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool,
HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults,
HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore,
HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) {
this.commandGroup = initGroupKey(group);
this.commandKey = initCommandKey(key, getClass());
this.properties = initCommandProperties(this.commandKey, propertiesStrategy, commandPropertiesDefaults);
this.threadPoolKey = initThreadPoolKey(threadPoolKey, this.commandGroup, this.properties.executionIsolationThreadPoolKeyOverride().get());
this.metrics = initMetrics(metrics, this.commandGroup, this.threadPoolKey, this.commandKey, this.properties);
//初始化断路器
this.circuitBreaker = initCircuitBreaker(this.properties.circuitBreakerEnabled().get(), circuitBreaker, this.commandGroup, this.commandKey, this.properties, this.metrics);
this.threadPool = initThreadPool(threadPool, this.threadPoolKey, threadPoolPropertiesDefaults);
//Strategies from plugins
this.eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
this.concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
HystrixMetricsPublisherFactory.createOrRetrievePublisherForCommand(this.commandKey, this.commandGroup, this.metrics, this.circuitBreaker, this.properties);
this.executionHook = initExecutionHook(executionHook);
this.requestCache = HystrixRequestCache.getInstance(this.commandKey, this.concurrencyStrategy);
this.currentRequestLog = initRequestLog(this.properties.requestLogEnabled().get(), this.concurrencyStrategy);
/* fallback semaphore override if applicable */
this.fallbackSemaphoreOverride = fallbackSemaphore;
/* execution semaphore override if applicable */
//此处设置executionSemaphoreOverride成员变量为null
this.executionSemaphoreOverride = executionSemaphore;
}
这里主要关注我们在rpc框架里用到的两个功能,分别是断路器和信号量,信号量这里设置为null,先来看断路器的初始化
private static HystrixCircuitBreaker initCircuitBreaker(boolean enabled, HystrixCircuitBreaker fromConstructor,
HystrixCommandGroupKey groupKey, HystrixCommandKey commandKey,
HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
if (enabled) {
if (fromConstructor == null) {
// get the default implementation of HystrixCircuitBreaker
//走这里通过工厂获取断路器
return HystrixCircuitBreaker.Factory.getInstance(commandKey, groupKey, properties, metrics);
} else {
return fromConstructor;
}
} else {
return new NoOpCircuitBreaker();
}
}
看HystrixCircuitBreaker.Factory.getInstance的方法实现
class Factory {
// String is HystrixCommandKey.name() (we can't use HystrixCommandKey directly as we can't guarantee it implements hashcode/equals correctly)
//静态成员变量circuitBreakersByCommand,key为commandkey的name,value为断路器,在类加载时初始化
private static ConcurrentHashMap<String, HystrixCircuitBreaker> circuitBreakersByCommand = new ConcurrentHashMap<String, HystrixCircuitBreaker>();
//被abstract command调用来获取断路器
//先从静态成员变量中根据command key获取,如果有则直接返回
//如果没有,则新建,并加入到静态成员变量中,然后返回
//可以看到,当传入的command key相同时,总是返回同一个断路器
public static HystrixCircuitBreaker getInstance(HystrixCommandKey key, HystrixCommandGroupKey group, HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
// this should find it for all but the first time
HystrixCircuitBreaker previouslyCached = circuitBreakersByCommand.get(key.name());
if (previouslyCached != null) {
return previouslyCached;
}
// if we get here this is the first time so we need to initialize
// Create and add to the map ... use putIfAbsent to atomically handle the possible race-condition of
// 2 threads hitting this point at the same time and let ConcurrentHashMap provide us our thread-safety
// If 2 threads hit here only one will get added and the other will get a non-null response instead.
HystrixCircuitBreaker cbForCommand = circuitBreakersByCommand.putIfAbsent(key.name(), new HystrixCircuitBreakerImpl(key, group, properties, metrics));
if (cbForCommand == null) {
// this means the putIfAbsent step just created a new one so let's retrieve and return it
return circuitBreakersByCommand.get(key.name());
} else {
// this means a race occurred and while attempting to 'put' another one got there before
// and we instead retrieved it and will now return it
return cbForCommand;
}
}
public static HystrixCircuitBreaker getInstance(HystrixCommandKey key) {
return circuitBreakersByCommand.get(key.name());
}
}
再来看execute的逻辑,会走这个方法
private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {
// mark that we're starting execution on the ExecutionHook
// if this hook throws an exception, then a fast-fail occurs with no fallback. No state is left inconsistent
executionHook.onStart(_cmd);
/* determine if we're allowed to execute */
//看刚刚init的断路器是否关闭,即当前请求能否通行
if (circuitBreaker.attemptExecution()) {
//获取信号量限流器,重点在这里
final TryableSemaphore executionSemaphore = getExecutionSemaphore();
final AtomicBoolean semaphoreHasBeenReleased = new AtomicBoolean(false);
final Action0 singleSemaphoreRelease = new Action0() {
@Override
public void call() {
if (semaphoreHasBeenReleased.compareAndSet(false, true)) {
executionSemaphore.release();
}
}
};
final Action1<Throwable> markExceptionThrown = new Action1<Throwable>() {
@Override
public void call(Throwable t) {
eventNotifier.markEvent(HystrixEventType.EXCEPTION_THROWN, commandKey);
}
};
//看是否达到信号量限流器最大流量
if (executionSemaphore.tryAcquire()) {
try {
/* used to track userThreadExecutionTime */
executionResult = executionResult.setInvocationStartTime(System.currentTimeMillis());
return executeCommandAndObserve(_cmd)
.doOnError(markExceptionThrown)
.doOnTerminate(singleSemaphoreRelease)
.doOnUnsubscribe(singleSemaphoreRelease);
} catch (RuntimeException e) {
return Observable.error(e);
}
} else {
return handleSemaphoreRejectionViaFallback();
}
} else {
return handleShortCircuitViaFallback();
}
}
再来看getExecutionSemaphore方法是如何获取信号量限流器的
//Abstract command类的静态成员变量,key为command key name,value为信号量限流器
protected static final ConcurrentHashMap<String, TryableSemaphore> executionSemaphorePerCircuit = new ConcurrentHashMap<String, TryableSemaphore>();
protected TryableSemaphore getExecutionSemaphore() {
//如果当前的限流策略是信号量限流
if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.SEMAPHORE) {
//如果executionSemaphoreOverride为空,注意框架里传的就是null
if (executionSemaphoreOverride == null) {
//从静态成员变量中根据command key name拿到信号量限流器
TryableSemaphore _s = executionSemaphorePerCircuit.get(commandKey.name());
if (_s == null) {
//如果第一次拿,则根据properties新创建一个限流器,并缓存到静态成员变量里
// we didn't find one cache so setup
executionSemaphorePerCircuit.putIfAbsent(commandKey.name(), new TryableSemaphoreActual(properties.executionIsolationSemaphoreMaxConcurrentRequests()));
// assign whatever got set (this or another thread)
//返回
return executionSemaphorePerCircuit.get(commandKey.name());
} else {
//如果不是第一次拿则直接返回
return _s;
}
} else {
return executionSemaphoreOverride;
}
} else {
// return NoOp implementation since we're not using SEMAPHORE isolation
return TryableSemaphoreNoOp.DEFAULT;
}
}
注意这里的逻辑跟断路器十分相似,也是维护了一个静态成员变量,key是command key name,值为信号量限流器,当第一次new一个command时,会根据传入的properties生成一个信号量限流器放入静态成员变量中,当下一次再new一个command时,从静态成员变量取的时候就能直接取到之前生成的那个限流器。
再回顾一下断路器,当new一个command时,会init断路器,init的过程是从factory静态成员变量中根据command key name获取一个断路器,当第一次new的时候,会根据properties创建一个断路器,并塞到静态成员变量中,当下一个再new command时就直接从fatory的静态成员变量中取出这个断路器。
通过上面的分析,当new command传入相同的command key时,那么取到的限流器和断路器一定是同一个对象,比如第一个线程获取了一个semaphore,那第二个线程来获取的时候看到的容量就是总容量-1,如果以method name作为command key时,一个method对应了一个限流器和断路器。从上面的获取流程可知,第二次在new的时候是直接从map中取到第一次创建的结果的,因此第二次传入的properties是没用的,不会改变熔断配置。
那么一开始的问题就有了答案
1、是通过静态成员变量做到的
2、以command key维度
3、不会改变之前的配置