第十五章 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)的核心思想是数据抽象.继承和动态绑定. 通过使用数据抽象,可以将类的接口和实现分离. 使用继承,可以定义相似的类型并对其相似关系 ...
随机推荐
- LoadRunner性能测试入门教程
javaweb性能测试那些事 一:什么是javaweb性能测试: 二:javaweb性能测试基本流程 三:javaweb性能测试常用指标: 1:响应时间:2-5-8 原则 2:吞吐量 3:资源使用率 ...
- PHP: POST Content-Length of xxx bytes exceeds the limit of 8388608 bytes
用户上传了 4 个附件,每个小于 5M,但是总大小超过了 15 M. 在 Nginx 日志中找到了如下错误信息,还没有到 Laravel 日志那一层. 2018/08/13 10:14:38 [err ...
- bzoj1150 堆应用,好题
#include<bits/stdc++.h> using namespace std; #define maxn 100005 #define INF 0x3fffffff #defin ...
- 《剑指offer》-孩子们的游戏(圆圈中最后剩下的数)
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此.HF作为牛客的资深元老,自然也准备了一些小游戏.其中,有个游戏是这样的:首先,让小朋友们围成一个大圈.然后,他随机指定一个数m ...
- Linux命令02--系统管理
<1>查看当前日历:cal <2>显示或者设置时间 设置时间格式(需要管理员权限):一般都是有ntp服务的,你的系统会根据这个对照互联网时间 <3>查看进程信息:p ...
- FastAdmin 在 Nginx 中的配置
FastAdmin 使用的是 ThinkPHP 5 框架. 在 Apache 下很简单,但是在 nginx 下就需要自行配置一下了. 在网上找了很多,都没有找到很好的. 刚刚在 QQ 里群里找到一个, ...
- 《Android进阶之光》--View体系与自定义View
No1: View的滑动 1)layout()方法的 public class CustomView extends View{ private int lastX; private int last ...
- HDU 1358 Period (kmp求循环节)(经典)
<题目链接> 题目大意: 意思是,从第1个字母到第2字母组成的字符串可由某一周期性的字串(“a”) 的两次组成,也就是aa有两个a组成: 第三行自然就是aabaab可有两个aab组成: 第 ...
- Coredata 单表简单使用
** 使用Coredata 工程中的DataModel创建:系统创建.手动创建** ** 使用Coredata需要要导入<CoreData/CoreData.h> ** 1.系统创建(系统 ...
- MyBatis之ResultMap标签
ResultMap标签基本作用:建立SQL查询结果字段与实体属性的映射关系信息 在深入ResultMap标签前,我们需要了解从SQL查询结果集到JavaBean或POJO实体的过程. 1. 通过JDB ...