dubbo的缓存实现

简介: dubbo的缓存实现

这次的目标是缓存,没错,绝壁常用的一个知识点,我们怎么能不了解一下它的内部实现源码呢?!

dubbo的官方描述很简洁,好的封装就是这么强大, 让你用起来丝毫不费力。我们今天就费力的看一下dubbo是如何提供cache功能的。有想直接使用的童鞋,就可以跳过下面内容直面看官方提供的简单例子

按照SPI的要求,我们从配置文件中可以看到dubbo提供的三种缓存接口的入口:

threadlocal=com.alibaba.dubbo.cache.support.threadlocal.ThreadLocalCacheFactory
lru=com.alibaba.dubbo.cache.support.lru.LruCacheFactory
jcache=com.alibaba.dubbo.cache.support.jcache.JCacheFactory

先来看一下dubbo提供的AbstractCacheFactory的细节:

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);

}

很直观的看得出,该类完成了具体cache实现的实例化工作(注意getCache的返回类型Cache,该接口规范了不同缓存的实现),接下来我们就分三部分来具体看一下不同的缓存接口的具体实现。

ThreadLocal

如果你的配置如下:


<dubbo:reference interface="com.foo.BarService" cache="threadlocal" />

那就表明你使用的是该类型的缓存,根据SPI机制,会执行下面这个工厂类:

public class ThreadLocalCacheFactory extends AbstractCacheFactory {

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

注意该类继承了上面提到的AbstractCacheFactory。可以看出,真正实例化的具体缓存层实现是ThreadLocalCache类型。由于此类型是基于线程本地变量的,所以非常简单:

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);
    }
}

这里注意的是,为了遵循接口定义才需要初始化时传入url参数,但其实该类型的缓存实现是完全不需要额外参数的。

最后要叮嘱的是,该缓存应用场景为:

比如一个页面渲染,用到很多portal,每个portal都要去查用户信息,通过线程缓存,可以减少这种多余访问。

场景描述的核心内容是当前请求的上下文,可以结合dubbo的线程模型来更好的消化这一点。也许我们以后还会单独来分析这个主题。

LRU

类似ThreadLocal,我们就不再重复列举对应的工厂方法了,直接看LruCache类的实现:

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 LinkedHashMap<Object, Object>() {
            private static final long serialVersionUID = -3834209229668463829L;
            @Override
            protected boolean removeEldestEntry(Entry<Object, Object> eldest) { //jdk提供的接口,用于移除最旧条目的需求
                return size() > max;
            }
        };
    }

    public void put(Object key, Object value) {
        synchronized (store) {  //注意这里的同步条件
            store.put(key, value);
        }
    }

    public Object get(Object key) {
        synchronized (store) {  //注意这里的同步条件
            return store.get(key);
        }
    }
}

相比ThreadLocal,可以看出,该类型的缓存是跨线程的,也匹配我们常见的缓存场景。

JCache

对于我这种java新手,什么是JCache,显然需要科普一下,这里给出了我找到的几篇不错的文章:官府草根小栗子注解篇中文完美篇。由于内容太多,我就不胡乱翻译了~~

由于这部分的代码太简单,节省篇幅就不列源码了。不过我们的项目缓存是基于redis的,而我并没有找到支持JCache的redis客户端,不知道大家有没有推荐的啊~??

如何解析“cache”属性

那么,cache层的逻辑是如何一步一步“注入”到我们的业务逻辑里呢?这还是要追溯到dubbo的过滤器上,我们知道在dubbo初始化指定protocol的时候,会使用装饰器模式把所有需要加载的过滤器封装到目标protocol上,这个细节指引我来查看ProtocolFilterWrapper类:

refer() --->  buildInvokerChain()
                        |
                        V

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
    if (filters.size() > 0) {
        for (int i = filters.size() - 1; i >= 0; i --) {
            final Filter filter = filters.get(i);
            final Invoker<T> next = last;
            last = new Invoker<T>() {

                public Class<T> getInterface() {
                    return invoker.getInterface();
                }

                public URL getUrl() {
                    return invoker.getUrl();
                }

                public boolean isAvailable() {
                    return invoker.isAvailable();
                }

                public Result invoke(Invocation invocation) throws RpcException {
                    return filter.invoke(next, invocation);
                }

                public void destroy() {
                    invoker.destroy();
                }

                @Override
                public String toString() {
                    return invoker.toString();
                }
            };
        }
    }
    return last;
}

