第十五章 dubbo结果缓存机制
dubbo提供了三种结果缓存机制:
- lru:基于最近最少使用原则删除多余缓存,保持最热的数据被缓存
- threadlocal:当前线程缓存
- jcache:可以桥接各种缓存实现
一、使用方式
<dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService">
<dubbo:method name="sayHello" timeout="60000" cache="lru"/>
</dubbo:reference>
添加cache配置。
注意:dubbo结果缓存有一个bug,https://github.com/alibaba/dubbo/issues/1362,当cache="xxx"配置在服务级别时,没有问题,当配置成方法级别的时候,不管怎么配置,都睡使用LruCache。
二、LRU缓存源码解析
/**
* CacheFilter
* 配置了cache配置才会加载CacheFilter
*/
@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))) {
// 使用CacheFactory$Adaptive获取具体的CacheFactory,然后再使用具体的CacheFactory获取具体的Cache对象
Cache cache = cacheFactory.getCache(invoker.getUrl().addParameter(Constants.METHOD_KEY, invocation.getMethodName()));
if (cache != null) {
// 缓存对象的key为arg1,arg2,arg3,...,arg4
String key = StringUtils.toArgumentString(invocation.getArguments());
// 获取缓存value
Object value = cache.get(key);
if (value != null) {
return new RpcResult(value);
}
Result result = invoker.invoke(invocation);
// 响应结果没有exception信息,则将相应结果的值塞入缓存
if (!result.hasException()) {
cache.put(key, result.getValue());
}
return result;
}
}
return invoker.invoke(invocation);
}
}
从@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)中我们可以看出,consumer端或provider端配置了cache="xxx",则会走该CacheFilter。
首先获取具体Cache实例:CacheFilter中的cacheFactory属性是CacheFactory$Adaptive实例。
 public class CacheFactory$Adaptive implements com.alibaba.dubbo.cache.CacheFactory {
     public com.alibaba.dubbo.cache.Cache getCache(com.alibaba.dubbo.common.URL arg0) {
         if (arg0 == null) throw new IllegalArgumentException("url == null");
         com.alibaba.dubbo.common.URL url = arg0;
         String extName = url.getParameter("cache", "lru");
         if (extName == null)
             throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.cache.CacheFactory) name from url(" + url.toString() + ") use keys([cache])");
         // 获取具体的CacheFactory
         com.alibaba.dubbo.cache.CacheFactory extension = (com.alibaba.dubbo.cache.CacheFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.cache.CacheFactory.class).getExtension(extName);
         // 使用具体的CacheFactory获取具体的Cache
         return extension.getCache(arg0);
     }
 }
这里extName使我们配置的lru,如果不配置,默认也是lru。这里获取到的具体的CacheFactory是LruCacheFactory。
 @SPI("lru")
 public interface CacheFactory {
     @Adaptive("cache")
     Cache getCache(URL url);
 }
 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);
 }
 public class LruCacheFactory extends AbstractCacheFactory {
     protected Cache createCache(URL url) {
         return new LruCache(url);
     }
 }
调用LruCacheFactory.getCache(URL url)方法,实际上调用的是其父类AbstractCacheFactory的方法。逻辑是:创建一个LruCache实例,之后存储在ConcurrentMap<String, Cache> caches中,key为url.toFullString()。
再来看LruCache的创建:
 public interface Cache {
     void put(Object key, Object value);
     Object get(Object key);
 }
 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 LRUCache<Object, Object>(max);
     }
     public void put(Object key, Object value) {
         store.put(key, value);
     }
     public Object get(Object key) {
         return store.get(key);
     }
 }
默认缓存存储的最大个数为1000个。之后创建了一个LRUCache对象。
 public class LRUCache<K, V> extends LinkedHashMap<K, V> {
     private static final long serialVersionUID = -5167631809472116969L;
     private static final float DEFAULT_LOAD_FACTOR = 0.75f;
     private static final int DEFAULT_MAX_CAPACITY = 1000;
     private final Lock lock = new ReentrantLock();
     private volatile int maxCapacity;
     public LRUCache(int maxCapacity) {
         /**
          * 注意:
          * LinkedHashMap 维护着一个运行于所有Entry的双向链表:此链表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序
          * 而真正存储的数据结构还是其父类HashMap的那个Entry[]数组,上述的双向链表仅用于维护迭代顺序(帮助实现lru算法等)
          *
          * LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
          * 第三个参数accessOrder:false(插入顺序),true(访问顺序)
          */
         super(16, DEFAULT_LOAD_FACTOR, true);
         this.maxCapacity = maxCapacity;
     }
     /**
      * 是否需要删除最老的数据(即最近没有被访问的数据)
      * @param eldest
      * @return
      */
     @Override
     protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
         return size() > maxCapacity;
     }
     @Override
     public V get(Object key) {
         try {
             lock.lock();
             return super.get(key);
         } finally {
             lock.unlock();
         }
     }
     @Override
     public V put(K key, V value) {
         try {
             lock.lock();
             return super.put(key, value);
         } finally {
             lock.unlock();
         }
     }
     @Override
     public V remove(Object key) {
         try {
             lock.lock();
             return super.remove(key);
         } finally {
             lock.unlock();
         }
     }
     @Override
     public int size() {
         try {
             lock.lock();
             return super.size();
         } finally {
             lock.unlock();
         }
     }
     ...
 }
