服务降级熔断 - 请求缓存

前言

相信大家对Hystrix都很熟悉,它的源码大量使用RxJava,正好笔者的老本行是Android开发工程师,以前也略微接触过,想分享下自己看完Hystix的请求合并与请求缓存部分源码的一些收获。

Hystrix简介

  • Hystrix由Netflix开源,官方定义如下:

Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.

  • 笔者理解:在分布式环境中,错误不可避免,Hystrix提供了一种隔离、降级、熔断等机制。

1、隔离:通过隔离,避免服务之间相互影响,一个服务不可用,不会影响别的服务,避免了服务雪崩。 2、降级:分布式环境中,服务不可用的情况无法避免,降级机制可以给出更加友好的交互(默认值、异常返回)。 3、熔断:熔断机制可以避免在服务不可用时,服务调用方还在调用不可用的服务,导致资源消耗、耗时增加。 4、提供可视化的监控,Hystrix Dashboard。 4、当然,还有笔者今天要讲的请求合并与请求缓存

  • 请求合并与请求缓存,对应于官方给出的What does it do?的第3项:

Parallel execution. Concurrency aware request caching. Automated batching through request collapsing.

  • 以下都是通过官方给的测试用例作为入口,查找源码并进行分析。

1、请求缓存:CommandUsingRequestCache 2、请求合并:CommandCollapserGetValueForKey

请求缓存

  • 请求缓存的例子在CommandUsingRequestCache,继承自HystrixCommand,和一般的Command一致。
  • 那么,使用缓存和不使用缓存代码层面有何不同呢?

1、初始化HystrixRequestContext 2、重写getCacheKey

HystrixRequestContext

  • HystrixRequestContext.initializeContext代码在HystrixRequestContext中,从类名可以看出这是个请求上下文,保存一些请求的信息。

  • 从源码可以看出,new出一个HystrixRequestContext,塞入ThreadLocal变量中。


    private static ThreadLocal<HystrixRequestContext> requestVariables = new ThreadLocal<HystrixRequestContext>();

/**
     * Call this at the beginning of each request (from parent thread)
     * to initialize the underlying context so that {@link HystrixRequestVariableDefault} can be used on any children threads and be accessible from
     * the parent thread.
     * <p>
     * <b>NOTE: If this method is called then <code>shutdown()</code> must also be called or a memory leak will occur.</b>
     * <p>
     * See class header JavaDoc for example Servlet Filter implementation that initializes and shuts down the context.
     */
    public static HystrixRequestContext initializeContext() {
        HystrixRequestContext state = new HystrixRequestContext();
        requestVariables.set(state);
        return state;
    }
  • 那么,HystrixRequestContext存储上下文的数据结构是怎样的呢?
// 每个HystrixRequestContext实例,都会有一个ConcurrentMap
ConcurrentHashMap<HystrixRequestVariableDefault<?>, HystrixRequestVariableDefault.LazyInitializer<?>> state = new ConcurrentHashMap<HystrixRequestVariableDefault<?>, HystrixRequestVariableDefault.LazyInitializer<?>>();

/**
     删除ConcurrentMap中存储的所有键值对,如果初始化了HystrixRequestContext对象,没有调用shutdown方法,确实会导致内存泄漏,因为state还在。
     */
    public void shutdown() {
        if (state != null) {
            for (HystrixRequestVariableDefault<?> v : state.keySet()) {
                // for each RequestVariable we call 'remove' which performs the shutdown logic
                try {
                    HystrixRequestVariableDefault.remove(this, v);
                } catch (Throwable t) {
                    HystrixRequestVariableDefault.logger.error("Error in shutdown, will continue with shutdown of other variables", t);
                }
            }
            // null out so it can be garbage collected even if the containing object is still
            // being held in ThreadLocals on threads that weren't cleaned up
            state = null;
        }
    }
  • 这个ConcurrentHashMap里存的HystrixRequestVariableDefault及静态内部类HystrixRequestVariableDefault.LazyInitializer又是什么呢?

HystrixRequestVariableDefault

  • HystrixRequestVariableDefault其实就是存储了泛型Tvalue,并且封装了initialValuegetset方法。
  • LazyInitializer顾名思义就是为了懒汉式初始化value,而设计的内部类。
// 作用一:作为内部类调用HystrixRequestVariableDefault.initialValue方法,通过维护initialized布尔值,使HystrixRequestVariableDefault.initialValue方法只调用一次。
// 作用二:new一个LazyInitializer对象或LazyInitializer被垃圾回收时不会调用HystrixRequestVariableDefault.initialValue方法,也就是说对于业务初始化逻辑的影响被排除。
// 作用三:调用get方法时,可以通过CAS乐观锁的方式实现value的获取,具体请参照get方法。
static final class LazyInitializer<T> {
        // @GuardedBy("synchronization on get() or construction")
        private T value;

