02深度解析Spring Cloud Hystrix---hystrixCommandAspect詳解
總結
主要分析了 信號量和線程池 隔離機制
一、HystrixCommandAspect類
我們首先看一下 主要的方法methodsAnnotatedWithHystrixCommand ,其所有代碼如下,可以知道這個方法是對 @HystrixCommand 和 @HystrixCollapser 進行攔截,接下來我們先把其他的一些輔助點先介紹
@Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
xxx .... xxxx
}
1.1 @HystrixCommand註解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HystrixCommand {
//組 的名稱 默認是 註解的方法所在類 的類名
String groupKey() default "";
//命令名稱 默認就是 @HystrixCommand 所在的方法名
String commandKey() default "";
// 線程池名稱,主要是來表示 HystrixThreadPool 監控,收集等
String threadPoolKey() default "";
/**回調方法,就是 失敗或者超時時的方法
這個方法必須和 @HystrixCommand 註釋的方法在同一個類裏面
並且有一樣的入參和出參
*/
String fallbackMethod() default "";
// 配置相關的參數 ,以Key-Value 的形式,可配置多個,是數組形式
HystrixProperty[] commandProperties() default {};
//線程池的屬性配置,也是key-value形式,可配置多個
HystrixProperty[] threadPoolProperties() default {};
// 定義哪些異常可以忽略
Class<? extends Throwable>[] ignoreExceptions() default {};
/**
*定義 用哪種模式 運行hystrix observable command.
* eager - {@link HystrixObservable#observe()},
* lazy - {@link HystrixObservable#toObservable()}.
*/
ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;
HystrixException[] raiseHystrixExceptions() default {};
/*
默認的回退方法,如果fallbackMethod 和defaultFallback 同時指定了,fallbackMethod優先
默認的回退方法 不能有入參,出參需要保持一致
*/
String defaultFallback() default "";
}
1.2 @HystrixCollapser 註解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HystrixCollapser {
//合併名稱,默認是 註解的方法名稱
String collapserKey() default "";
/* 合併的方法名稱
合併的方法必須 要是這種特徵:
java.util.List method(java.util.List)
合併的方法只能有一個 參數
*/
String batchMethod();
/**
範圍, 默認是一次請求,可以改成 GLOBAL 全局的
*/
Scope scope() default Scope.REQUEST;
/**
* 合併的相關屬性配置
* 以 key-value 形式配置,可以配置多個,數組
*/
HystrixProperty[] collapserProperties() default {};
}
1.3 MetaHolderFactory
我們從 HystrixCommandAspect 類裏面有一個 META_HOLDER_FACTORY_MAP屬性,裏面存儲的 是 不同的類型對應的 MetaHolder的Factory
@Aspect
public class HystrixCommandAspect {
private static final Map<HystrixPointcutType, MetaHolderFactory> META_HOLDER_FACTORY_MAP;
static {
META_HOLDER_FACTORY_MAP = ImmutableMap.<HystrixPointcutType, MetaHolderFactory>builder()
.put(HystrixPointcutType.COMMAND, new CommandMetaHolderFactory())
.put(HystrixPointcutType.COLLAPSER, new CollapserMetaHolderFactory())
.build();
}
}
CommandMetaHolderFactory 和 CollapserMetaHolderFactory 都是繼承了 抽象類MetaHolderFactory,作用就是 根據不同的類型創建一個MetaHolder ,MetaHolder 裏面包含了當前方法 所需的各種必要信息 ,主要分析一下各自的 create 方法即可
CommandMetaHolderFactory 的create 方法邏輯如下:
- 獲取@HystrixCommand註解的屬性
- 根據方法的返回類型判斷 是 同步還是異步 ,這裏有三種方式 同步(SYNCHRONOUS),異步(ASYNCHRONOUS),響應式(OBSERVABLE) 如果是 Future.class返回類型的爲異步
如果是 (Observable.class, Single.class, Completable.class) 這三種類型中一種,爲OBSERVABLE ,剩下的類型爲 同步方式 - 構建 MetaHolder ,將各種必須的參數 填入 MetaHolder 對象裏面,同時獲取 指定的 FallbackMethod 並進行填充,並對 FallbackMethod 進行校驗,比如返回類型的校驗
- 獲取@DefaultProperties 註解裏面的配置對原先的進行覆蓋,這個優先級比@HystrixCommand高
private static class CommandMetaHolderFactory extends MetaHolderFactory {
@Override
public MetaHolder create(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) {
HystrixCommand hystrixCommand = method.getAnnotation(HystrixCommand.class);
ExecutionType executionType = ExecutionType.getExecutionType(method.getReturnType());
MetaHolder.Builder builder = metaHolderBuilder(proxy, method, obj, args, joinPoint);
if (isCompileWeaving()) {
builder.ajcMethod(getAjcMethodFromTarget(joinPoint));
}
return builder.defaultCommandKey(method.getName())
.hystrixCommand(hystrixCommand)
.observableExecutionMode(hystrixCommand.observableExecutionMode())
.executionType(executionType)
.observable(ExecutionType.OBSERVABLE == executionType)
.build();
}
}
1.4 methodsAnnotatedWithHystrixCommand方法
@Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
Method method = getMethodFromTarget(joinPoint);
Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint);
if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) {
throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " +
"annotations at the same time");
}
MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method));
MetaHolder metaHolder = metaHolderFactory.create(joinPoint);
HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?
metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();
Object result;
try {
if (!metaHolder.isObservable()) {
result = CommandExecutor.execute(invokable, executionType, metaHolder);
} else {
result = executeObservable(invokable, executionType, metaHolder);
}
} catch (HystrixBadRequestException e) {
throw e.getCause() != null ? e.getCause() : e;
} catch (HystrixRuntimeException e) {
throw hystrixRuntimeExceptionToThrowable(metaHolder, e);
}
return result;
}
主要流程如下:
- 獲取到對應的方法Method
- 如果方法的註解上面同時有 @HystrixCommand 和@HystrixCollapser 註解,拋出異常
- 根據不同的註解,選擇對應的metaHolderFactory ,創建MetaHolder ,MetaHolder 裏面涵蓋了所有的 必須的信息
- 獲取執行方式 executionType
- CommandExecutor.execute(invokable, executionType, metaHolder);先通過castToExecutable轉換成HystrixExecutable類型 ,再執行運行execute();
- 接下來 主要的重點就是 execute()
- execute() 方法 就是 queue().get(); 接下來 是RxJava 的編程風格 ,就挑選主要的重點
- queue() 還是 調用了 final Future delegate = toObservable().toBlocking().toFuture();
關於RxJava,解釋一些基本的概念
核心思想和Java的觀察者模式非常像:被觀察者和觀察者通過訂閱產生一種關係,當被觀察者發生一些改變,通知觀察者,觀察者對應做出相應的迴應。
主要有三個關鍵詞需要記住:被觀察者(Observable),訂閱(subscribe),觀察者(Observer)
方法介紹:
R execute():同步執行,從依賴服務得到單一結果對象.
Future queue():異步執行,返回一個 Future 以便獲取執行結果,也是單一結果對象.
Observable observe():創建Observable後會訂閱Observable,可以返回多個結果.
Observable toObservable():返回一個Observable,只有訂閱時纔會執行,可以返回多個結果.
Defer 操作符會一直等待直到有觀察者訂閱它,然後它使用Observable工廠方法生成一個Observable
二、運行流程
2.1 toObservable()方法
public Observable<R> toObservable() {
final AbstractCommand<R> _cmd = this;
xxx...xxxx
final Func0<Observable<R>> applyHystrixSemantics = new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
if (commandState.get().equals(CommandState.UNSUBSCRIBED)) {
return Observable.never();
}
return applyHystrixSemantics(_cmd);
}
};
return Observable.defer(new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
xxx...xxxx
Observable<R> hystrixObservable =
Observable.defer(applyHystrixSemantics)
.map(wrapWithAllOnNextHooks);
Observable<R> afterCache;
// put in cache
if (requestCacheEnabled && cacheKey != null) {
// wrap it for caching
HystrixCachedObservable<R> toCache = HystrixCachedObservable.from(hystrixObservable, _cmd);
HystrixCommandResponseFromCache<R> fromCache = (HystrixCommandResponseFromCache<R>) requestCache.putIfAbsent(cacheKey, toCache);
if (fromCache != null) {
// another thread beat us so we'll use the cached value instead
toCache.unsubscribe();
isResponseFromCache = true;
return handleRequestCacheHitAndEmitValues(fromCache, _cmd);
} else {
// we just created an ObservableCommand so we cast and return it
afterCache = toCache.toObservable();
}
} else {
afterCache = hystrixObservable;
}
return afterCache
.doOnTerminate(terminateCommandCleanup) // perform cleanup once (either on normal terminal state (this line), or unsubscribe (next line))
.doOnUnsubscribe(unsubscribeCommandCleanup) // perform cleanup once
.doOnCompleted(fireOnCompletedHook);
}
});
}
toObservable() 方法 return Observable.defer ,在call 方法裏面 的 afterCache 又是Observable.defer(applyHystrixSemantics),所以定位到 方法applyHystrixSemantics(_cmd);
2.2 applyHystrixSemantics()方法
private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {
... xxx ... xxx
// 判斷斷路器是否開啓 ,這裏也有好幾種情況
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();
}
}
};
//判斷是否可以訪問,如果是線程隔離,默認是true ,如果是信號量,需要判斷當前是否可以,具體邏輯在下面
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 {
//直接進入Fallback處理邏輯。
return handleShortCircuitViaFallback();
}
}
獲取當前的隔離type ,然後再判斷tryAcquire() 是否有權限,如果是信號量隔離,邏輯在下面 ,如果是 TryableSemaphoreNoOp ,tryAcquire永遠是 true, 我們繼續分析線程池隔離,代碼追蹤如下:
2.3 TryableSemaphore 接口
TryableSemaphore 接口裏面的 方法比較 簡單,就是 一個判斷是否可以訪問,一個釋放資源,一個獲取當前的使用的個數
static interface TryableSemaphore {
public abstract boolean tryAcquire();
public abstract void release();
public abstract int getNumberOfPermitsUsed();
}
從結構上可以看出 被 TryableSemaphore 被兩個實現了,一個是 TryableSemaphoreActual,一個是TryableSemaphoreNoOp,TryableSemaphoreNoOp 是沒有操作的(tryAcquire()永遠true),主要看一下TryableSemaphoreActual
2.3.1 TryableSemaphoreActual 信號量
static class TryableSemaphoreActual implements TryableSemaphore {
protected final HystrixProperty<Integer> numberOfPermits;
private final AtomicInteger count = new AtomicInteger(0);
public TryableSemaphoreActual(HystrixProperty<Integer> numberOfPermits) {
this.numberOfPermits = numberOfPermits;
}
//先+1 ,然後 和numberOfPermits (默認10個)比較大小,
// 大於numberOfPermits ,減1 並返回 false,反之返回 true
@Override
public boolean tryAcquire() {
int currentCount = count.incrementAndGet();
if (currentCount > numberOfPermits.get()) {
count.decrementAndGet();
return false;
} else {
return true;
}
}
@Override
public void release() {
count.decrementAndGet();
}
@Override
public int getNumberOfPermitsUsed() {
return count.get();
}
}
主要邏輯就是:
- 當前數量 先+1 ,然後 和numberOfPermits (默認10個)比較大小
- 大於numberOfPermits ,減1 並返回 false
- 反之返回 true
信號量的判斷 ,如果是false ,就走拒絕策略,同時也可以看出 信號量的判斷,沒有請求時間長短的過濾判斷
2.4 線程池相關
在上面我們已經定位到了 getUserExecutionObservable方法,主要就是HystrixCommand的 run() 方法,這裏的具體邏輯在
@Override
final protected Observable<R> getExecutionObservable() {
return Observable.defer(new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
try {
return Observable.just(run());
} catch (Throwable ex) {
return Observable.error(ex);
}
}
}).doOnSubscribe(new Action0() {
@Override
public void call() {
// Save thread on which we get subscribed so that we can interrupt it later if needed
executionThread.set(Thread.currentThread());
}
});
}
2.4.1 threadPool
threadPool 是 AbstractCommand 的構造函數 在 實例的時候 直接初始化了,具體方法爲:
this.threadPool = initThreadPool(threadPool, this.threadPoolKey, threadPoolPropertiesDefaults);
private static HystrixThreadPool initThreadPool(HystrixThreadPool fromConstructor, HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults) {
if (fromConstructor == null) {
// get the default implementation of HystrixThreadPool
return HystrixThreadPool.Factory.getInstance(threadPoolKey, threadPoolPropertiesDefaults);
} else {
return fromConstructor;
}
}
在獲取數據的時候,用了一個 Map做了緩存,key 就是 threadPoolKey,Value 就是 HystrixThreadPool
final static ConcurrentHashMap<String, HystrixThreadPool> threadPools = new ConcurrentHashMap<String, HystrixThreadPool>();
static HystrixThreadPool getInstance(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesBuilder) {
// get the key to use instead of using the object itself so that if people forget to implement equals/hashcode things will still work
String key = threadPoolKey.name();
// this should find it for all but the first time
HystrixThreadPool previouslyCached = threadPools.get(key);
if (previouslyCached != null) {
return previouslyCached;
}
// if we get here this is the first time so we need to initialize
synchronized (HystrixThreadPool.class) {
if (!threadPools.containsKey(key)) {
threadPools.put(key, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder));
}
}
return threadPools.get(key);
}
分析到這裏 ,線程池隔離的大致流程也已經清晰
- 在實例AbstractCommand 的時候,就會根據 配置的線程池參數創建一個線程大小,同時制定 一個 threadPoolKey ,沒有設置就是方法名稱,
- 存放到 對應的內存緩存裏面
- 調用的時候就從裏面取
這裏分析了 線程池相關的,下面貼一張 官方原圖,
Hystrix 的官方地址