1. 程式人生 > >dubbo結果快取機制

dubbo結果快取機制

此文已由作者趙計剛授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。


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 }


注意:

  • LinkedHashMap維護著一個運行於所有Entry的雙向連結串列:此連結串列定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序(真正儲存的資料結構還是其父類HashMap的那個Entry[]陣列,上述的雙向連結串列僅用於維護迭代順序)

  • 當指定了LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)第三個引數accessOrder=true時,每次執行get(Object key)時,獲取出來的Entry都會被放到尾節點,也就是說雙向連結串列的header節點是最久以前訪問的,當執行put(Object key, Object value)的時候,就執行removeEldestEntry(java.util.Map.Entry<K, V> eldest)來判斷是否需要刪除這個header節點。(這些是LinkedHashMap實現的,具體原始碼分析見 https://yikun.github.io/2015/04/02/Java-LinkedHashMap%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E5%AE%9E%E7%8E%B0/  http://wiki.jikexueyuan.com/project/java-collection/linkedhashmap.html

 

三、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。


相關文章:
【推薦】 從golang的垃圾回收說起(上篇)
【推薦】 Kylin儲存和查詢的分片問題
【推薦】 手把手帶你打造一個 Android 熱修復框架(上篇)