在普通应用中,要么使用本地缓存(如EhCache)或直接使用缓存服务(远程缓存,如Redis)。
在较高并发下,需要把本地缓存和远程缓存结合起来使用。期望达到效果是:先从本地缓存读取,如果有,直接返回;如果本地没有,从远程缓存读取,远程缓存有,把值写到本地,再返回;如果远程缓存也没有,就需要查数据库了。从数据库查到数据后,再把结果放入到缓存。
Spring提供了一个org.springframework.cache.support.CompositeCacheManager,可以实现多个缓存依次查找,只要查找到就返回。但是在本地缓存没有,远程缓存有的情况下,每次都需要从远程缓存获取,不符合期望。
因此需要自己实现org.springframework.cache.CacheManager和org.springframework.cache.Cache来达到目标,本文以EhCache为本地缓存,Redis为远程缓存为例来进行说明。
首先定义Exception,根据业务需要,定义CacheManager,该CacheManager需要从org.springframework.cache.CacheManager继承,如果不添加方法,该接口可以去掉。
package com.nowfox.commons.cache;
/**
*
* Title: CacheManager
* Description: 缓存管理器泛型接口
* Copyright: Copyright (c) 2017
*
* @version 1.0
*/
public interface CacheManager extends org.springframework.cache.CacheManager {
/**
* 创建缓存
*
* @param name
* @param keyType
* @param valueType
* @return
* @throws CacheException
*/
public <K, V> Cache<K, V> createCache(String name, Class<K> keyType, Class<V> valueType) throws CacheException;
/**
* 创建缓存
*
* @param name
* @param keyType
* @param valueType
* @param expireTimeOut
* 失效时长,单位:秒
* @return
* @throws CacheException
*/
public <K, V> Cache<K, V> createCache(String name, Class<K> keyType, Class<V> valueType, long expireTimeOut)
throws CacheException;
}
接下来定义Cache。
package com.nowfox.commons.cache;
/**
*
* Title: Cache
* Description: 缓存相关操作接口
* Copyright: Copyright (c) 2017
*
* @version 1.0
*/
public interface Cache<K, V> extends org.springframework.cache.Cache{
/**
* 根据key移除缓存中对象,spring中定义为evict
*
* @param key
* @throws CacheException
*/
public void remove(K key) throws CacheException;
}
编写Cache抽象类,实现EhCache和Redis Cache公用部分。
package com.nowfox.commons.cache;
import java.util.concurrent.Callable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.support.SimpleValueWrapper;
public abstract class AbstractCache<K, V> implements Cache<K, V> {
protected final Logger logger = LoggerFactory.getLogger(getClass());
protected String name;
@Override
public String getName() {
return name;
}
@SuppressWarnings("unchecked")
@Override
public ValueWrapper get(Object key) {
if (key == null) {
return null;
}
Object value = lookup((K) key);
return toValueWrapper(value);
}
@SuppressWarnings("unchecked")
@Override
public <T> T get(Object key, Class<T> type) {
if (key == null) {
return null;
}
Object value = lookup((K) key);
if (value != null && type != null && !type.isInstance(value)) {
throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value);
}
return (T) value;
}
@SuppressWarnings("unchecked")
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
// TODO 待完善
Object value = lookup((K) key);
return (T) value;
}
@SuppressWarnings("unchecked")
@Override
public void evict(Object key) {
remove((K) key);
}
public abstract V lookup(K key);
protected ValueWrapper toValueWrapper(Object value) {
return (value != null ? new SimpleValueWrapper(value) : null);
}
}
EhCacheCacheManager的实现
package com.nowfox.commons.cache.ehcache;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.EntryUnit;
import org.ehcache.expiry.Duration;
import org.ehcache.expiry.Expirations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.nowfox.commons.cache.Cache;
import com.nowfox.commons.cache.CacheException;
import com.nowfox.commons.cache.CacheManager;
public class EhCacheCacheManager implements CacheManager {
private static final Logger logger = LoggerFactory.getLogger(EhCacheCacheManager.class);
@SuppressWarnings("rawtypes")
private final ConcurrentMap<String, Cache> map = new ConcurrentHashMap<String, Cache>();
private long expireTimeOut = 600;
private org.ehcache.CacheManager localCacheManager;
private CacheManager remoteCacheManager;
public EhCacheCacheManager() {
localCacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true);
}
public void setExpireTimeOut(long expireTimeOut) {
this.expireTimeOut = expireTimeOut;
}
public void setRemoteCacheManager(CacheManager remoteCacheManager) {
this.remoteCacheManager = remoteCacheManager;
}
@Override
public <K, V> Cache<K, V> createCache(String name, Class<K> keyType, Class<V> valueType) throws CacheException {
return createCache(name, keyType, valueType, expireTimeOut);
}
@SuppressWarnings("unchecked")
@Override
public <K, V> Cache<K, V> createCache(String name, Class<K> keyType, Class<V> valueType, long expireTimeOut)
throws CacheException {
//根据自己需要修改config
CacheConfigurationBuilder<K, V> cacheConfig = CacheConfigurationBuilder
.newCacheConfigurationBuilder(keyType, valueType,
ResourcePoolsBuilder.newResourcePoolsBuilder().heap(100, EntryUnit.ENTRIES))
.withDispatcherConcurrency(4)
.withExpiry(Expirations.timeToLiveExpiration(Duration.of(expireTimeOut, TimeUnit.SECONDS)));
org.ehcache.Cache<K, V> localCache = localCacheManager.createCache(name, cacheConfig);
Cache<K, V> cache = map.get(name);
if (cache == null) {
Cache<K, V> remoteCache = null;
if (remoteCacheManager != null) {
remoteCache = (Cache<K, V>) remoteCacheManager.createCache(name, keyType, valueType, expireTimeOut);
}
cache = new EhCacheCache<K, V>(name, localCache, remoteCache);
map.put(name, cache);
if (logger.isInfoEnabled()) {
logger.info("创建本地缓存:{}", name);
}
}
return cache;
}
@Override
public Cache<?, ?> getCache(String name) {
return map.get(name);
}
@Override
public Collection<String> getCacheNames() {
return map.keySet();
}
}
以下是EhCacheCache的实现,主要关键为lookup方法
package com.nowfox.commons.cache.ehcache;
import com.nowfox.commons.cache.AbstractCache;
import com.nowfox.commons.cache.Cache;
import com.nowfox.commons.cache.CacheException;
public class EhCacheCache<K, V> extends AbstractCache<K, V> {
private org.ehcache.Cache<K, V> localCache;
private Cache<K, V> remoteCache;
public EhCacheCache(String name, org.ehcache.Cache<K, V> localCache, Cache<K, V> remoteCache) {
this.name = name;
this.localCache = localCache;
this.remoteCache = remoteCache;
}
@Override
public void remove(K key) throws CacheException {
localCache.remove(key);
if (remoteCache != null) {
remoteCache.remove(key);
}
}
@Override
public void clear() throws CacheException {
localCache.clear();
}
@Override
public Object getNativeCache() {
return localCache;
}
@SuppressWarnings("unchecked")
@Override
public void put(Object key, Object value) {
if (key != null && value != null) {
localCache.put((K) key, (V) value);
if (remoteCache != null) {
remoteCache.put(key, value);//根据情况使用异步
}
}
}
@SuppressWarnings("unchecked")
@Override
public ValueWrapper putIfAbsent(Object key, Object value) {
if (value == null) {
return get(key);
}
Object existingValue = localCache.putIfAbsent((K) key, (V) value);
if (remoteCache != null) {
remoteCache.putIfAbsent(key, existingValue);
}
return toValueWrapper(existingValue);
}
/**
* 先查找本地,没有再查找远程缓存,如果有,写到本地缓存
*
* @param key
* @return
*/
@SuppressWarnings("unchecked")
@Override
public V lookup(K key) {
V object = localCache.get(key);
if (object != null) {
return object;
}
if (remoteCache == null) {
return null;
}
ValueWrapper remoteValueWrapper = remoteCache.get(key);
if (remoteValueWrapper == null) {
return null;
} else {
localCache.put((K) key, (V) remoteValueWrapper.get());
return (V) remoteValueWrapper.get();
}
}
}
XML的缓存配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.2.xsd">
<cache:annotation-driven cache-manager="localCacheManager"
proxy-target-class="true" />
<bean id="remoteCacheManager" class="com.nowfox.commons.cache.redis.RedisCacheManager">
<property name="namespace" value="agent"></property>
<property name="template" ref="redisTemplate"></property>
</bean>
<bean id="localCacheManager" class="com.nowfox.commons.cache.ehcache.EhCacheCacheManager">
<property name="expireTimeOut" value="600"></property>
<property name="remoteCacheManager" ref="remoteCacheManager"></property>
</bean>
</beans>
在使用时,EhCache必需先创建对应的Cache。
@Configuration
public class CacheConfig {
@Autowired
private CacheManager localCacheManager;
@Autowired
private CacheManager remoteCacheManager;
@PostConstruct
public void init() {
localCacheManager.createCache("agent", String.class, AgentVO.class);
}
}
在具体的agentService实现类中,结合@Cacheable注解,即可实现本地缓存->远程缓存->数据库的读取方式,在加上@Cacheable注解后,如果第一次是从数据库获取的数据,可自动把缓存放入到本地和远程缓存。后续再获取时,会自动从本地缓存获取。
本文使用的EhCache为3.4.0,pom.xml节选如下
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.4.0</version>
</dependency>