        /*
         * Boolean to ensure only-once initialValue() execution instead of using
         * a null check in case initialValue() returns null
         */
        // @GuardedBy("synchronization on get() or construction")
        private boolean initialized = false;

        private final HystrixRequestVariableDefault<T> rv;

        // 不会调用HystrixRequestVariableDefault.initialValue,不会更新initialized值
        private LazyInitializer(HystrixRequestVariableDefault<T> rv) {
            this.rv = rv;
        }

        // 不会调用HystrixRequestVariableDefault.initialValue,只能通过set方式调用
        private LazyInitializer(HystrixRequestVariableDefault<T> rv, T value) {
            this.rv = rv;
            this.value = value;
            this.initialized = true;
        }
        // 如果未初始化(没有调用过set方法)过,则返回HystrixRequestVariableDefault.initialValue的值,初始化过则返回初始化的值
        public synchronized T get() {
            if (!initialized) {
                value = rv.initialValue();
                initialized = true;
            }
            return value;
        }
    }
  • get方法,先从ConcurrentHashMap中取出对应的LazyInitializer,如果为空则使用CAS乐观锁的方式,new一个LazyInitializer并存入ConcurrentHashMap,最后返回调用LazyInitializer.get()并返回
public T get() {
        // 当前线程的HystrixRequestContext为null 或 ConcurrentHashMap<HystrixRequestVariableDefault<?>, HystrixRequestVariableDefault.LazyInitializer<?>> 为null
        if (HystrixRequestContext.getContextForCurrentThread() == null) {
            throw new IllegalStateException(HystrixRequestContext.class.getSimpleName() + ".initializeContext() must be called at the beginning of each request before RequestVariable functionality can be used.");
        }
        ConcurrentHashMap<HystrixRequestVariableDefault<?>, LazyInitializer<?>> variableMap = HystrixRequestContext.getContextForCurrentThread().state;

        // short-circuit the synchronized path below if we already have the value in the ConcurrentHashMap
        LazyInitializer<?> v = variableMap.get(this);
        if (v != null) {
            return (T) v.get();
        }

        /*
         * 乐观锁方式(CAS)new一个LazyInitializer,放进ConcurrentHashMap 
         * 这里值得注意的是,不调用LazyInitializer.get方法是不会执行HystrixRequestVariableDefault.initialValue,故当putIfAbsent失败时,可以乐观地放弃该实例,使该实例被GC。
         * 不管哪个LazyInitializer实例的get方法被调用,HystrixRequestVariableDefault.initialValue也只会被调用一次。
         */
        LazyInitializer<T> l = new LazyInitializer<T>(this);
        LazyInitializer<?> existing = variableMap.putIfAbsent(this, l);
        if (existing == null) {
            /*
             * We won the thread-race so can use 'l' that we just created.
             */
            return l.get();
        } else {
            /*
             * We lost the thread-race so let 'l' be garbage collected and instead return 'existing'
             */
            return (T) existing.get();
        }
    }

各类之间的关系

  • 一个request(不局限于一个线程) -> HystrixRequestContext -> ConcurrentHashMap<HystrixRequestVariableDefault, HystrixRequestVariableDefault.LazyInitializer>
  • 也就是说每个request都有一个ConcurrentHashMap<HystrixRequestVariableDefault, HystrixRequestVariableDefault.LazyInitializer> map。

获取缓存

  • getCacheKey重写了AbstractCommand.getCacheKey方法,AbstractCommandHystrixCommand的基类。
  • 根据上图,我们可以看出execute方法,最终调用toObservable方法,而toObservable方法在AbstractCommand中,因此我们可以初步断定在AbstractCommand.toObservable方法中,会与HystrixRequestVariableDefault或者其实现的接口产生关联,进行缓存的读取和写入。

