专栏首页wannshan(javaer,RPC)dubbo 缓存的使用和实现解析

dubbo 缓存的使用和实现解析

dubbo缓存主要实现,对方法调用结果的缓存。 在服务消费方和提供方都可以配置使用缓存。 以消费方为例,可以配置全局缓存策略,这样所有服务引用都启动缓存 <dubbo:consumer cache="lru"/> 可以对某个服务引用配置缓存策略 <dubbo:reference id="demoService"   interface="demo.dubbo.api.DemoService" cache="lru"  > 也支持对单个方法启用缓存策略 <dubbo:reference id="demoService"    interface="demo.dubbo.api.DemoService" >    <dubbo:method name="sayHello" cache="lru"> </dubbo:method> </dubbo:reference> 服务方配置方法一样。

下面分析具体的实现过程

dubbo的缓存是通过过滤器实现的 通过 这篇博文 对注解Activate的认识,还有缓存的使用配置cache 这里找到了对应的Filter实现CacheFilter

//Activate指明服务方和消费方都可以启用缓存
@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))) {
           //invoker.getUrl().addParameter(Constants.METHOD_KEY, invocation.getMethodName()) 
	   //作为缓存对象的key 可知不同的服务提供者,每个方法都会单独分配一个缓存对象
	    Cache cache = cacheFactory.getCache(invoker.getUrl().addParameter(Constants.METHOD_KEY, invocation.getMethodName()));
            if (cache != null) {
	        //方法的参数作为key
                String key = StringUtils.toArgumentString(invocation.getArguments());
                if (cache != null && key != null) {
                    Object value = cache.get(key);
                    if (value != null) {
		        //缓存命中,直接返回,也就是说,
			//这里要注意,如果有多个过滤器,cache后面的过滤器不会执行
                        return new RpcResult(value);
                    }
                    Result result = invoker.invoke(invocation);
                    if (!result.hasException()) {
                        cache.put(key, result.getValue());
                    }
                    return result;
                }
            }
        }
        return invoker.invoke(invocation);
    }

}

关于cacheFactory的实现,这里看下由dubbo动态生成的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;
        //默认是 lru 缓存策略
        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])");
        com.alibaba.dubbo.cache.CacheFactory extension = (com.alibaba.dubbo.cache.CacheFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.cache.CacheFactory.class).getExtension(extName);
        return extension.getCache(arg0);
    }
}

duobb提供了CacheFactory的具体有三种实现,类图如下

AbstractCacheFactory抽象父类中定义了缓存对象的获取方法getCache

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;
    }
    //具体缓存实现在子类中,这也是dubbo一贯的设计模式,公共方法提到抽象类中
    protected abstract Cache createCache(URL url);

}
//LruCacheFactory子类,返回LruCache对象,实现LRU策略缓存
public class LruCacheFactory extends AbstractCacheFactory {

    protected Cache createCache(URL url) {
        return new LruCache(url);
    }

}
//JCacheFactory子类,返回LruCache对象,可与符合JSR107规范的缓存桥接
public class JCacheFactory extends AbstractCacheFactory {

    protected Cache createCache(URL url) {
        return new JCache(url);
    }

}
//ThreadLocalCacheFactory子类,返回LruCache对象,ThreadLocal利用当前线程缓存
public class ThreadLocalCacheFactory extends AbstractCacheFactory {

    protected Cache createCache(URL url) {
        return new ThreadLocalCache(url);
    }

}

上面提到的三种Cache对象类都实现了com.alibaba.dubbo.cache接口,类图如下,

Cache接口很简单

public interface Cache {
    //put value到缓存
    void put(Object key, Object value);
    //通过key 获取value
    Object get(Object key);

}

三种Cache类都实现也值得学习 LruCache类

public class LruCache implements Cache {

    private final Map<Object, Object> store;

    public LruCache(URL url) {
        //从配置中,获取cache大小
        final int max = url.getParameter("cache.size", 1000);
        //内部是LRUCache对象
        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);
    }

}

分析LRUCache源码,可以看到LRUCache继承了LinkedHashMap,而LinkedHashMap是个双向链表,它是个天然的lru数据结构 只要定义LinkedHashMap是有序的,比如LRUCache的构造函数的定义

  public LRUCache(int maxCapacity) {
        //默认有序的链表,初始数组申请空间大小16,负载因子0.75(触发扩展的填充度临界值)
        super(16, DEFAULT_LOAD_FACTOR, true);
        this.maxCapacity = maxCapacity;
    }

