Spring Cloud Hystrix 源码系列:隔离策略和请求缓存

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/itsoftchenfei/article/details/87990077

隔离是一种常见的风险控制(保护)手段,Hystrix 也使用了隔离策略,称之为 bulkhead pattern,翻译为:舱壁隔离模式(舱壁将船体内部空间划分成多个舱室,将舱与舱之间严密分开,在航行中,即使有舱破损进水,水也不会流到其他舱)。

1. Hystrix 隔离

Hystrix 的隔离指使用 bulkhead 模式来隔离各个依赖服务之间的调用,同时限制对各个依赖服务的并发访问,使得各依赖服务之间互不影响。Hystrix 提供了两种隔离方式。

1.1 Thread Pool

将各依赖服务的访问交由独立的线程池来处理,会为每个依赖服务创建一个线程池。虽然可以起到很好的隔离作用,但也增加了计算开销,每个命令的执行都涉及到queueing、scheduling、context switching

1.2 Semaphore

通过为各依赖服务设置信号量(或计数器)来限制并发调用,相当于对各依赖服务做限流。信号量模式下任务由当前线程直接处理,不涉及到线程切换,自然也就没有超时控制。

1.3 源码

Spring Cloud Hystrix 源码系列:工作原理,介绍过 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的构建及获取

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); 
                ...
            }
        });
    }
}
abstract class AbstractCommand<R> implements HystrixInvokableInfo<R>, HystrixObservable<R> {
    protected final HystrixThreadPool threadPool;
    //初始化方式
    private static HystrixThreadPool initThreadPool(HystrixThreadPool fromConstructor, HystrixThreadPoolKey threadPoolKey, com.netflix.hystrix.HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults) {
        return fromConstructor == null ? com.netflix.hystrix.HystrixThreadPool.Factory.getInstance(threadPoolKey, threadPoolPropertiesDefaults) : fromConstructor;
    }

}
// 每个threadPoolKey会维护一个HystrixThreadPool
public interface HystrixThreadPool {
    public static class Factory {
        static final ConcurrentHashMap<String, HystrixThreadPool> threadPools = new ConcurrentHashMap();

        public Factory() {
        }

        static HystrixThreadPool getInstance(HystrixThreadPoolKey threadPoolKey, Setter propertiesBuilder) {
            String key = threadPoolKey.name();
            HystrixThreadPool previouslyCached = (HystrixThreadPool)threadPools.get(key);
            if (previouslyCached != null) {
                return previouslyCached;
            } else {
                Class var4 = HystrixThreadPool.class;
                synchronized(HystrixThreadPool.class) {
                    if (!threadPools.containsKey(key)) {
                        threadPools.put(key, new HystrixThreadPool.HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder));
                    }
                }

                return (HystrixThreadPool)threadPools.get(key);
            }
        }
   }
}

信号量隔离

只有在隔离策略为SEMAPHORE时,才会创建TryableSemaphoreActual,否则返回一个什么也不做的TryableSemaphoreNoOp(tryAcquire()将永远返回true)。

protected TryableSemaphore getExecutionSemaphore() {
    if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.SEMAPHORE) {
        ...
    } else {
        // return NoOp implementation since we're not using SEMAPHORE isolation
        return TryableSemaphoreNoOp.DEFAULT;
    }
}
static class TryableSemaphoreActual implements TryableSemaphore {
    protected final HystrixProperty<Integer> numberOfPermits;
    private final AtomicInteger count = new AtomicInteger(0);
    public TryableSemaphoreActual(HystrixProperty<Integer> numberOfPermits) {
        // 每个HystrixCommandKey默认信号量数量,默认10
        this.numberOfPermits = numberOfPermits;
    }
    @Override
    public boolean tryAcquire() {
        int currentCount = count.incrementAndGet();
        // 如果信号量超过设定的信号量,则启动信号量拒绝
        if (currentCount > numberOfPermits.get()) {
            count.decrementAndGet();
            return false;
        } else {
            return true;
        }
    }
    ...
}

2. 请求缓存

微服务中,服务之间的依赖非常多,为了提升性能,如果每个方法都自行处理缓存的话,应用中可以想象有多少累赘的缓存代码。在请求生命周期内,无论是当前线程,还是其他线程,只要请求相同的数据,就自动做缓存,不侵入业务代码。

扫描二维码关注公众号,回复: 5456110 查看本文章

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

**/

缓存的生命周期

缓存是request scope,在同一个请求范围内,所有线程都可以使用相同cacheKey已缓存的结果。为什么是request scope呢?在数据动态变化的情况下,即使参数相同,多次请求访问时,缓存也没有意义。所以只在同一个请求下使用。

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 tempCmd = new HystrixCommandCacheTest("kitty");
            System.out.println("第" + i + " 次执行:" + tempCmd.execute() + ";使用缓存:" + tempCmd.isResponseFromCache());
        }
        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.1 源码

AbstractCommand.toObservable()中关于请求缓存的源码。请求缓存有2个条件,一是启用了请求缓存,二是有cacheKey。整个逻辑还是非常简单的,在启用缓存的前提后,有缓存则读缓存,没缓存则缓存结果供下次使用。

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 {}

HystrixRequestCache实例的存储是由自身的静态变量搞定,未提供public的构造器,通过 getInstance() 的静态方法来获取与cacheKey对应的实例。存储HystrixCachedObservable的数据结构是ConcurrentHashMap,cacheKey作为key,HystrixCachedObservable为value。

再看看缓存的结果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的缓存比较鸡肋。

猜你喜欢

转载自blog.csdn.net/itsoftchenfei/article/details/87990077