此文已由作者赵计刚授权网易云社区发布。


欢迎访问​​网易云社区​​,了解更多网易技术产品运营经验。


dubbo提供了三种结果缓存机制:


  • lru:基于最近最少使用原则删除多余缓存,保持最热的数据被缓存
  • threadlocal:当前线程缓存
  • jcache:可以桥接各种缓存实现


一、使用方式

1     <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService"> 2         <dubbo:method name="sayHello" timeout="60000" cache="lru"/> 3     </dubbo:reference>

添加cache配置。

注意:dubbo结果缓存有一个bug,​​https://github.com/alibaba/dubbo/issues/1362​​,当cache="xxx"配置在服务级别时,没有问题,当配置成方法级别的时候,不管怎么配置,都睡使用LruCache。


二、LRU缓存源码解析


1 /**  2  * CacheFilter  3  * 配置了cache配置才会加载CacheFilter  4  */  5 @Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)  6 public class CacheFilter implements Filter {  7     private CacheFactory cacheFactory;  8   9     public void setCacheFactory(CacheFactory cacheFactory) { 10         this.cacheFactory = cacheFactory; 11     } 12  13     public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { 14         if (cacheFactory != null && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.CACHE_KEY))) { 15             // 使用CacheFactory$Adaptive获取具体的CacheFactory,然后再使用具体的CacheFactory获取具体的Cache对象 16             Cache cache = cacheFactory.getCache(invoker.getUrl().addParameter(Constants.METHOD_KEY, invocation.getMethodName())); 17             if (cache != null) { 18                 // 缓存对象的key为arg1,arg2,arg3,...,arg4 19                 String key = StringUtils.toArgumentString(invocation.getArguments()); 20                 // 获取缓存value 21                 Object value = cache.get(key); 22                 if (value != null) { 23                     return new RpcResult(value); 24                 } 25                 Result result = invoker.invoke(invocation); 26                 // 响应结果没有exception信息,则将相应结果的值塞入缓存 27                 if (!result.hasException()) { 28                     cache.put(key, result.getValue()); 29                 } 30                 return result; 31             } 32         } 33         return invoker.invoke(invocation); 34     } 35 }


从@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)中我们可以看出,consumer端或provider端配置了cache="xxx",则会走该CacheFilter。

首先获取具体Cache实例:CacheFilter中的cacheFactory属性是CacheFactory$Adaptive实例。


