此文已由作者赵计刚授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

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 热修复框架(上篇)

dubbo结果缓存机制的更多相关文章

  1. 第十五章 dubbo结果缓存机制

    dubbo提供了三种结果缓存机制: lru:基于最近最少使用原则删除多余缓存,保持最热的数据被缓存 threadlocal:当前线程缓存 jcache:可以桥接各种缓存实现 一.使用方式 <du ...

  2. Dubbo剖析-SPI机制

    文章要点: 1.什么是SPi 2.Dubbo为什么要实现自己的SPi 3.Dubbo的IOC和AOP 4.Dubbo的Adaptive机制 5.Dubbo动态编译机制 6.Dubbo与Spring的融 ...

  3. 探究Dubbo的拓展机制: 上

    这篇博文是我决心深度学习Dubbo框架时记录的笔记, 主题是Dubbo的拓展点, 下面的几个部分相对来说比较零散, 貌似是不和主题挂钩的 , 并且是一些很冷门的知识点 , 但是它们确实是深入学习Dub ...

  4. 探究Dubbo的拓展机制: 下

    承接上篇, 本篇博文的主题就是认认真真捋一捋, 看一下 Dubbo是如何实现他的IOC / AOP / 以及Dubbo SPI这个拓展点的 总览: 本篇的话总体上分成两部分进行展开 第一点就是 Dub ...

  5. jdk和dubbo的SPI机制

    前言:开闭原则一直是软件开发领域中所追求的,开闭原则中的"开"是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的,“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代 ...

  6. Dubbo的SPI机制与JDK机制的不同及原理分析

    从今天开始,将会逐步介绍关于DUbbo的有关知识.首先先简单介绍一下DUbbo的整体概述. 概述 Dubbo是SOA(面向服务架构)服务治理方案的核心框架.用于分布式调用,其重点在于分布式的治理. 简 ...

  7. 面试常问的dubbo的spi机制到底是什么?

    前言 dubbo是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力.作为spring cloud alibaba体系中重要的一部分,随着spring cloud alibaba在 ...

  8. 【腾讯Bugly干货分享】彻底弄懂 Http 缓存机制 - 基于缓存策略三要素分解法

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/qOMO0LIdA47j3RjhbCWUEQ 作者:李 ...

  9. MyCat源码分析系列之——BufferPool与缓存机制

    更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...

随机推荐

  1. 蓝桥杯 基础训练 BASIC-27 2n皇后问题

    基础练习 2n皇后问题   时间限制:1.0s   内存限制:512.0MB 问题描述 给定一个n*n的棋盘,棋盘中有一些位置不能放皇后.现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都 ...

  2. 转载:trap 的用法 /etc/init.d/rcS trap :1 2 3 24

    在有些情况下,我们不希望自己的shell脚本在运行时刻被中断,比如说我们写得shell脚 本设为某一用户的默认shell,使这一用户进入系统后只能作某一项工作,如数据库备份, 我 们可不希望用户使用c ...

  3. 【转】 Pro Android学习笔记(八六):了解Package(5):使用lib

    目录(?)[-] 在项目中使用lib 源代码 了解一些机制 文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注册等附加条件.转载须注明出处:http://blog.csdn.net/flowin ...

  4. typescript相关知识点总结

    本文讲解typescript语法 由于js语法本身的混乱,再加上目前框架的割据,导致typescript用起来没有一致性,本文尽量总结实际开发中可能会用到的知识点 目录 数据类型 类型断言 duck ...

  5. 2016.4.6 WinForm显示PDF两种方法

    1.最直接的方法,添加webbrowser控件 webb.Url = new Uri(path);可显示pdf控件. 如果需要在打开时跳转到某页,可用在路径后直接加#page=,例如webb.Url ...

  6. 如何创建和配置Solaris10 zones (ZT)

    http://thegeekdiary.com/how-to-create-and-configure-solaris-10-zones/ Solaris zones enables a softwa ...

  7. Oracle 设置主键自增长__Oracle

    转自:https://yq.aliyun.com/ziliao/258074 如果想在Oracle数据库里实现数据表主键自增,我们似乎没有办法像MySql般直接定义列的属性来实现.不过对于这个数据库的 ...

  8. GCD详细介绍

    (1)是基于C语言的底层API (2)用Block定义任务,使用起来非常灵活便捷 (3)提供了更多的控制能力以及操作队列中所不能使用的底层函数 小结 说明:同步函数不具备开启线程的能力,无论是什么队列 ...

  9. elasticsearch配置文件里的一些坑 [Failed to load settings from [elasticsearch.yml]]

    这里整理几个空格引起的问题. 版本是elasticsearch-2.3.0 或者elasticsearch-rtf-master Exception in thread "main" ...

  10. Codeforces 1110D Jongmah (DP)

    题意:你有n个数字,范围[1, m],你可以选择其中的三个数字构成一个三元组,但是这三个数字必须是连续的或者相同的,每个数字只能用一次,问这n个数字最多构成多少个三元组? 解析:首先我们容易发现,我们 ...