AbstractCommand.toObservable的关键代码如下:

 final String cacheKey = getCacheKey();

                /* 如果开启了缓存功能,从缓存读取 */
                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;

                // 放进缓存
                if (requestCacheEnabled && cacheKey != null) {
                    // 包装成缓存Observable对象
                    HystrixCachedObservable<R> toCache = HystrixCachedObservable.from(hystrixObservable, _cmd);
                    HystrixCommandResponseFromCache<R> fromCache = (HystrixCommandResponseFromCache<R>) requestCache.putIfAbsent(cacheKey, toCache);
  • 接下来,我们就只要寻找HystrixRequestCacheHystrixRequestVariableDefault之间的关联了,AbstractCommand构造器中通过HystrixRequestCache.getInstance构造了HystrixRequestCache对象。
// 又是CAS,putIfAbsent。。。
 private static HystrixRequestCache getInstance(RequestCacheKey rcKey, HystrixConcurrencyStrategy concurrencyStrategy) {
        HystrixRequestCache c = caches.get(rcKey);
        if (c == null) {
            HystrixRequestCache newRequestCache = new HystrixRequestCache(rcKey, concurrencyStrategy);
            HystrixRequestCache existing = caches.putIfAbsent(rcKey, newRequestCache);
            if (existing == null) {
                // we won so use the new one
                c = newRequestCache;
            } else {
                // we lost so use the existing
                c = existing;
            }
        }
        return c;
    }
  • 来看HystrixRequestCache的值是怎么存储的,看HystrixRequestCache.putIfAbsent
HystrixCachedObservable<T> putIfAbsent(String cacheKey, HystrixCachedObservable<T> f) {
        // 使用HystrixRequestCache.prefix + concurrencyStrategy + HystrixCommand.getCacheKey包装成缓存key
        ValueCacheKey key = getRequestCacheKey(cacheKey);
        if (key != null) {
            // 寻找缓存,关键代码
            ConcurrentHashMap<ValueCacheKey, HystrixCachedObservable<?>> cacheInstance = requestVariableForCache.get(concurrencyStrategy);
            if (cacheInstance == null) {
                throw new IllegalStateException("Request caching is not available. Maybe you need to initialize the HystrixRequestContext?");
            }
            HystrixCachedObservable<T> alreadySet = (HystrixCachedObservable<T>) cacheInstance.putIfAbsent(key, f);
            if (alreadySet != null) {
                // someone beat us so we didn't cache this
                return alreadySet;
            }
        }
        // we either set it in the cache or do not have a cache key
        return null;
    }
  • requestVariableInstance.get(key)HystrixRequestVariableHolder中的方法。
 // 找到了关联。。。这里有HystrixRequestVariable
 private static ConcurrentHashMap<RVCacheKey, HystrixRequestVariable<?>> requestVariableInstance = new ConcurrentHashMap<RVCacheKey, HystrixRequestVariable<?>>();
 // 
 public T get(HystrixConcurrencyStrategy concurrencyStrategy) {

        RVCacheKey key = new RVCacheKey(this, concurrencyStrategy);
        HystrixRequestVariable<?> rvInstance = requestVariableInstance.get(key);
        if (rvInstance == null) {
            requestVariableInstance.putIfAbsent(key, concurrencyStrategy.getRequestVariable(lifeCycleMethods));
            /*
             * 内存泄漏检测,
             */
            if (requestVariableInstance.size() > 100) {
                logger.warn("Over 100 instances of HystrixRequestVariable are being stored. This is likely the sign of a memory leak caused by using unique instances of HystrixConcurrencyStrategy instead of a single instance.");
            }
        }
        // HystrixRequestVariable.get取出ConcurrentHashMap<ValueCacheKey, HystrixCachedObservable<?>>的map,再从ConcurrentHashMap<ValueCacheKey, HystrixCachedObservable<?>>中根据重写的getCacheKey构造出ValueCacheKey,拿出缓存值。
        return (T) requestVariableInstance.get(key).get();
    }

获取缓存过程中各个对象的对应关系

  • 一个commandKey
  • 一个HystrixRequestVariableHolder<ConcurrentHashMap<ValueCacheKey, HystrixCachedObservable<?>>>
  • 一个ConcurrentHashMap<RVCacheKey, HystrixRequestVariable> requestVariableInstance = new ConcurrentHashMap>()

请求缓存总结

最后,再总结下请求缓存机制,一个request对应一个HystrixRequestContextHystrixRequestVariable中存储缓存值,通过重写getCacheKey构造对应RVCacheKey,通过HystrixRequestVariableHolder拿到HystrixRequestVariable的值。

总结

看了源码才发现,作者有如下感受:

1、各种ConcurrentHashMap 2、终于RxJava第一次看到在非Android领域运用 3、懒加载+CAS伴随整个流程,后续也会考虑这种非锁实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 服务降级 由于爆炸性的流量冲击,对一些服务进行有策略的放弃,以此缓解系统压力,保证目前主要业务的正常运行。它主要是...
    十丈_红尘阅读 8,051评论 0 34
  • 原文:https://my.oschina.net/7001/blog/1619842 摘要: Hystrix是N...
    laosijikaichele阅读 4,234评论 0 25
  • 前言 分布式系统中经常会出现某个基础服务不可用造成整个系统不可用的情况, 这种现象被称为服务雪崩效应. 为了应对服...
    简约生活owen阅读 855评论 0 6
  • 一、认识Hystrix Hystrix是Netflix开源的一款容错框架,包含常用的容错方法:线程池隔离、信号量隔...
    新栋BOOK阅读 3,926评论 0 19
  • 我们对这个世界的真实性的探查笼罩在巨大的迷雾之中,就像今天在咖啡馆看到漂亮的女店员背对顾客系上系带围上围裙,把呈完...
    前折口阅读 9,394评论 2 10