注意ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);这一行,单步调试可以得知它会返回所有需要“注入”的Filter逻辑,当然也包含我们关注的缓存:com.alibaba.dubbo.cache.filter.CacheFilter

注意看该类声明的开头:

@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)

这一行是关键哟,上面提到的getActivateExtension方法就是靠这一行注解工作的。dubbo以这种设计风格完成了大多数的功能,所以对于研究dubbo源码的童鞋,一定要多多注意。

经历了这一圈下来,所有过滤器就已经注入到我们的服务当中了。

业务层如何使用cache

最后再来仔细看一下com.alibaba.dubbo.cache.filter.CacheFilter类的invoke方法:

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    if (cacheFactory != null && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.CACHE_KEY))) {
        Cache cache = cacheFactory.getCache(invoker.getUrl().addParameter(Constants.METHOD_KEY, invocation.getMethodName()));
        if (cache != null) {
            String key = StringUtils.toArgumentString(invocation.getArguments());
            if (cache != null && key != null) {
                Object value = cache.get(key);
                if (value != null) {
                    return new RpcResult(value);
                }
                Result result = invoker.invoke(invocation);
                if (! result.hasException()) {
                    cache.put(key, result.getValue());
                }
                return result;
            }
        }
    }
    return invoker.invoke(invocation);
}

可以看出,这里根据不同的配置会初始化并使用不同的缓存实现,好了,关于缓存的分析就到此为止。

相关文章
|
4月前
|
存储 缓存 Dubbo
原来Dubbo中的缓存玩的这么花
原来Dubbo中的缓存玩的这么花
90 1
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
509 0
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
625 0
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
java初中级面试题(SSM+Mysql+微服务(SpringCloud+Dubbo)+消息队列(RocketMQ)+缓存(Redis+MongoDB)+设计模式+搜索引擎(ES)+JVM
788 0
|
存储 缓存 NoSQL
dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(六)Spring中Redis的缓存的使用
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.
1127 0
|
4月前
|
Dubbo Java 应用服务中间件
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
|
23天前
|
Dubbo Java 应用服务中间件
💥Spring Cloud Dubbo火爆来袭!微服务通信的终极利器,你知道它有多强大吗?🔥
【8月更文挑战第29天】随着信息技术的发展,微服务架构成为企业应用开发的主流模式,而高效的微服务通信至关重要。Spring Cloud Dubbo通过整合Dubbo与Spring Cloud的优势,提供高性能RPC通信及丰富的生态支持,包括服务注册与发现、负载均衡和容错机制等,简化了服务调用管理并支持多种通信协议,提升了系统的可伸缩性和稳定性,成为微服务通信领域的优选方案。开发者仅需关注业务逻辑,而无需过多关心底层通信细节,使得Spring Cloud Dubbo在未来微服务开发中将更加受到青睐。
49 0
|
1月前
|
负载均衡 Dubbo 应用服务中间件
框架巨擘:Dubbo如何一统异构微服务江湖,成为开发者的超级武器!
【8月更文挑战第8天】在软件开发中,微服务架构因灵活性和可扩展性备受欢迎。面对异构微服务的挑战,Apache Dubbo作为高性能Java RPC框架脱颖而出。它具备服务注册与发现、负载均衡及容错机制等核心特性,支持多种通信协议和序列化方式,能有效连接不同技术栈的微服务。Dubbo的插件化设计保证了面向未来的扩展性,使其成为构建稳定高效分布式系统的理想选择。
35 5
|
4月前
|
Dubbo Java 应用服务中间件
阿里巴巴资深架构师深度解析微服务架构设计之SpringCloud+Dubbo
软件架构是一个包含各种组织的系统组织,这些组件包括Web服务器,应用服务器,数据库,存储,通讯层),它们彼此或和环境存在关系。系统架构的目标是解决利益相关者的关注点。
|
4月前
|
Dubbo Cloud Native 应用服务中间件
【阿里云云原生专栏】云原生环境下的微服务治理:阿里云 Dubbo 与 Nacos 的深度整合
【5月更文挑战第25天】阿里云Dubbo和Nacos提供微服务治理的强大工具,整合后实现灵活高效的治理。Dubbo是高性能RPC框架,Nacos则负责服务发现和配置管理。整合示例显示,通过Nacos注册中心,服务能便捷注册发现,动态管理配置。简化部署,提升适应性,但也需注意服务稳定性和策略规划。这种整合为云原生环境的微服务架构带来强大支持,未来应用前景广阔。
262 2