zoukankan      html  css  js  c++  java
  • 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 热修复框架(上篇)

  • 相关阅读:
    Stream processing with Apache Flink and Minio
    Replicated Ship 本地 kubernetes 环境试用
    replicatedhq-ship 基于Kustomize 项目的快速kubernetes 应用部署工具
    kustomize 模版自由的配置&&自定义kubernetes工具
    hasura graphql-engine v1.0.0-alpha30 remote schema stitch 试用
    Modern Data Lake with Minio : Part 2
    Modern Data Lake with Minio : Part 1
    使用rclone 进行minio 文件同步
    hasura graphql-engine v1.0.0-alpha30 功能试用
    hasura graphql-engine v1.0.0-alpha30 版本新功能介绍
  • 原文地址:https://www.cnblogs.com/163yun/p/9970167.html
Copyright © 2011-2022 走看看