1 public class CacheFactory$Adaptive implements com.alibaba.dubbo.cache.CacheFactory {  2     public com.alibaba.dubbo.cache.Cache getCache(com.alibaba.dubbo.common.URL arg0) {  3         if (arg0 == null) throw new IllegalArgumentException("url == null");  4         com.alibaba.dubbo.common.URL url = arg0;  5         String extName = url.getParameter("cache", "lru");  6         if (extName == null)  7             throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.cache.CacheFactory) name from url(" + url.toString() + ") use keys([cache])");  8         // 获取具体的CacheFactory  9         com.alibaba.dubbo.cache.CacheFactory extension = (com.alibaba.dubbo.cache.CacheFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.cache.CacheFactory.class).getExtension(extName); 10         // 使用具体的CacheFactory获取具体的Cache 11         return extension.getCache(arg0); 12     } 13 }


这里extName使我们配置的lru,如果不配置,默认也是lru。这里获取到的具体的CacheFactory是LruCacheFactory。


1 @SPI("lru")  2 public interface CacheFactory {  3     @Adaptive("cache")  4     Cache getCache(URL url);  5 }  6   7 public abstract class AbstractCacheFactory implements CacheFactory {  8     private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();  9  10     public Cache getCache(URL url) { 11         String key = url.toFullString(); 12         Cache cache = caches.get(key); 13         if (cache == null) { 14             caches.put(key, createCache(url)); 15             cache = caches.get(key); 16         } 17         return cache; 18     } 19  20     protected abstract Cache createCache(URL url); 21 } 22  23 public class LruCacheFactory extends AbstractCacheFactory { 24     protected Cache createCache(URL url) { 25         return new LruCache(url); 26     } 27 }


调用LruCacheFactory.getCache(URL url)方法,实际上调用的是其父类AbstractCacheFactory的方法。逻辑是:创建一个LruCache实例,之后存储在ConcurrentMap<String, Cache> caches中,key为url.toFullString()。

再来看LruCache的创建:


1 public interface Cache {  2     void put(Object key, Object value);  3     Object get(Object key);  4 }  5   6 public class LruCache implements Cache {  7     private final Map<Object, Object> store;  8   9     public LruCache(URL url) { 10         final int max = url.getParameter("cache.size", 1000); 11         this.store = new LRUCache<Object, Object>(max); 12     } 13  14     public void put(Object key, Object value) { 15         store.put(key, value); 16     } 17  18     public Object get(Object key) { 19         return store.get(key); 20     } 21 }


默认缓存存储的最大个数为1000个。之后创建了一个LRUCache对象。


1 public class LRUCache<K, V> extends LinkedHashMap<K, V> {  2     private static final long serialVersionUID = -5167631809472116969L;  3   4     private static final float DEFAULT_LOAD_FACTOR = 0.75f;  5   6     private static final int DEFAULT_MAX_CAPACITY = 1000;  7     private final Lock lock = new ReentrantLock();  8     private volatile int maxCapacity;  9  10     public LRUCache(int maxCapacity) { 11         /** 12          * 注意: 13          * LinkedHashMap 维护着一个运行于所有Entry的双向链表:此链表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序 14          * 而真正存储的数据结构还是其父类HashMap的那个Entry[]数组,上述的双向链表仅用于维护迭代顺序(帮助实现lru算法等) 15          * 16          * LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 17          * 第三个参数accessOrder:false(插入顺序),true(访问顺序) 18          */ 19         super(16, DEFAULT_LOAD_FACTOR, true); 20         this.maxCapacity = maxCapacity; 21     } 22  23     /** 24      * 是否需要删除最老的数据(即最近没有被访问的数据) 25      * @param eldest 26      * @return 27      */ 28     @Override 29     protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) { 30         return size() > maxCapacity; 31     } 32  33     @Override 34     public V get(Object key) { 35         try { 36             lock.lock(); 37             return super.get(key); 38         } finally { 39             lock.unlock(); 40         } 41     } 42  43     @Override 44     public V put(K key, V value) { 45         try { 46             lock.lock(); 47             return super.put(key, value); 48         } finally { 49             lock.unlock(); 50         } 51     } 52  53     @Override 54     public V remove(Object key) { 55         try { 56             lock.lock(); 57             return super.remove(key); 58         } finally { 59             lock.unlock(); 60         } 61     } 62  63     @Override 64     public int size() { 65         try { 66             lock.lock(); 67             return super.size(); 68         } finally { 69             lock.unlock(); 70         } 71     } 72     ... 73 }


注意:


三、ThreadLocal缓存源码解析

根据文章开头提到的bug,cache=""只能配置在服务级别。

1 <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService" cache="threadlocal"/>
1 public class ThreadLocalCacheFactory extends AbstractCacheFactory {  2     protected Cache createCache(URL url) {  3         return new ThreadLocalCache(url);  4     }  5 }  6   7 public class ThreadLocalCache implements Cache {  8     private final ThreadLocal<Map<Object, Object>> store;  9  10     public ThreadLocalCache(URL url) { 11         this.store = new ThreadLocal<Map<Object, Object>>() { 12             @Override 13             protected Map<Object, Object> initialValue() { 14                 return new HashMap<Object, Object>(); 15             } 16         }; 17     } 18  19     public void put(Object key, Object value) { 20         store.get().put(key, value); 21     } 22  23     public Object get(Object key) { 24         return store.get().get(key); 25     } 26 }


ThreadLocalCache的实现是HashMap。