隔离是一种常见的风险控制(保护)手段,Hystrix 也使用了隔离策略,称之为 bulkhead pattern,翻译为:舱壁隔离模式(舱壁将船体内部空间划分成多个舱室,将舱与舱之间严密分开,在航行中,即使有舱破损进水,水也不会流到其他舱)。本文基于hystrix-core 1.5.18(近年来几乎很少更新,建议升级)
目录
1. Hystrix 隔离
1.1 Thread Pool
1.2 Semaphore
1.3 源码
2. 请求缓存
2.1 ReplaySubject
2.2 缓存的生命周期
2.3 源码
1. Hystrix 隔离
Hystrix 的隔离指使用 bulkhead 模式来隔离各个依赖服务之间的调用,同时限制对各个依赖服务的并发访问,使得各依赖服务之间互不影响。Hystrix 提供了两种隔离方式。
1.1 Thread Pool
将各依赖服务的访问交由独立的线程池来处理,会为每个依赖服务创建一个线程池。虽然可以起到很好的隔离作用,但也增加了计算开销,每个命令的执行都涉及到queueing、scheduling、context switching。
1.2 Semaphore
通过为各依赖服务设置信号量(或计数器)来限制并发调用,相当于对各依赖服务做限流。信号量模式下任务由当前线程直接处理,不涉及到线程切换,自然也就没有超时控制。
1.3 源码
Spring Cloud Hystrix 源码系列:HystrixCommandAspect入口解析,介绍过 Hystrix 的原理,重点关注executeCommandWithSpecifiedIsolation(),隔离策略在这个方法中实现。
在Hystrix的工作机制中,在判断熔断之后,准备执行任务前,会先判断信号量拒绝和线程池拒绝的情况,如下:
private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {
if (circuitBreaker.attemptExecution()) {
// 获取信号量
final TryableSemaphore executionSemaphore = getExecutionSemaphore();
...
// 尝试获取执行信号,能否执行当前命令
if (executionSemaphore.tryAcquire()) {
...
} else {
return handleSemaphoreRejectionViaFallback();
}
} else {
return handleShortCircuitViaFallback();
}
}
线程池隔离
使用的是rxjava的subscribeOn,重点关注AbstractCommand.threadPool的构建及获取。
之前的HystrixThreadPoolKey也讲过:
具有相同的threadPoolKey的使用同一个线程池,如果注解未指定threadPoolKey,则使用groupKey作为HystrixThreadPoolKey。通过HystrixThreadPoolKey.Factory.asKey(groupKey.name())获取。
private Observable<R> executeCommandWithSpecifiedIsolation(final AbstractCommand<R> _cmd) {
if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.THREAD) {
return Observable.defer(new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
...
// 获取包裹实际Task的Observable
return getUserExecutionObservable(_cmd);
...
}
}
}).doOnTerminate(...)
.doOnUnsubscribe(...)
// 线程池模式执行
.subscribeOn(threadPool.getScheduler(new Func0<Boolean>() {...}));
} else {// 信号量模式在当前线程执行
return Observable.defer(new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
...
return getUserExecutionObservable(_cmd);
...
}
});
}
}
// 每个threadPoolKey会维护一个HystrixThreadPool
public interface HystrixThreadPoolKey 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, HystrixThreadPoolKey> intern
= new InternMap<String, HystrixThreadPoolKey>(
new InternMap.ValueConstructor<String, HystrixThreadPoolKey>() {
@Override
public HystrixThreadPoolKey create(String key) {
return new HystrixThreadPoolKeyDefault(key);
}
});
public static HystrixThreadPoolKey asKey(String name) {
return intern.interned(name);
}
private static class HystrixThreadPoolKeyDefault extends HystrixKeyDefault implements HystrixThreadPoolKey {
public HystrixThreadPoolKeyDefault(String name) {
super(name);
}
}
static int getThreadPoolCount() {
return intern.size();
}
}
}
信号量隔离
隔离策略为SEMAPHORE时会创建TryableSemaphoreActual,否则返回一个什么也不做的TryableSemaphoreNoOp(tryAcquire()将永远返回true)。
//AbstractCommand.toObservable方法中定义applyHystrixSemantics变量
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);
}
};
//在判断熔断之后,准备执行任务前,会先判断信号量拒绝和线程池拒绝的情况
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 */
if (circuitBreaker.allowRequest()) {
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();
}
}
//获取信号量
protected TryableSemaphore getExecutionSemaphore() {
if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.SEMAPHORE) {
TryableSemaphore _s = executionSemaphorePerCircuit.get(commandKey.name());
return _s;
} else {
// return NoOp implementation since we're not using SEMAPHORE isolation
return TryableSemaphoreNoOp.DEFAULT;//无任何实现直接通过
}
}
//信号量,每个HystrixCommandKey默认信号量数量,默认10
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;
}
@Override
public boolean tryAcquire() {
int currentCount = count.incrementAndGet();
// 如果信号量超过设定的信号量,则启动信号量拒绝
if (currentCount > numberOfPermits.get()) {
count.decrementAndGet();
return false;
} else {
return true;
}
}
...
}
2. 请求缓存
微服务中,服务之间的依赖非常多,为了提升性能,如果每个方法都自行处理缓存的话,应用中可以想象有多少累赘的缓存代码。在请求生命周期内,无论是当前线程,还是其他线程,只要请求相同的数据,就自动做缓存,不侵入业务代码。
2.1 ReplaySubject
Hystrix 的请求缓存用的就是RxJava 中的 ReplaySubject 这个特性。replay 译为重放,Subject 是个合体工具,既可以做数据发射器(被观察者、Observable),也可以做数据消费者(观察者、Observer)。如果请求相同数据,就把原先的结果发你一份。
@Test
public void replaySubject() {
ReplaySubject<Integer> replaySubject = ReplaySubject.create();
replaySubject.subscribe(v -> System.out.println("订阅者1:" + v));
replaySubject.onNext(1);
replaySubject.onNext(2);
replaySubject.subscribe(v -> System.out.println("订阅者2:" + v));
replaySubject.onNext(3);
replaySubject.subscribe(v -> System.out.println("订阅者3:" + v));
}
/** 无论是 replaySubject 多久前发射的数据,新的订阅者都可以收到所有数据
订阅者1:1
订阅者1:2
订阅者2:1
订阅者2:2
订阅者1:3
订阅者2:3
订阅者3:1
订阅者3:2
订阅者3:3
**/
2.2 缓存的生命周期
缓存是request scope,在同一个请求范围内(Thread),可以使用相同cacheKey已缓存的结果。
public class HystrixCommandCacheTest extends HystrixCommand<String> {
private final String value;
public HystrixCommandCacheTest(String value) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.value = value;
}
// 重要:要重写getCacheKey()方法
@Override
protected String getCacheKey() {
return value;
}
@Override
protected String run() throws Exception {
return "hello," + value;
}
public static void main(String[] args) {
// 第一个请求环境
HystrixRequestContext context1 = HystrixRequestContext.initializeContext();
HystrixCommandCacheTest cmd1 = new HystrixCommandCacheTest("kitty");
System.out.println("cmd1结果:" + cmd1.execute() + ";使用缓存:" + cmd1.isResponseFromCache());
// 模拟10个相同请求参数的命令执行
for (int i = 0; i < 10; i++) {
HystrixCommandCacheTest temp2 = new HystrixCommandCacheTest("kitty");
System.out.println("第" + i + " 次执行:" + temp2.execute() + ";使用缓存:" + temp2.isResponseFromCache());
}
try {
new Thread(() -> {
HystrixCommandCacheTest tempCmd = new HystrixCommandCacheTest("kitty");
System.out.println("线程2执行:" + tempCmd.execute() + ";使用缓存:" + tempCmd.isResponseFromCache());
}).start();
} catch (Exception ex) {//报错:Request caching is not available. Maybe you need to initialize the HystrixRequestContext?
ex.printStackTrace();
}
context1.shutdown();
// 第二个请求环境
HystrixRequestContext context2 = HystrixRequestContext.initializeContext();
HystrixCommandCacheTest cmd2 = new HystrixCommandCacheTest("kitty");
System.out.println("cmd2结果:" + cmd2.execute() + ";使用缓存:" + cmd2.isResponseFromCache());
}
}
/**
cmd1结果:hello,kitty;使用缓存:false
第0 次执行:hello,kitty;使用缓存:true
第1 次执行:hello,kitty;使用缓存:true
第2 次执行:hello,kitty;使用缓存:true
第3 次执行:hello,kitty;使用缓存:true
第4 次执行:hello,kitty;使用缓存:true
第5 次执行:hello,kitty;使用缓存:true
第6 次执行:hello,kitty;使用缓存:true
第7 次执行:hello,kitty;使用缓存:true
第8 次执行:hello,kitty;使用缓存:true
第9 次执行:hello,kitty;使用缓存:true
cmd2结果:hello,kitty;使用缓存:false
**/
2.3 源码
AbstractCommand.toObservable()中关于请求缓存的源码。请求缓存有2个条件:
- 启用了请求缓存
- 有相同cacheKey
整个逻辑还是非常简单的,在启用缓存的前提后,有缓存则读缓存,没缓存则缓存结果供下次使用。
HystrixRequestCache实例的存储是由自身的静态变量搞定,未提供public的构造器,通过 getInstance() 的静态方法来获取与cacheKey对应的实例。
requestVariableForCache缓存这请求的结果,它的value存储HystrixCachedObservable(数据结构是ConcurrentHashMap,cacheKey作为key,HystrixCachedObservable为value)
public Observable<R> toObservable() {
final AbstractCommand<R> _cmd = this;
...
return Observable.defer(new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
...
final boolean requestCacheEnabled = isRequestCachingEnabled();
final String cacheKey = getCacheKey();
// 启用了requestCache, 则尝试从缓存中获取
if (requestCacheEnabled) {
HystrixCommandResponseFromCache<R> fromCache = (HystrixCommandResponseFromCache<R>) requestCache.get(cacheKey);
if (fromCache != null) {
isResponseFromCache = true;
// 从缓存中获取数据
return handleRequestCacheHitAndEmitValues(fromCache, _cmd);
}
}
Observable<R> hystrixObservable =
Observable.defer(applyHystrixSemantics)
.map(wrapWithAllOnNextHooks);
Observable<R> afterCache;
// 启用缓存而且有cacheKey
if (requestCacheEnabled && cacheKey != null) {
// 使用HystrixCachedObservable来包装Obervable,并且添加到requestCache中
HystrixCachedObservable<R> toCache = HystrixCachedObservable.from(hystrixObservable, _cmd);
HystrixCommandResponseFromCache<R> fromCache = (HystrixCommandResponseFromCache<R>) requestCache.putIfAbsent(cacheKey, toCache);
...
afterCache = toCache.toObservable();
}
...
}
});
// 缓存的工具
/**
Cache that is scoped to the current request as managed by HystrixRequestVariableDefault.
This is used for short-lived caching of HystrixCommand instances to allow de-duping of command executions within a request.
缓存仅在请求范围内使用,主要用途是减少HystrixCommand实例的执行次数(缓存结果后执行次数自然少了)
**/
public class HystrixRequestCache {
private final static ConcurrentHashMap<RequestCacheKey, HystrixRequestCache> caches = new ConcurrentHashMap<RequestCacheKey, HystrixRequestCache>();
/**
* A ConcurrentHashMap per 'prefix' and per request scope that is used to to dedupe requests in the same request.
* <p>
* Key => CommandPrefix + CacheKey : Future<?> from queue()
*/
private static final HystrixRequestVariableHolder<ConcurrentHashMap<ValueCacheKey, HystrixCachedObservable<?>>> requestVariableForCache = new HystrixRequestVariableHolder<ConcurrentHashMap<ValueCacheKey, HystrixCachedObservable<?>>>(new HystrixRequestVariableLifecycle<ConcurrentHashMap<ValueCacheKey, HystrixCachedObservable<?>>>() {
@Override
public ConcurrentHashMap<ValueCacheKey, HystrixCachedObservable<?>> initialValue() {
return new ConcurrentHashMap<ValueCacheKey, HystrixCachedObservable<?>>();
}
@Override
public void shutdown(ConcurrentHashMap<ValueCacheKey, HystrixCachedObservable<?>> value) {
// nothing to shutdown
}
});
public static HystrixRequestCache getInstance(HystrixCommandKey key, HystrixConcurrencyStrategy concurrencyStrategy) {
return getInstance(new RequestCacheKey(key, concurrencyStrategy), concurrencyStrategy);
}
}
再看看缓存的结果HystrixCachedObservable,这个就用到了上面提过的ReplaySubject。将一个普通的Observable包装成了HystrixCachedObservable。因此,command执行一次拿到结果来自于ReplaySubject。后续无论有多少次订阅(即执行command),都把已有的结果推送一次,从而达到缓存请求结果的效果。
public class HystrixCachedObservable<R> {
protected final Subscription originalSubscription;
protected final Observable<R> cachedObservable;
private volatile int outstandingSubscriptions = 0;
protected HystrixCachedObservable(final Observable<R> originalObservable) {
ReplaySubject<R> replaySubject = ReplaySubject.create();
// 订阅普通的Observable, 拿到其中的数据
this.originalSubscription = originalObservable
.subscribe(replaySubject);
this.cachedObservable = replaySubject...
}
...
// 将cachedObservable作为数据源提供出去, 完成普通Observable向ReplaySubject的转换
public Observable<R> toObservable() {
return cachedObservable;
}
}
如何使用缓存的结果
由于toObservable()拿到的是一个ReplaySubject,下次命令再次执行时,订阅ReplaySubject后,可以直接拿到之前已有的结果。
// 以HystrixCommand的 queue() 方法为例
public Future<R> queue() {
// 调用 toObservable 拿到数据源
final Future<R> delegate = toObservable().toBlocking().toFuture();
...
}
//在toFuture()中会订阅这个数据源:
public static <T> Future<T> toFuture(Observable<? extends T> that) {
final CountDownLatch finished = new CountDownLatch(1);
final AtomicReference<T> value = new AtomicReference<T>();
final AtomicReference<Throwable> error = new AtomicReference<Throwable>();
// 首先,通过single()确保从Observable中拿到单个结果. 然后订阅数据源
@SuppressWarnings("unchecked")
final Subscription s = ((Observable<T>)that).single().subscribe(new Subscriber<T>() {
@Override
public void onNext(T v) {
// 拿到执行的结果后放到AtomicReference中
value.set(v);
}
});
return new Future<T>() {
private volatile boolean cancelled;
// 返回执行结果
@Override
public T get() throws InterruptedException, ExecutionException {
finished.await();
return getValue();
}
};
}
利用缓存可以极大的提升性能,但是Hystrix的缓存比较鸡肋。