在开始隔离策略的源码分析之前,先了解下Hystrix的类组织形式。在Spring中配置的Bean都有一个scop属性,默认是singleton
。保证在一个IOC容器中,一个class只有一个实例,减少内存的占用,同时又利用GC。另外:Spring中的单例模式和普通的单例模式是不一样的,在Spring中是一个IOC容器中只有一个实例,但是一个JVM可以有多个IOC容器。而普通的单例是指一个JVM中只能有一个实例。
同样在Hystrix内部,也是用的单例模式。每个接口类文件中都有一个Factory,和该接口的实现类,通过该接口文件中的Factory的获取接口的具体实现对象,从而操作对象的相关方法。Factory中持有一个静态的ConCurrentHash类型的常量,当需要一个接口的实例时,如果Map中没有,则new一个putIfAbsent到map中。
这里有一个小技巧,new对象是放在putIfAbsent方法里的,所以是局部变量,不存在线程安全问题。putIfAbsent又是ConcurrentHashMap提供的方法,内部的通过Segment对同步做了优化,同时又保证了线程安全,所以在初始化对象时不需要Synchornized
。如果同时有两个线程 访问putIfAbsent方法时,只会有一个能返回instance ,另一个返回null 。 只需要对访问结果进行非null判断即可,如果返回null就表示map中已经有对象了,get即可。如:
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;
}
在上一章节中设置CommandGroupKey时,首先需要把String转换成HystrixCommandGroupKey对象,这里是通过withGroupKey(HystrixCommandGroupKey.Factory.asKey("hello"))
,同样其他对象也是通过XXX.Factory.xx操作。在Hystrix内部所有的对象都是通过xxx(接口).Factory.getInstance()操作的。
在Hystrix中,类的引用都是单实例对象,由静态工厂方法创建,使用时如:xxx.Factory.getInstance() 。如获取一个HystrixCircuitBreaker实例:HystrixCircuitBreaker
circuitBreaker = HystrixCircuitBreaker.Factory.getInstance(key);
接口、Factory(工厂类)、实现通常都是在一个类文件中,下面是HystrixCircuitBreaker这个类的文件内容:
public interface HystrixCircuitBreaker {
/**
* Every {@link HystrixCommand} requests asks this if it is allowed to proceed or not.
* <p>
* This takes into account the half-open logic which allows some requests through when determining if it should be closed again.
*
* @return boolean whether a request should be permitted
*/
public boolean allowRequest();
/**
* Whether the circuit is currently open (tripped).
*
* @return boolean state of circuit breaker
*/
public boolean isOpen();
/**
* Invoked on successful executions from {@link HystrixCommand} as part of feedback mechanism when in a half-open state.
*/
/* package */void markSuccess();
/**
* @ExcludeFromJavadoc
* @ThreadSafe
*/
public static class Factory {
// String is HystrixCommandKey.name() (we can't use HystrixCommandKey directly as we can't guarantee it implements hashcode/equals correctly)
private static ConcurrentHashMap<String, HystrixCircuitBreaker> circuitBreakersByCommand = new ConcurrentHashMap<String, HystrixCircuitBreaker>();
/**
* Get the {@link HystrixCircuitBreaker} instance for a given {@link HystrixCommandKey}.
* <p>
* This is thread-safe and ensures only 1 {@link HystrixCircuitBreaker} per {@link HystrixCommandKey}.
*
* @param key
* {@link HystrixCommandKey} of {@link HystrixCommand} instance requesting the {@link HystrixCircuitBreaker}
* @param group
* Pass-thru to {@link HystrixCircuitBreaker}
* @param properties
* Pass-thru to {@link HystrixCircuitBreaker}
* @param metrics
* Pass-thru to {@link HystrixCircuitBreaker}
* @return {@link HystrixCircuitBreaker} for {@link HystrixCommandKey}
*/
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;
}
}
/**
* Get the {@link HystrixCircuitBreaker} instance for a given {@link HystrixCommandKey} or null if none exists.
*
* @param key
* {@link HystrixCommandKey} of {@link HystrixCommand} instance requesting the {@link HystrixCircuitBreaker}
* @return {@link HystrixCircuitBreaker} for {@link HystrixCommandKey}
*/
public static HystrixCircuitBreaker getInstance(HystrixCommandKey key) {
return circuitBreakersByCommand.get(key.name());
}
/**
* Clears all circuit breakers. If new requests come in instances will be recreated.
*/
/* package */static void reset() {
circuitBreakersByCommand.clear();
}
}
/**
* The default production implementation of {@link HystrixCircuitBreaker}.
*
* @ExcludeFromJavadoc
* @ThreadSafe
*/
/* package */static class HystrixCircuitBreakerImpl implements HystrixCircuitBreaker {
private final HystrixCommandProperties properties;
private final HystrixCommandMetrics metrics;
/* track whether this circuit is open/closed at any given point in time (default to false==closed) */
private AtomicBoolean circuitOpen = new AtomicBoolean(false);
/* when the circuit was marked open or was last allowed to try a 'singleTest' */
private AtomicLong circuitOpenedOrLastTestedTime = new AtomicLong();
protected HystrixCircuitBreakerImpl(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
this.properties = properties;
this.metrics = metrics;
}
public void markSuccess() {
if (circuitOpen.get()) {
// TODO how can we can do this without resetting the counts so we don't lose metrics of short-circuits etc?
metrics.resetCounter();
// If we have been 'open' and have a success then we want to close the circuit. This handles the 'singleTest' logic
circuitOpen.set(false);
}
}
@Override
public boolean allowRequest() {
if (properties.circuitBreakerForceOpen().get()) {
// properties have asked us to force the circuit open so we will allow NO requests
return false;
}
if (properties.circuitBreakerForceClosed().get()) {
// we still want to allow isOpen() to perform it's calculations so we simulate normal behavior
isOpen();
// properties have asked us to ignore errors so we will ignore the results of isOpen and just allow all traffic through
return true;
}
return !isOpen() || allowSingleTest();
}
public boolean allowSingleTest() {
long timeCircuitOpenedOrWasLastTested = circuitOpenedOrLastTestedTime.get();
// 1) if the circuit is open
// 2) and it's been longer than 'sleepWindow' since we opened the circuit
if (circuitOpen.get() && System.currentTimeMillis() > timeCircuitOpenedOrWasLastTested + properties.circuitBreakerSleepWindowInMilliseconds().get()) {
// We push the 'circuitOpenedTime' ahead by 'sleepWindow' since we have allowed one request to try.
// If it succeeds the circuit will be closed, otherwise another singleTest will be allowed at the end of the 'sleepWindow'.
if (circuitOpenedOrLastTestedTime.compareAndSet(timeCircuitOpenedOrWasLastTested, System.currentTimeMillis())) {
// if this returns true that means we set the time so we'll return true to allow the singleTest
// if it returned false it means another thread raced us and allowed the singleTest before we did
return true;
}
}
return false;
}
@Override
public boolean isOpen() {
if (circuitOpen.get()) {
// if we're open we immediately return true and don't bother attempting to 'close' ourself as that is left to allowSingleTest and a subsequent successful test to close
return true;
}
// we're closed, so let's see if errors have made us so we should trip the circuit open
HealthCounts health = metrics.getHealthCounts();
// check if we are past the statisticalWindowVolumeThreshold
if (health.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
// we are not past the minimum volume threshold for the statisticalWindow so we'll return false immediately and not calculate anything
return false;
}
if (health.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
return false;
} else {
// our failure rate is too high, trip the circuit
if (circuitOpen.compareAndSet(false, true)) {
// if the previousValue was false then we want to set the currentTime
circuitOpenedOrLastTestedTime.set(System.currentTimeMillis());
return true;
} else {
// How could previousValue be true? If another thread was going through this code at the same time a race-condition could have
// caused another thread to set it to true already even though we were in the process of doing the same
// In this case, we know the circuit is open, so let the other thread set the currentTime and report back that the circuit is open
return true;
}
}
}
}
/**
* An implementation of the circuit breaker that does nothing.
*
* @ExcludeFromJavadoc
*/
/* package */static class NoOpCircuitBreaker implements HystrixCircuitBreaker {
@Override
public boolean allowRequest() {
return true;
}
@Override
public boolean isOpen() {
return false;
}
@Override
public void markSuccess() {
}
}
}
再来看一下,感觉很有意思的类
package com.netflix.hystrix;
/**
* Basic class for hystrix keys
*/
public interface HystrixKey {
/**
* The word 'name' is used instead of 'key' so that Enums can implement this interface and it work natively.
*
* @return String
*/
String name();
/**
* Default implementation of the interface
*/
abstract class HystrixKeyDefault implements HystrixKey {
private final String name;
public HystrixKeyDefault(String name) {
this.name = name;
}
@Override
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
}
}
Hystrix中使用了大量这种内部类实现接口的形式非常的方便,可以在一个接口中完成默认的接口实现,,,也可以同时完成多个接口实现。。再来一个看看。。单例实现的也非同寻常。。而且实现了类似AOP功能中的容器功能。。。
package com.netflix.hystrix;
import com.netflix.hystrix.util.InternMap;
/**
* A group name for a {@link HystrixCommand}. This is used for grouping together commands such as for reporting, alerting, dashboards or team/library ownership.
* <p>
* By default this will be used to define the {@link HystrixThreadPoolKey} unless a separate one is defined.
* <p>
* This interface is intended to work natively with Enums so that implementing code can have an enum with the owners that implements this interface.
*/
public interface HystrixCommandGroupKey extends HystrixKey {
class Factory {
private Factory() {
}
// used to intern instances so we don't keep re-creating them millions of times for the same key
private static final InternMap<String, HystrixCommandGroupDefault> intern
= new InternMap<String, HystrixCommandGroupDefault>(
new InternMap.ValueConstructor<String, HystrixCommandGroupDefault>() {
@Override
public HystrixCommandGroupDefault create(String key) {
return new HystrixCommandGroupDefault(key);
}
});
/**
* Retrieve (or create) an interned HystrixCommandGroup instance for a given name.
*
* @param name command group name
* @return HystrixCommandGroup instance that is interned (cached) so a given name will always retrieve the same instance.
*/
public static HystrixCommandGroupKey asKey(String name) {
return intern.interned(name);
}
private static class HystrixCommandGroupDefault extends HystrixKey.HystrixKeyDefault implements HystrixCommandGroupKey {
public HystrixCommandGroupDefault(String name) {
super(name);
}
}
/* package-private */ static int getGroupCount() {
return intern.size();
}
}
}
package com.netflix.hystrix.util;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Utility to have 'intern' - like functionality, which holds single instance of wrapper for a given key
*/
public class InternMap<K, V> {
private final ConcurrentMap<K, V> storage = new ConcurrentHashMap<K, V>();
private final ValueConstructor<K, V> valueConstructor;
public interface ValueConstructor<K, V> {
V create(K key);
}
public InternMap(ValueConstructor<K, V> valueConstructor) {
this.valueConstructor = valueConstructor;
}
public V interned(K key) {
V existingKey = storage.get(key);
V newKey = null;
if (existingKey == null) {
newKey = valueConstructor.create(key);
existingKey = storage.putIfAbsent(key, newKey);
}
return existingKey != null ? existingKey : newKey;
}
public int size() {
return storage.size();
}
}
再看看一个类中实现责任链模式的类。。。
/**
* Fluent interface for arguments to the {@link HystrixCommand} constructor.
* <p>
* The required arguments are set via the 'with' factory method and optional arguments via the 'and' chained methods.
* <p>
* Example:
* <pre> {@code
* Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GroupName"))
.andCommandKey(HystrixCommandKey.Factory.asKey("CommandName"));
* } </pre>
*
* @NotThreadSafe
*/
final public static class Setter {
protected final HystrixCommandGroupKey groupKey;
protected HystrixCommandKey commandKey;
protected HystrixThreadPoolKey threadPoolKey;
protected HystrixCommandProperties.Setter commandPropertiesDefaults;
protected HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults;
/**
* Setter factory method containing required values.
* <p>
* All optional arguments can be set via the chained methods.
*
* @param groupKey
* {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixCommand} objects.
* <p>
* The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace
* with,
* common business purpose etc.
*/
protected Setter(HystrixCommandGroupKey groupKey) {
this.groupKey = groupKey;
}
/**
* Setter factory method with required values.
* <p>
* All optional arguments can be set via the chained methods.
*
* @param groupKey
* {@link HystrixCommandGroupKey} used to group together multiple {@link HystrixCommand} objects.
* <p>
* The {@link HystrixCommandGroupKey} is used to represent a common relationship between commands. For example, a library or team name, the system all related commands interace
* with,
* common business purpose etc.
*/
public static Setter withGroupKey(HystrixCommandGroupKey groupKey) {
return new Setter(groupKey);
}
/**
* @param commandKey
* {@link HystrixCommandKey} used to identify a {@link HystrixCommand} instance for statistics, circuit-breaker, properties, etc.
* <p>
* By default this will be derived from the instance class name.
* <p>
* NOTE: Every unique {@link HystrixCommandKey} will result in new instances of {@link HystrixCircuitBreaker}, {@link HystrixCommandMetrics} and {@link HystrixCommandProperties}.
* Thus,
* the number of variants should be kept to a finite and reasonable number to avoid high-memory usage or memory leacks.
* <p>
* Hundreds of keys is fine, tens of thousands is probably not.
* @return Setter for fluent interface via method chaining
*/
public Setter andCommandKey(HystrixCommandKey commandKey) {
this.commandKey = commandKey;
return this;
}
/**
* @param threadPoolKey
* {@link HystrixThreadPoolKey} used to define which thread-pool this command should run in (when configured to run on separate threads via
* {@link HystrixCommandProperties#executionIsolationStrategy()}).
* <p>
* By default this is derived from the {@link HystrixCommandGroupKey} but if injected this allows multiple commands to have the same {@link HystrixCommandGroupKey} but different
* thread-pools.
* @return Setter for fluent interface via method chaining
*/
public Setter andThreadPoolKey(HystrixThreadPoolKey threadPoolKey) {
this.threadPoolKey = threadPoolKey;
return this;
}
/**
* Optional
*
* @param commandPropertiesDefaults
* {@link HystrixCommandProperties.Setter} with property overrides for this specific instance of {@link HystrixCommand}.
* <p>
* See the {@link HystrixPropertiesStrategy} JavaDocs for more information on properties and order of precedence.
* @return Setter for fluent interface via method chaining
*/
public Setter andCommandPropertiesDefaults(HystrixCommandProperties.Setter commandPropertiesDefaults) {
this.commandPropertiesDefaults = commandPropertiesDefaults;
return this;
}
/**
* Optional
*
* @param threadPoolPropertiesDefaults
* {@link HystrixThreadPoolProperties.Setter} with property overrides for the {@link HystrixThreadPool} used by this specific instance of {@link HystrixCommand}.
* <p>
* See the {@link HystrixPropertiesStrategy} JavaDocs for more information on properties and order of precedence.
* @return Setter for fluent interface via method chaining
*/
public Setter andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults) {
this.threadPoolPropertiesDefaults = threadPoolPropertiesDefaults;
return this;
}
}