按照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 caches = new ConcurrentHashMap();
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,该接口规范了不同缓存的实现),接下来我们就分三部分来具体看一下不同的缓存接口的具体实现。
该缓存应用场景为
比如一个页面渲染,用到很多portal,每个portal都要去查用户信息,通过线程缓存,可以减少这种多余访问。
场景描述的核心内容是当前请求的上下文
配置示例如下:
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
引用: http://flychao88.iteye.com/blog/1977653
LRU原理
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:
1. 新数据插入到链表头部;
2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
3. 当链表满的时候,将链表尾部的数据丢弃。
LruCache类的实现
public class LruCache implements Cache {
private final Map store;
public LruCache(URL url) {
final int max = url.getParameter("cache.size", 1000); //定义了缓存的容量
this.store = new LinkedHashMap() {
private static final long serialVersionUID = -3834209229668463829L;
@Override
protected boolean removeEldestEntry(Entry 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);
}
}
}
那么,cache层的逻辑是如何一步一步“注入”到我们的业务逻辑里呢?这还是要追溯到dubbo的过滤器上,我们知道在dubbo初始化指定protocol的时候,会使用装饰器模式把所有需要加载的过滤器封装到目标protocol上,这个细节指引我来查看ProtocolFilterWrapper类:
refer() ---> buildInvokerChain()
|
V
private static Invoker buildInvokerChain(final Invoker invoker, String key, String group) {
Invoker last = invoker;
List 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 next = last;
last = new Invoker() {
public Class 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);
}
可以看出,这里根据不同的配置会初始化并使用不同的缓存实现。