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结果缓存机制的更多相关文章

  1. Android笔记(二十五) ListView的缓存机制与BaseAdapter

    之前接触了ListView和Adapter,Adapter将数据源和View连接起来,实际应用中,我们要显示的数据往往有很多,而屏幕只有那么大,系统只能屏幕所能显示的内容,当我们滑动屏幕,会将旧的内容 ...

  2. Gradle 1.12用户指南翻译——第六十五章. Maven 发布(新)

    其他章节的翻译请参见:http://blog.csdn.net/column/details/gradle-translation.html翻译项目请关注Github上的地址:https://gith ...

  3. Gradle 1.12用户指南翻译——第四十五章. 应用程序插件

    本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  4. Gradle 1.12用户指南翻译——第二十五章. Scala 插件

    其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...

  5. Gradle 1.12用户指南翻译——第三十五章. Sonar 插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  6. “全栈2019”Java多线程第二十四章:等待唤醒机制详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  7. 第十五章、Python多线程之信号量和GIL

    目录 第十五章.Python多线程之信号量和GIL 1. 信号量(Semaphore) 2. GIL 说明: 第十五章.Python多线程之信号量和GIL 1. 信号量(Semaphore) 信号量用 ...

  8. 20190901 On Java8 第十五章 异常

    第十五章 异常 要想创建健壮的系统,它的每一个构件都必须是健壮的. 异常概念 C++的异常处理机制基于 Ada,Java 中的异常处理则建立在 C++的基础之上(尽管看上去更像 Object Pasc ...

  9. 【C++】《C++ Primer 》第十五章

    第十五章 面向对象程序设计 一.OOP:概述 面向对象程序设计(OOP)的核心思想是数据抽象.继承和动态绑定. 通过使用数据抽象,可以将类的接口和实现分离. 使用继承,可以定义相似的类型并对其相似关系 ...

随机推荐

  1. 根据条件返回相应值 decode(条件,值1,翻译值1,值2,翻译值2,...值n,翻译值n,缺省值)

    decode(条件,值1,翻译值1,值2,翻译值2,...值n,翻译值n,缺省值) [功能]根据条件返回相应值 [参数]c1, c2, ...,cn,字符型/数值型/日期型,必须类型相同或null 注 ...

  2. WAP网页中点击链接直接拨打电话的方法

    主要方法有两种: 第一种: 这种也是最简单的. 示例代码: <a href="tel:+18790853577">点击拨打</a> 第二种: <a h ...

  3. LeetCode(4):两个排序数组的中位数

    Hard! 题目描述: 有两个大小为 m 和 n 的排序数组 nums1 和 nums2 . 请找出两个排序数组的中位数并且总的运行时间复杂度为 O(log (m+n)) . 示例 1: nums1 ...

  4. Python 索引迭代

    1.使用enumerate函数 L = ['Adam', 'Lisa', 'Bart', 'Paul'] for index, name in enumerate(L):     print inde ...

  5. linux 卸载自带apache httpd 安装apache httpd

    一.卸载自带apache httpd 1.关闭httpd服务:/etc/init.d/httpd stop 2.列出相关程序包:rpm -qa|grep httpd 3.卸载命令:rpm -e --n ...

  6. Number对象的常用属性和方法

    方法 描述 isNan() 检查值是否为数字 toFied() 将特定数字四舍五入至小数位数(返回一个字符串) toPrecision() 按数字的位数四舍五入(返回一个字符串) toExponent ...

  7. WebApi的调用-3.Basic验证

    webapi里的特性 /// <summary> /// Basic验证 /// </summary> /// <remarks> /// /// </rem ...

  8. C++对文本文件的读取和输出

    本文转载自xmh_free 自己浏览了上方的博客,主要整理了一下关于C++对文件的输入输出方法,如果想看C语言的输入输出方法,可浏览上述链接的博客 C++读写函数 在C++中,对文件的操作是通过str ...

  9. 安装及配置sublime

    纯个人笔记,粗略编写,有时间再修改 参考:关于Sublime text3 配置及插件整理 参考:https://github.com/boontime/sublime-text3-config 参考: ...

  10. 洛谷 P1004 方格取数 【多进程dp】

    题目链接:https://www.luogu.org/problemnew/show/P1004 题目描述 设有N*N的方格图(N<=9),我们将其中的某些方格中填入正整数,而其他的方格中则放 ...