Dubbo高级篇_13_Dubbo缓存

一、缓存算法介绍

  • 结果缓存,用于加速热门数据的访问速度, Dubbo提供声明式缓存,以减少用户加缓存的工作量。

按照SPI的要求,我们从配置文件中可以看到dubbo提供的三种缓存接口的入口:

threadlocal=com.alibaba.dubbo.cache.support.threadlocal.ThreadLocalCacheFactory
lru=com.alibaba.dubbo.cache.support.lru.LruCacheFactory
jcache=com.alibaba.dubbo.cache.support.jcache.JCacheFactory

先来看一下dubbo提供的AbstractCacheFactory的细节:

public abstract class AbstractCacheFactory implements CacheFactory {

    private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

    public Cache getCache(URL url) {
        String key = url.toFullString();
        Cache cache = caches.get(key);
        if (cache == null) {
            caches.put(key, createCache(url));
            cache = caches.get(key);
        }
        return cache;
    }

    protected abstract Cache createCache(URL url);

}

很直观的看得出,该类完成了具体cache实现的实例化工作(注意getCache的返回类型Cache,该接口规范了不同缓存的实现),接下来我们就分三部分来具体看一下不同的缓存接口的具体实现。

二、缓存类型

2.1 ThreadLocal

该缓存应用场景为

  • 比如一个页面渲染,用到很多portal,每个portal都要去查用户信息,通过线程缓存,可以减少这种多余访问。

  • 场景描述的核心内容是当前请求的上下文

配置示例如下:

<dubbo:reference interface="com.foo.BarService" cache="threadlocal" />

那就表明你使用的是该类型的缓存,根据SPI机制,会执行下面这个工厂类:

public class ThreadLocalCacheFactory extends AbstractCacheFactory {

    protected Cache createCache(URL url) {
        return new ThreadLocalCache(url);
    }
}

注意该类继承了上面提到的AbstractCacheFactory。可以看出,真正实例化的具体缓存层实现是ThreadLocalCache类型。由于此类型是基于线程本地变量的,所以非常简单:

public class ThreadLocalCache implements Cache {

    private final ThreadLocal<Map<Object, Object>> store;

    public ThreadLocalCache(URL url) {
        this.store = new ThreadLocal<Map<Object, Object>>() {
            @Override
            protected Map<Object, Object> initialValue() {
                return new HashMap<Object, Object>();
            }
        };
    }

    public void put(Object key, Object value) {
        store.get().put(key, value);
    }

    public Object get(Object key) {
        return store.get().get(key);
    }
}

2.2 LRU缓存淘汰算法

  • lru 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。
  • 该类型的缓存是跨线程的

引用: http://flychao88.iteye.com/blog/1977653

LRU原理
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:

1. 新数据插入到链表头部;
2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
3. 当链表满的时候,将链表尾部的数据丢弃。

LruCache类的实现

public class LruCache implements Cache {

    private final Map<Object, Object> store;

    public LruCache(URL url) {
        final int max = url.getParameter("cache.size", 1000);   //定义了缓存的容量
        this.store = new LinkedHashMap<Object, Object>() {
            private static final long serialVersionUID = -3834209229668463829L;
            @Override
            protected boolean removeEldestEntry(Entry<Object, Object> eldest) { //jdk提供的接口,用于移除最旧条目的需求
                return size() > max;
            }
        };
    }

    public void put(Object key, Object value) {
        synchronized (store) {  //注意这里的同步条件
            store.put(key, value);
        }
    }

    public Object get(Object key) {
        synchronized (store) {  //注意这里的同步条件
            return store.get(key);
        }
    }
}

2.3 JCache

  • jcache 与JSR107集成,可以桥接各种缓存实现。

三、如何解析“cache”属性

那么,cache层的逻辑是如何一步一步“注入”到我们的业务逻辑里呢?这还是要追溯到dubbo的过滤器上,我们知道在dubbo初始化指定protocol的时候,会使用装饰器模式把所有需要加载的过滤器封装到目标protocol上,这个细节指引我来查看ProtocolFilterWrapper类:

refer() --->  buildInvokerChain()
                        |
                        V

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
    if (filters.size() > 0) {
        for (int i = filters.size() - 1; i >= 0; i --) {
            final Filter filter = filters.get(i);
            final Invoker<T> next = last;
            last = new Invoker<T>() {

                public Class<T> getInterface() {
                    return invoker.getInterface();
                }

                public URL getUrl() {
                    return invoker.getUrl();
                }

                public boolean isAvailable() {
                    return invoker.isAvailable();
                }

                public Result invoke(Invocation invocation) throws RpcException {
                    return filter.invoke(next, invocation);
                }

                public void destroy() {
                    invoker.destroy();
                }

                @Override
                public String toString() {
                    return invoker.toString();
                }
            };
        }
    }
    return last;
}

注意ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);这一行,单步调试可以得知它会返回所有需要“注入”的Filter逻辑,当然也包含我们关注的缓存:

com.alibaba.dubbo.cache.filter.CacheFilter

注意看该类声明的开头:

@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)

这一行是关键哟,上面提到的getActivateExtension方法就是靠这一行注解工作的。dubbo以这种设计风格完成了大多数的功能,所以对于研究dubbo源码的童鞋,一定要多多注意。

经历了这一圈下来,所有过滤器就已经注入到我们的服务当中了。

业务层如何使用cache

最后再来仔细看一下com.alibaba.dubbo.cache.filter.CacheFilter类的invoke方法:

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    if (cacheFactory != null && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.CACHE_KEY))) {
        Cache cache = cacheFactory.getCache(invoker.getUrl().addParameter(Constants.METHOD_KEY, invocation.getMethodName()));
        if (cache != null) {
            String key = StringUtils.toArgumentString(invocation.getArguments());
            if (cache != null && key != null) {
                Object value = cache.get(key);
                if (value != null) {
                    return new RpcResult(value);
                }
                Result result = invoker.invoke(invocation);
                if (! result.hasException()) {
                    cache.put(key, result.getValue());
                }
                return result;
            }
        }
    }
    return invoker.invoke(invocation);
}

可以看出,这里根据不同的配置会初始化并使用不同的缓存实现。

猜你喜欢

转载自blog.csdn.net/hardworking0323/article/details/81293402