dubbo 结果缓存
官网:https://dubbo.apache.org/zh/docs/advanced/result-cache/
结果缓存
应用:加速热门数据访问速度,避免额外加缓存的工作量(如redis缓存)
配置
# 接口级别缓存
<dubbo:reference interface="com.foo.BarService" cache="lru" />
# 方法级别缓存
<dubbo:reference interface="com.foo.BarService">
<dubbo:method name="findBar" cache="lru" />
</dubbo:reference>
# cache可选值:
lru:默认每个线程存储1000个缓存,缓存满后,删除最长时间没有使用的缓存
lfu:默认每个线程存储1000个缓存,缓存满后,删除一段时间使用次数最少的缓存
threadlocal:threadlocal使用jvm内存存储缓存,无过期和删除策略,应该给jvm设置合适的内存避免内存溢出
jcache:与jsr107集成,使用jcache在内存中存储缓存
expiring:使用map在内存中存储缓存,设置过期时间,默认为180s,每隔10s检查一次缓存是否过期
CacheFactory:默认为lru缓存
@SPI("lru")
public interface CacheFactory {
/**
* CacheFactory implementation class needs to implement this return underlying cache instance for method against
* url and invocation.
* @param url
* @param invocation
* @return Instance of Cache containing cached value against method url and invocation.
*/
@Adaptive("cache")
Cache getCache(URL url, Invocation invocation);
}
META-INF/dubbo/internal/org.apache.dubbo.cache.CacheFactory
threadlocal=org.apache.dubbo.cache.support.threadlocal.ThreadLocalCacheFactory
lru=org.apache.dubbo.cache.support.lru.LruCacheFactory
jcache=org.apache.dubbo.cache.support.jcache.JCacheFactory
expiring=org.apache.dubbo.cache.support.expiring.ExpiringCacheFactory
lfu=org.apache.dubbo.cache.support.lfu.LfuCacheFactory
LruCacheFactory
public class LruCacheFactory extends AbstractCacheFactory {
/**
* Takes url as an method argument and return new instance of cache store implemented by LruCache.
* @param url url of the method
* @return ThreadLocalCache instance of cache
*/
@Override
protected Cache createCache(URL url) {
return new LruCache(url);
} //创建LruCache
}
LruCache
/**
* This class store the cache value per thread. If a service,method,consumer or provided is configured with key <b>cache</b>
* with value <b>lru</b>, dubbo initialize the instance of this class using {@link LruCacheFactory} to store method's returns value
* to server from store without making method call.
//为每个线程存储缓存,service、method、consumer、provider需要配置cache=lru
//如果缓存了值,就可使用缓存里面的数据,不触发方法调用
* <pre>
* e.g. 1) <dubbo:service cache="lru" cache.size="5000"/>
* 2) <dubbo:consumer cache="lru" />
* </pre>
* <pre>
* LruCache uses url's <b>cache.size</b> value for its max store size, if nothing is provided then
* default value will be 1000
* </pre> //cache.size配置缓存个数,默认1000
*
* @see Cache
* @see LruCacheFactory
* @see org.apache.dubbo.cache.support.AbstractCacheFactory
* @see org.apache.dubbo.cache.filter.CacheFilter
*/
public class LruCache implements Cache {
/**
* This is used to store cache records
*/
private final Map<Object, Object> store;
/**
* Initialize LruCache, it uses constructor argument <b>cache.size</b> value as its storage max size.
* If nothing is provided then it will use 1000 as default value.
* @param url A valid URL instance
*/
public LruCache(URL url) {
final int max = url.getParameter("cache.size", 1000);
this.store = new LRUCache<>(max);
}
/**
* API to store value against a key in the calling thread scope.
* @param key Unique identifier for the object being store.
* @param value Value getting store
*/
@Override
public void put(Object key, Object value) {
store.put(key, value);
}
/**
* API to return stored value using a key against the calling thread specific store.
* @param key Unique identifier for cache lookup
* @return Return stored object against key
*/
@Override
public Object get(Object key) {
return store.get(key);
}
}
LfuCacheFactory
public class LfuCacheFactory extends AbstractCacheFactory {
/**
* Takes url as an method argument and return new instance of cache store implemented by LfuCache.
* @param url url of the method
* @return ThreadLocalCache instance of cache
*/
@Override
protected Cache createCache(URL url) {
return new LfuCache(url);
} //创建LfuCache缓存
}
LfuCache
/**
* This class store the cache value per thread. If a service,method,consumer or provided is configured with key <b>cache</b>
* with value <b>lfu</b>, dubbo initialize the instance of this class using {@link LfuCacheFactory} to store method's returns value
* to server from store without making method call.
//为每个线程存储缓存,如果service、method、consumer、provider配置cache=lfu
//如果缓存命中,就不会触发方法调用,直接返回缓存值
* <pre>
* e.g. 1) <dubbo:service cache="lfu" cache.size="5000" cache.evictionFactor="0.3"/>
* 2) <dubbo:consumer cache="lfu" />
* </pre>
* <pre>
* LfuCache uses url's <b>cache.size</b> value for its max store size, url's <b>cache.evictionFactor</b> value for its eviction factor,
* default store size value will be 1000, default eviction factor will be 0.3
* </pre> //可用cache.size设置缓存个数,默认1000
*
* @see Cache
* @see LfuCacheFactory
* @see org.apache.dubbo.cache.support.AbstractCacheFactory
* @see org.apache.dubbo.cache.filter.CacheFilter
*/
public class LfuCache implements Cache {
/**
* This is used to store cache records
*/
private final LFUCache store;
/**
* Initialize LfuCache, it uses constructor argument <b>cache.size</b> value as its storage max size.
* If nothing is provided then it will use 1000 as default size value. <b>cache.evictionFactor</b> value as its eviction factor.
* If nothing is provided then it will use 0.3 as default value.
* @param url A valid URL instance
*/
public LfuCache (URL url) {
final int max = url.getParameter("cache.size", 1000);
final float factor = url.getParameter("cache.evictionFactor", 0.75f);
this.store = new LFUCache(max, factor);
}
/**
* API to store value against a key in the calling thread scope.
* @param key Unique identifier for the object being store.
* @param value Value getting store
*/
@Override
public void put(Object key, Object value) {
store.put(key, value);
}
/**
* API to return stored value using a key against the calling thread specific store.
* @param key Unique identifier for cache lookup
* @return Return stored object against key
*/
@Override
public Object get(Object key) {
return store.get(key);
}
}
JCacheFactory
/**
* JCacheFactory is factory class to provide instance of javax spi cache.Implement {@link org.apache.dubbo.cache.CacheFactory} by
* extending {@link AbstractCacheFactory} and provide
* @see AbstractCacheFactory
* @see JCache
* @see org.apache.dubbo.cache.filter.CacheFilter
* @see Cache
* @see CachingProvider
* @see javax.cache.Cache
* @see javax.cache.CacheManager
*/
public class JCacheFactory extends AbstractCacheFactory {
/**
* Takes url as an method argument and return new instance of cache store implemented by JCache.
* @param url url of the method
* @return JCache instance of cache
*/
@Override
protected Cache createCache(URL url) {
return new JCache(url);
}
}
JCache
/**
* This class store the cache value per thread. If a service,method,consumer or provided is configured with key <b>cache</b>
* with value <b>jcache</b>, dubbo initialize the instance of this class using {@link JCacheFactory} to store method's returns value
* to server from store without making method call.
*
* @see Cache
* @see JCacheFactory
* @see org.apache.dubbo.cache.support.AbstractCacheFactory
* @see org.apache.dubbo.cache.filter.CacheFilter
*/
public class JCache implements org.apache.dubbo.cache.Cache {
private final Cache<Object, Object> store;
public JCache(URL url) {
String method = url.getParameter(METHOD_KEY, "");
String key = url.getAddress() + "." + url.getServiceKey() + "." + method;
// jcache parameter is the full-qualified class name of SPI implementation
String type = url.getParameter("jcache");
CachingProvider provider = StringUtils.isEmpty(type) ? 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<>()
.setTypes(Object.class, Object.class)
.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) {
// concurrent cache initialization
cache = cacheManager.getCache(key);
}
}
this.store = cache;
}
@Override
public void put(Object key, Object value) {
store.put(key, value);
}
@Override
public Object get(Object key) {
return store.get(key);
}
}
ExpiringCacheFactory
public class ExpiringCacheFactory extends AbstractCacheFactory {
/**
* Takes url as an method argument and return new instance of cache store implemented by JCache.
* @param url url of the method
* @return ExpiringCache instance of cache
*/
@Override
protected Cache createCache(URL url) {
return new ExpiringCache(url);
} //创建ExpiringCache
}
ExpiringCache
/**
* This class store the cache value with the characteristic of expiration time. If a service,method,consumer or provided is configured with key <b>cache</b>
* with value <b>expiring</b>, dubbo initialize the instance of this class using {@link ExpiringCacheFactory} to store method's returns value
* to server from store without making method call.
* <pre>
* e.g. 1) <dubbo:service cache="expiring" cache.seconds="60" cache.interval="10"/>
* 2) <dubbo:consumer cache="expiring" />
* </pre>
//使用cache="expiring"配置expiring缓存
* <li>It used constructor argument url instance <b>cache.seconds</b> value to decide time to live of cached object.Default value of it is 180 second.</li>
* <li>It used constructor argument url instance <b>cache.interval</b> value for cache value expiration interval.Default value of this is 4 second</li>
//cache.seconds:缓存过期时间,默认180s
//cache.interval:缓存过期时间检查间隔,默认为4s
* @see Cache
* @see ExpiringCacheFactory
* @see org.apache.dubbo.cache.support.AbstractCacheFactory
* @see org.apache.dubbo.cache.filter.CacheFilter
*/
public class ExpiringCache implements Cache {
private final Map<Object, Object> store; //使用map存储缓存
public ExpiringCache(URL url) {
// cache time (second)
final int secondsToLive = url.getParameter("cache.seconds", 180);
// Cache check interval (second)
final int intervalSeconds = url.getParameter("cache.interval", 4);
ExpiringMap<Object, Object> expiringMap = new ExpiringMap<>(secondsToLive, intervalSeconds);
expiringMap.getExpireThread().startExpiryIfNotStarted();
this.store = expiringMap;
}
/**
* API to store value against a key in the calling thread scope.
* @param key Unique identifier for the object being store.
* @param value Value getting store
*/
@Override
public void put(Object key, Object value) {
store.put(key, value);
}
/**
* API to return stored value using a key against the calling thread specific store.
* @param key Unique identifier for cache lookup
* @return Return stored object against key
*/
@Override
public Object get(Object key) {
return store.get(key);
}
}
ThreadLocalCacheFactory
public class ThreadLocalCacheFactory extends AbstractCacheFactory {
/**
* Takes url as an method argument and return new instance of cache store implemented by ThreadLocalCache.
* @param url url of the method
* @return ThreadLocalCache instance of cache
*/
@Override
protected Cache createCache(URL url) {
return new ThreadLocalCache(url);
} //创建ThreadLocalCache
}
ThreadLocalCache
/**
* This class store the cache value per thread. If a service,method,consumer or provided is configured with key <b>cache</b>
* with value <b>threadlocal</b>, dubbo initialize the instance of this class using {@link ThreadLocalCacheFactory} to store method's returns value
* to server from store without making method call.
* <pre>
* e.g. <dubbo:service cache="threadlocal" />
* </pre> //使用cache="threadlocal"配置使用threadlocal缓存
* <pre>
* As this ThreadLocalCache stores key-value in memory without any expiry or delete support per thread wise, if number threads and number of key-value are high then jvm should be
* configured with appropriate memory.
* </pre>
//threadlocal在内存中存储缓存,没有过期和删除策略,
//如果线程数很大、或者缓存数据很多,应该设置一个合适的jvm内存
*
* @see org.apache.dubbo.cache.support.AbstractCacheFactory
* @see org.apache.dubbo.cache.filter.CacheFilter
* @see Cache
*/
public class ThreadLocalCache implements Cache {
/**
* Thread local variable to store cached data.
*/
private final ThreadLocal<Map<Object, Object>> store; //每个线程使用map存储缓存
/**
* Taken URL as an argument to create an instance of ThreadLocalCache. In this version of implementation constructor
* argument is not getting used in the scope of this class.
* @param url
*/
public ThreadLocalCache(URL url) {
this.store = ThreadLocal.withInitial(HashMap::new);
}
/**
* API to store value against a key in the calling thread scope.
* @param key Unique identifier for the object being store.
* @param value Value getting store
*/
@Override
public void put(Object key, Object value) {
store.get().put(key, value);
}
/**
* API to return stored value using a key against the calling thread specific store.
* @param key Unique identifier for cache lookup
* @return Return stored object against key
*/
@Override
public Object get(Object key) {
return store.get().get(key);
}
}
实现原理
CacheFilter:服务端、消费端均可设置缓存
@Activate(group = {CONSUMER, PROVIDER}, value = CACHE_KEY)
public class CacheFilter implements Filter {
private CacheFactory cacheFactory;
/**
* Dubbo will populate and set the cache factory instance based on service/method/consumer/provider configured
* cache attribute value. Dubbo will search for the class name implementing configured <b>cache</b> in file org.apache.dubbo.cache.CacheFactory
* under META-INF sub folders.
*
* @param cacheFactory instance of CacheFactory based on <b>cache</b> type
*/
public void setCacheFactory(CacheFactory cacheFactory) {
this.cacheFactory = cacheFactory;
}
/**
* 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))) {
Cache cache = cacheFactory.getCache(invoker.getUrl(), invocation);
//获取缓存
if (cache != null) { //如果缓存不为null,查找缓存中是否有对应的值
String key = StringUtils.toArgumentString(invocation.getArguments()); //用方法参数创建key
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);
} //如果有对应的value,直接返回value,不发起方法调用
}
Result result = invoker.invoke(invocation); //如果缓存中没有对应的value,发起远程调用
if (!result.hasException()) {
cache.put(key, new ValueWrapper(result.getValue()));
}
return result;
}
}
return invoker.invoke(invocation); //如果没有设置缓存,直接发起方法调用
}
/**
* Cache value wrapper.
*/
static class ValueWrapper implements Serializable {
private static final long serialVersionUID = -1777337318019193256L;
private final Object value;
public ValueWrapper (Object value) {
this.value = value;
}
public Object get() {
return this.value;
}
}
}
使用示例
*************
服务端
application.yml
dubbo:
application:
name: dubbo-provider
#register-mode: instance
registry:
address: localhost:2181
protocol: zookeeper
group: dubbo
protocol:
name: dubbo
#port: 20880
HelloService
public interface HelloService {
String get(Integer id);
}
HelloServiceImpl
@DubboService
public class HelloServiceImpl implements HelloService {
@Override
public String get(Integer id) {
System.out.println("hello");
return "hello";
}
}
*************
消费端
application.yml
dubbo:
application:
name: dubbo-consumer
registry:
protocol: zookeeper
address: localhost:2181
group: dubbo
#register-mode: instance
protocol:
name: dubbo
#port: 20880
server:
port: 8081
HelloService
public interface HelloService {
String get(Integer id);
}
HelloController
@RestController
public class HelloController {
@DubboReference(cache = "lru")
private HelloService helloService;
@RequestMapping("/hello")
public String hello(){
helloService.get(1);
helloService.get(1);
helloService.get(1);
helloService.get(1);
helloService.get(1);
helloService.get(1);
return "hello";
}
}
*************
使用测试
localhost:8081/hello:cacheFilter缓存的key为1,value包含查询结果