注意:
- 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=""只能配置在服务级别。
<dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService" cache="threadlocal"/>
 public class ThreadLocalCacheFactory extends AbstractCacheFactory {
     protected Cache createCache(URL url) {
         return new ThreadLocalCache(url);
     }
 }
 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);
     }
 }
ThreadLocalCache的实现是HashMap。
四、JCache缓存源码解析
//TODO
第十五章 dubbo结果缓存机制的更多相关文章
- Android笔记(二十五) ListView的缓存机制与BaseAdapter
		之前接触了ListView和Adapter,Adapter将数据源和View连接起来,实际应用中,我们要显示的数据往往有很多,而屏幕只有那么大,系统只能屏幕所能显示的内容,当我们滑动屏幕,会将旧的内容 ... 
- Gradle 1.12用户指南翻译——第六十五章. Maven 发布(新)
		其他章节的翻译请参见:http://blog.csdn.net/column/details/gradle-translation.html翻译项目请关注Github上的地址:https://gith ... 
- Gradle 1.12用户指南翻译——第四十五章. 应用程序插件
		本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ... 
- Gradle 1.12用户指南翻译——第二十五章. Scala 插件
		其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ... 
- Gradle 1.12用户指南翻译——第三十五章. Sonar 插件
		本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ... 
- “全栈2019”Java多线程第二十四章:等待唤醒机制详解
		难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ... 
- 第十五章、Python多线程之信号量和GIL
		目录 第十五章.Python多线程之信号量和GIL 1. 信号量(Semaphore) 2. GIL 说明: 第十五章.Python多线程之信号量和GIL 1. 信号量(Semaphore) 信号量用 ... 
- 20190901 On Java8 第十五章 异常
		第十五章 异常 要想创建健壮的系统,它的每一个构件都必须是健壮的. 异常概念 C++的异常处理机制基于 Ada,Java 中的异常处理则建立在 C++的基础之上(尽管看上去更像 Object Pasc ... 
- 【C++】《C++ Primer 》第十五章
		第十五章 面向对象程序设计 一.OOP:概述 面向对象程序设计(OOP)的核心思想是数据抽象.继承和动态绑定. 通过使用数据抽象,可以将类的接口和实现分离. 使用继承,可以定义相似的类型并对其相似关系 ... 
随机推荐
- Jmeter安装和启动和使用
			一.安装配置JDK 1.下载安装jdk,地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html 2.配置JDK环境变 ... 
- java  扫描输入
			到目前为止,从文件或标准输入读取数据还是一件相当痛苦第事情,一般第解决之道就是读入一行文本,对其进行分词,然后使用Integer Double 等类第各种解析方法来解析数据: //: strings/ ... 
- composer卸载重装
			$ composer remove phpunit/phpunit --dev $ composer require phpunit/phpunit --dev 
- ERP产品销售发货判断库存功能(四十二)
			产品数量的前端(键盘抬起的事件): <td> <input type="text" name="proCount" onkeyup=" ... 
- Ext.js入门:模板(四)
			1.Ext.DomHelper简介2.Template语法使用简介3.Template简单应用4.Template中使用转换函数5.使用模板的自定义接口6.XTemplate应用 一:Ext.DomH ... 
- DailyWallpaper v1.03 released
			根据这一段时间的使用发现了一些问题,重新修正一下. 修正电脑从休眠状态中恢复时如果没有网络连接程序报错的bug. 添加了异常处理语句,防止抓取网页数据时的错误. 这个版本将是最后一个bug fix版本 ... 
- System.getenv()和System.getProperty() 的区别
			1.System.getenv() 方法是获取指定的环境变量的值.它有两种方法,一种是接收参数为任意字符串,当存在指定环境变量时即返回环境变量的值,否则返回null.另外一种是不接受参数,那么返回的是 ... 
- 【BZOJ5110】[CodePlus2017]Yazid 的新生舞会
			题解: 没笔的时候我想了一下 发现如果不是出现一半次数而是k次,并不太会做 然后我用前缀和写了一下发现就是维护一个不等式: 于是就可以随便维护了 
- Django ORM OneToOneField
			一对一关系 一对一关系与多对一关系非常相似.如果你在模型中定义一个OneToOneField,该模型的实例将可以通过该模型的一个简单属性访问关联的模型. class Person(models.Mod ... 
- 6-3 矩阵链成  uva 442
			较为简单的栈题 思路比较好 一次ac 1.char word :word=A:直接 a[word]=xxxx,不用 a[‘word’]=xxxx #include<bits/stdc++.h& ... 
