title: dubbo缓存代码分析 tags:

  • dubbo
  • cache
  • lru
  • removeEldestEntry
  • LinkedHashMap categories: 工作日志 date: 2017-06-25 18:18:55

dubbo是Ali出品的soa框架,属于互联网企业常见的rpc选择框架。

前几篇分析了多级缓存的相关代码,本篇就dubbo的缓存进行梳理。

dubbo的缓存针对的是客户端的缓存,可以设计相关的缓存策略

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

  • lru 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。
  • threadlocal 当前线程缓存,比如一个页面渲染,用到很多portal,每个portal都要去查用户信息,通过线程缓存,可以减少这种多余访问。
  • jcache 与JSR107集成,可以桥接各种缓存实现。

默认情况下使用的是lru策略。

dubbo中关于各种配置选择了扩展点作为配置选项 通常情况下使用SPI声明

/ 此方法已经getExtensionClasses方法同步过。
    private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if(defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if(value != null && (value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if(names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if(names.length == 1) cachedDefaultName = names[0];
            }
        }
         
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }
     
    private static final String SERVICES_DIRECTORY = "META-INF/services/";
     
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
     
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
复制代码

可以看到默认情况下 dubbo会加载三个文件夹目录,此时我们关注Cache。

可以看到/dubbo-2.5.3.jar!/META-INF/dubbo/internal/com.alibaba.dubbo.cache.CacheFactory

threadlocal=com.alibaba.dubbo.cache.support.threadlocal.ThreadLocalCacheFactory
    lru=com.alibaba.dubbo.cache.support.lru.LruCacheFactory
    jcache=com.alibaba.dubbo.cache.support.jcache.JCacheFactory
复制代码

默认声明了3中缓存,和文档保持一致

@SPI("lru")
    public interface CacheFactory {
     
        @Adaptive("cache")
        Cache getCache(URL url);
     
    }
复制代码

其次我们看到默认情况下如果Cache声明成true其实启用的是lru缓存也就是

com.alibaba.dubbo.cache.support.lru.LruCacheFactory
复制代码

众所周知的是dubbo采用了filter作为拦截器来增强和扩展相关功能

我们看一下CacheFilter

/**
     * CacheFilter
     *
     * @author william.liangf
     */
    @Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)
    public class CacheFilter implements Filter {
     
        private CacheFactory cacheFactory;
     
        public void setCacheFactory(CacheFactory cacheFactory) {
            this.cacheFactory = cacheFactory;
        }
     
        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);
        }
     
    }

    /**
     * AbstractCacheFactory
     *
     * @author william.liangf
     */
    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);
     
    }
复制代码

可以看出缓存的key是以url作为key的,具体创建缓存的逻辑有各个实现类具体完成。

默认情况下采取lru缓存

/**
     * LruCache
     *
     * @author william.liangf
     */
    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) {
                    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);
            }
        }
     
    }
复制代码

默认情况下可以根据参数最多缓存1000个。当方法数较多(均开启了缓存&&参数各不相同)有可能出现内存撑爆(特别是结果集太大)使用时需要评估===》缓存是把双刃剑要考虑缓存的效率和减少对服务方的依赖但也要考虑缓存不一致的场景。特别要注意缓存要用在幂等性场合

上述代码中我们看到了一个经典的lru缓存实现,使用LinkedHashMap重载removeEldestEntry。