并重写LinkedHashMap的removeEldestEntry方法

    @Override
    //定义换出缓存对象的条,这里是大小超过最大容量
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
        return size() > maxCapacity;
    }

这样就可以完成一个LRU缓存容器的创建,具体实现,可读写LinkedHashMap源码

ThreadLocalCache类实现

public class ThreadLocalCache implements Cache {
    //通过ThreadLocal把store 绑定到当前线程
    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);
    }

}

JCache类实现

public class JCache implements com.alibaba.dubbo.cache.Cache {

    private final Cache<Object, Object> store;

    public JCache(URL url) {
        String method = url.getParameter(Constants.METHOD_KEY, "");
        //每个服务提供者每个方法,分配一个cache对象
        String key = url.getAddress() + "." + url.getServiceKey() + "." + method;
        // jcache 为SPI实现的全限定类名
        String type = url.getParameter("jcache");
        
        //通过CachingProvider 等jsr107规范相关接口 操作,这样,就能通过购spi 机制桥接各种缓存实现了
        CachingProvider provider = type == null || type.length() == 0 ? Caching.getCachingProvider() : Caching.getCachingProvider(type);
        CacheManager cacheManager = provider.getCacheManager();
        Cache<Object, Object> cache = cacheManager.getCache(key);
        if (cache == null) {
            try {
                //configure the cache
                MutableConfiguration config =
                        new MutableConfiguration<Object, Object>()
                                .setTypes(Object.class, Object.class)
                                 //通过cache.write.expire 设置失效时间,默认为1分钟,但不知道cache.write.expire在哪设置的??
                                .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.MILLISECONDS, url.getMethodParameter(method, "cache.write.expire", 60 * 1000))))
                                .setStoreByValue(false)
                                .setManagementEnabled(true)
                                .setStatisticsEnabled(true);
                cache = cacheManager.createCache(key, config);
            } catch (CacheException e) {
                // 初始化cache 的并发情况
                cache = cacheManager.getCache(key);
            }
        }

        this.store = cache;
    }

    public void put(Object key, Object value) {
        store.put(key, value);
    }

    public Object get(Object key) {
        return store.get(key);
    }

}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • dubbo负载均衡代码分析3(加权轮询策略)

    接上篇 https://cloud.tencent.com/developer/article/1109577 加权轮询,我第一次没理解,个人觉得不好理解。于是...

    技术蓝海
  • dubbo路由代码分析3(condition路由器)

    这篇说说,dubbo condition类型路由器的路由解析和执行过程 由 https://cloud.tencent.com/developer/artic...

    技术蓝海
  • dubbo路由代码分析4(script路由器file路由器)

    接上篇 https://cloud.tencent.com/developer/article/1109564 这篇分析下,script类型和file类型路由器...

    技术蓝海
  • Axios 各种请求方式传递参数格式

    跟着阿笨一起玩NET
  • Spring Boot 2.x(十四):整合Redis,看这一篇就够了

    Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 ...

    山禾说
  • 跟我学Spring Cloud(Finchley版)-20-Spring Cloud Config-Git仓库配置详解

    Config Server的占位符支持{application}、{profile}和{label}。

    用户1516716
  • Scrapy 爬虫模板--SitemapSpider

    SitemapSiper 允许我们通过网站的 Sitemap 文件中的 URL 来爬取一个网站。Sitemap 文件包含整个网站的每个网址链接,其中包含了上次更...

    喵叔
  • 如何通过Cloudera Manager配置Spark1和Spark2的运行环境

    大部分用户在使用CDH集群做Spark开发的时候,由于开发环境的JDK版本比CDH集群默认使用的JDK1.7.0_67-cloudera版本新,可能会出现Spa...

    Fayson
  • PHP微信开发入门(一)

    微信的接入在填写服务器URL时指向TOKEN验证的php文件,列如http://localhost/wlink.php 验证示例代码 <?php //如果接收到...

    Pulsar-V
  • Vxlan基础理解

    本篇文章转自http://blog.csdn.net/freezgw1985/article/details/16354897

    DevinGeng

扫码关注云+社区

领取腾讯云代金券