Dubbo配置参数详解-cache
Dubbo是一个由阿里开源的服务治理框架,笔者的公司重度使用Dubbo。Dubbo开源这么多年,配置项已经非常丰富,了解各个配置项的作用也变得非常重要,本系列将从源代码的角度分析Dubbo目前的最新版本(2.7.4)各个常用的配置项的具体含义以及是怎么起作用的。
画外音:目前Dubbo在开源中国举办的2019年度最受欢迎中国开源软件中排名第3名,支持Dubbo的朋友可以去投票哇。2019年度最受欢迎中国开源软件
cache有啥用?
cache:默认不开启,如果设置了cache,consumer端调用服务时,不用每次都发送请求到provider,dubbo会把第一次请求获取的结果缓存起来,后面相同的调用都直接使用该缓存;
dubbo中cache可以设置为:"lru","threadlocal","jcache"
/**
* Specify cache implementation for service invocation, legal values include: lru, threadlocal, jcache
*/
String cache() default "";
cache怎么使用?
在dubbo中使用cache很简单,直接设置cache就可以
@Reference(cache = "lru")
private HelloDubboService helloDubboService;
cache源码分析?
这里用常见的lru缓存分析下dubbo的缓存时如何实现的,是否有坑。
当Reference设置了cache后,consumer端的调用链会增加一个过滤器:CacheFilter
/**
* If cache is configured, dubbo will invoke method on each method call. If cache value is returned by cache store
* then it will return otherwise call the remote method and return value. If remote method's return value has error
* then it will not cache the value.
* @param invoker service
* @param invocation invocation.
* @return Cache returned value if found by the underlying cache store. If cache miss it will call target method.
* @throws RpcException
*/
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
if (cacheFactory != null && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), CACHE_KEY))) {
//这个根据调用URL获取了Cache,这里的key可以认为是接口名+方法名
Cache cache = cacheFactory.getCache(invoker.getUrl(), invocation);
if (cache != null) {
String key = StringUtils.toArgumentString(invocation.getArguments());
//根据传入的参数获取结果
Object value = cache.get(key);
if (value != null) {
if (value instanceof ValueWrapper) {
return AsyncRpcResult.newDefaultAsyncResult(((ValueWrapper) value).get(), invocation);
} else {
return AsyncRpcResult.newDefaultAsyncResult(value, invocation);
}
}
Result result = invoker.invoke(invocation);
if (!result.hasException()) {
cache.put(key, new ValueWrapper(result.getValue()));
}
return result;
}
}
return invoker.invoke(invocation);
}
首先根据URL获取了Cache,我们看下是怎么获取Cache的
/**
* Takes URL and invocation instance and return cache instance for a given url.
* @param url url of the method
* @param invocation invocation context.
* @return Instance of cache store used as storage for caching return values.
*/
@Override
public Cache getCache(URL url, Invocation invocation) {
//将方法名拼接到URL中,所以key可以认为是接口名+方法名这种形式
url = url.addParameter(METHOD_KEY, invocation.getMethodName());
String key = url.toFullString();
Cache cache = caches.get(key);
if (cache == null) {
caches.put(key, createCache(url));
cache = caches.get(key);
}
return cache;
}
可以看出dubbo是根据URL+调用方法名获取的Cache,如果没有对应的Cache,则新建一个。
那又是怎么根据方法参数获取返回值的呢?我们首先来看LruCache类
public class LruCache implements Cache {
private final Map<Object, Object> store;
public LruCache(URL url) {
int max = url.getParameter("cache.size", 1000);
this.store = new LRUCache(max);
}
public void put(Object key, Object value) {
this.store.put(key, value);
}
public Object get(Object key) {
return this.store.get(key);
}
}
可以看到LruCache将缓存的工作委托给了store,store是一个LRUCache
画外音:一个叫LruCache,另一个叫LRUCache,是LruCache将工作委托给了LRUCache,这个命名是要吐槽一下,怎么不能学习下Mybatis的Cache命名;
我们在来看下LRUCache的具体实现:
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = -5167631809472116969L;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private static final int DEFAULT_MAX_CAPACITY = 1000;
private final Lock lock = new ReentrantLock();
private volatile int maxCapacity;
public LRUCache() {
this(DEFAULT_MAX_CAPACITY);
}
public LRUCache(int maxCapacity) {
super(16, DEFAULT_LOAD_FACTOR, true);
this.maxCapacity = maxCapacity;
}
@Override
protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
return size() > maxCapacity;
}
//。。。。。。
}
可以看到,最终稿LRU缓存就是一个LinkedHashMap,默认大小是1000,也就是每一个接口只能缓存1000条数据,超过1000条数据就会将最近不常用的删掉;
cache有啥问题
经过分析Dubbo的缓存实现,发现有一个问题,缓存不能设置过期时间,也没找到清除缓存的接口,如果Provider的数据发生了变化,Consumer会一直拿不到最新的数据;