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 熱修復框架(上篇)