赞
踩
redis会需要网络通信,本地缓存不需要,如果采用二级缓存会提高效率
本地缓存也可以叫做应用缓存,网络开销很小
如果采用用redis,会涉及到请求,有网络上的开销
本人现在没有过多研究选型相关,参考链接如下:
Java本地缓存技术选型(Guava Cache、Caffeine、Encache)
本地缓存选型(Guava/Caffeine/Ohc)及性能对比
springboot中集成spring cache,已经有了多种缓存方式的实现,例如Redis、Caffeine、JCache、EhCache等
springcache本身并没有提供缓存的实现,但是他提供了一套的接口、代码规范、配置、注解等,这样很容易就可以整合各种缓存,springcache主要包含了两个核心的接口来统一管理缓存相关的实现。分别是:
引入pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
我们使用Caffeine来作为本地的缓存,只需要引入
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
定义properties配置属性类,主要用于以配置文件进行配置。
@ConfigurationProperties(prefix = "model.cache.multi") public class RedisCaffeineCacheProperties { /** * 静态缓存名称 */ private Set<String> cacheNames = new HashSet<>(); /** * 一级缓存名称 */ private Set<String> firstCacheNames = new HashSet<>(); /** * 是否存储空值,默认true */ private boolean storeNullValues = true; /** * 缓存redis key的前缀 */ private String redisKeyPrefix = ""; private Redis redis = new Redis(); private Caffeine caffeine = new Caffeine(); public class Redis { //设置全局过期时间,默认8天 private long defaultExpiration = 60 * 60 * 24 * 8; //缓存更新时通知其他节点的topic名称 private String topic = "cache:redis:caffeine:topic"; //每个cacheName的过期时间,单位毫秒,优先级比defaultExpiration高 private Map<String, Long> expires = new HashMap<>(); public Map<String, Long> getExpires() { return expires; } public void setExpires(Map<String, Long> expires) { this.expires = expires; } public long getDefaultExpiration() { return defaultExpiration; } public void setDefaultExpiration(long defaultExpiration) { this.defaultExpiration = defaultExpiration; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } } public class Caffeine { //访问后过期时间,单位毫秒 private long expireAfterAccess; //写入后过期时间,单位毫秒 private long expireAfterWrite; //写入后刷新时间,单位毫秒 private long refreshAfterWrite; //设置最大缓存对象个数,超过此数量时之前放入的缓存将失效 private int maximumSize; //初始化大小 private int initialCapacity; //每个cacheName的写入后过期时间(expireAfterWrite),找不到就用上面定义的配置。单位毫秒 private Map<String, Long> expires = new HashMap<>(); public long getExpireAfterAccess() { return expireAfterAccess; } public void setExpireAfterAccess(long expireAfterAccess) { this.expireAfterAccess = expireAfterAccess; } public long getExpireAfterWrite() { return expireAfterWrite; } public void setExpireAfterWrite(long expireAfterWrite) { this.expireAfterWrite = expireAfterWrite; } public long getRefreshAfterWrite() { return refreshAfterWrite; } public void setRefreshAfterWrite(long refreshAfterWrite) { this.refreshAfterWrite = refreshAfterWrite; } public int getMaximumSize() { return maximumSize; } public void setMaximumSize(int maximumSize) { this.maximumSize = maximumSize; } public int getInitialCapacity() { return initialCapacity; } public void setInitialCapacity(int initialCapacity) { this.initialCapacity = initialCapacity; } public Map<String, Long> getExpires() { return expires; } public void setExpires(Map<String, Long> expires) { this.expires = expires; } } public boolean isStoreNullValues() { return storeNullValues; } public void setStoreNullValues(boolean storeNullValues) { this.storeNullValues = storeNullValues; } public String getRedisKeyPrefix() { return redisKeyPrefix; } public void setRedisKeyPrefix(String redisKeyPrefix) { this.redisKeyPrefix = redisKeyPrefix; } public Set<String> getCacheNames() { return cacheNames; } public void setCacheNames(Set<String> cacheNames) { this.cacheNames = cacheNames; } public Redis getRedis() { return redis; } public void setRedis(Redis redis) { this.redis = redis; } public Caffeine getCaffeine() { return caffeine; } public void setCaffeine(Caffeine caffeine) { this.caffeine = caffeine; } public Set<String> getFirstCacheNames() { return firstCacheNames; } public void setFirstCacheNames(Set<String> firstCacheNames) { this.firstCacheNames = firstCacheNames; } }
对多级缓存的具体操作实现,例如对缓存的get、put、evict操作
public class RedisCaffeineCache extends AbstractValueAdaptingCache { private static final Logger log = LoggerFactory.getLogger(RedisCaffeineCache.class); private RedisTemplate<String, Object> redisTemplate; /** * 多级缓存的唯一标识 */ private String uniqueKey = UUID.randomUUID().toString(); /** * 多级缓存的名称 */ private String name; /** * redis key前缀 */ private String cachePrefix; /** * redis key 的过期时间 */ private Map<String, Long> expires; /** * redis 全局过期时间 */ private long defaultExpiration = 0; /** * 定义一个全局的公平锁 */ private static ReentrantLock fairLock = new ReentrantLock(true); /** * 是否开启一级缓存 */ private boolean firstCache; /** * 一级缓存 */ private Cache<Object, Object> caffeineCache; /** * redis消息队列的topic */ private String topic = "cache:redis:caffeine:topic"; public RedisCaffeineCache(String name, RedisTemplate<String, Object> redisTemplate, Cache<Object, Object> caffeineCache,RedisCaffeineCacheProperties redisCaffeineCacheProperties) { super(redisCaffeineCacheProperties.isStoreNullValues()); this.name = name; this.redisTemplate = redisTemplate; this.cachePrefix = redisCaffeineCacheProperties.getRedisKeyPrefix(); this.expires = redisCaffeineCacheProperties.getRedis().getExpires(); this.defaultExpiration = redisCaffeineCacheProperties.getRedis().getDefaultExpiration(); this.topic = redisCaffeineCacheProperties.getRedis().getTopic(); this.caffeineCache = caffeineCache; if (this.caffeineCache == null) { this.firstCache = false; }else{ this.firstCache = true; } } /** * 执行底层查询操作 * @param key * @return */ @Override protected Object lookup(Object key) { //如果开启了一级缓存,则先从一级缓存中查询 if(firstCache){ Object value = caffeineCache.getIfPresent(key); if(ObjectUtils.isNotEmpty(value)) { log.info("从caffeine中获取数据:{}",value); return value; } } String redisKey = getKey(key); Object value = redisTemplate.opsForValue().get(redisKey); if(ObjectUtils.isNotEmpty(value)) { log.info("从redis中获取数据:{}",value); caffeineCache.put(key, value); } return value; } @Override public String getName() { return this.name; } @Override public Object getNativeCache() { return this; } /** * get cache value from RedisCaffeineCache * @param key * @param valueLoader * @param <T> * @return */ @Override public <T> T get(Object key, Callable<T> valueLoader) { Object value = lookup(key); if(ObjectUtils.isNotEmpty(value)) { return (T) value; } fairLock.lock(); try { value = lookup(key); if(ObjectUtils.isNotEmpty(value)) { return (T) value; } value = valueLoader.call(); Object storeValue = toStoreValue(value); put(key, storeValue); return (T) value; } catch (Exception e) { throw new ValueRetrievalException(key, valueLoader, e); }finally { fairLock.unlock(); } } /** * put value into RedisCaffeineCache * @param key * @param value */ @Override public void put(Object key, Object value) { if (!super.isAllowNullValues() && ObjectUtils.isEmpty(value)) { this.evict(key); return; } long expire = getExpire(); if(expire > 0) { redisTemplate.opsForValue().set(getKey(key), toStoreValue(value), expire, TimeUnit.MILLISECONDS); }else { redisTemplate.opsForValue().set(getKey(key), toStoreValue(value)); } if (firstCache) { //发送消息到消息队列 push(new CacheMessage(name,key,uniqueKey)); caffeineCache.put(key, value); } } /** * 如果不能存在put,存在返回oldValue * @param key * @param value * @return */ @Override public ValueWrapper putIfAbsent(Object key, Object value) { String cacheKey = getKey(key); Object oldValue = null; synchronized (key) { oldValue = redisTemplate.opsForValue().get(cacheKey); if (ObjectUtils.isEmpty(oldValue)) { long expire = getExpire(); if (expire > 0) { redisTemplate.opsForValue().set(cacheKey, toStoreValue(value), expire, TimeUnit.MILLISECONDS); }else { redisTemplate.opsForValue().set(cacheKey, toStoreValue(value)); } if (firstCache) { push(new CacheMessage(name,key,uniqueKey)); caffeineCache.put(key, value); } } } return toValueWrapper(oldValue); } @Override public void evict(Object key) { //删除redis中的缓存 redisTemplate.delete(getKey(key)); //删除一级缓存 if (firstCache) { push(new CacheMessage(name,key,uniqueKey)); caffeineCache.invalidate(key); } } @Override public void clear() { Set<String> scan = RedisHelper.scan(redisTemplate, this.name.concat(":*")); redisTemplate.delete(scan); if (firstCache) { push(new CacheMessage(name,null,uniqueKey)); caffeineCache.invalidateAll(); } } /** * 重写fromStoreValue方法 ,避免获取NULLValue报错的问题 * @param storeValue * @return */ @Nullable @Override protected Object fromStoreValue(Object storeValue) { if (super.isAllowNullValues() && (storeValue == NullValue.INSTANCE || storeValue instanceof NullValue)) { return null; } return storeValue; } /** * 获取redisKey缓存名称 * * @return */ private String getKey(Object key) { return name.concat(":").concat(StringUtils.isEmpty(cachePrefix) ? key.toString() : cachePrefix.concat(":").concat(key.toString())); } /** * 获取redis 缓存过期时间 * @return */ private long getExpire() { Long cacheNameExpire = expires.get(name); return cacheNameExpire == null ? defaultExpiration : cacheNameExpire; } public Cache<Object, Object> getLocalCache() { return caffeineCache; } /** * 通知其他缓存删除一级缓存 * @param message */ private void push(CacheMessage message) { redisTemplate.convertAndSend(topic, message); } /** * 清除多级缓存值 * @param key */ public void clearLocal(Object key) { if (!firstCache) { return; } if (key == null) { caffeineCache.invalidateAll(); } else { caffeineCache.invalidate(key); } } public String getUniqueKey(){ return this.uniqueKey; } }
其中的push方法主要是通知其他缓存删除特定的一级缓存
CacheMessage实现如下
public class CacheMessage implements Serializable { private static final long serialVersionUID = -3742394520710710440L; /** * 多级缓存的名称 */ private String name; /** * 一级缓存的key */ private Object key; /** * 多级缓存的唯一标识 */ private String redisCaffeineCacheUniqueKey; public CacheMessage() { } public CacheMessage(String name, Object key, String redisCaffeineCacheUniqueKey) { this.name = name; this.key = key; this.redisCaffeineCacheUniqueKey = redisCaffeineCacheUniqueKey; } public CacheMessage(String name, Object key) { this.name = name; this.key = key; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Object getKey() { return key; } public void setKey(Object key) { this.key = key; } public String getRedisCaffeineCacheUniqueKey() { return redisCaffeineCacheUniqueKey; } public void setRedisCaffeineCacheUniqueKey(String redisCaffeineCacheUniqueKey) { this.redisCaffeineCacheUniqueKey = redisCaffeineCacheUniqueKey; } }
当给redis发送消息后,redis接收到消息会做处理,需要重写redis的消息监听器
CacheMessageListener实现如下
public class CacheMessageListener implements MessageListener { private static final Logger log = LoggerFactory.getLogger(CacheMessageListener.class); private RedisTemplate<String, Object> redisTemplate; private RedisCaffeineCacheManager redisCaffeineCacheManager; public CacheMessageListener(RedisTemplate<String, Object> redisTemplate, RedisCaffeineCacheManager redisCaffeineCacheManager) { super(); this.redisTemplate = redisTemplate; this.redisCaffeineCacheManager = redisCaffeineCacheManager; } @Override public void onMessage(Message message, byte[] pattern) { //处理收到的消息,去清除其他多级缓存的一级缓存 CacheMessage cacheMessage = (CacheMessage) redisTemplate.getValueSerializer().deserialize(message.getBody()); log.debug("receive a redis topic message, clear local cache, the cacheName is {}, the key is {},the uniqueKey is {}", cacheMessage.getName(), cacheMessage.getKey(), cacheMessage.getRedisCaffeineCacheUniqueKey()); redisCaffeineCacheManager.clearLocal(cacheMessage.getName(), cacheMessage.getKey(), cacheMessage.getRedisCaffeineCacheUniqueKey()); } }
主要是对多级缓存的管理,核心方法就是getCache() 。
public class RedisCaffeineCacheManager implements CacheManager { /** * key: cacheName value: redisCaffeineCache */ private ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<String, Cache>(); private RedisTemplate<String, Object> stringKeyRedisTemplate; private RedisCaffeineCacheProperties redisCaffeineCacheProperties; /** * 是否根据cacheName动态生成 */ private boolean dynamic = true; /** * 不动态根据cacheName创建Cache的实现时,自定义设置的缓存名 */ private Set<String> cacheNames; /** * 一级缓存名集合 */ private Set<String> firstCacheNames; /** * 一级缓存的过期时间 */ private Map<String, Long> expires; public RedisCaffeineCacheManager(RedisTemplate<String, Object> redisTemplate, RedisCaffeineCacheProperties redisCaffeineCacheProperties) { super(); this.stringKeyRedisTemplate = redisTemplate; this.redisCaffeineCacheProperties = redisCaffeineCacheProperties; this.cacheNames = redisCaffeineCacheProperties.getCacheNames(); this.firstCacheNames = redisCaffeineCacheProperties.getFirstCacheNames(); this.expires = redisCaffeineCacheProperties.getCaffeine().getExpires(); } @Override public Cache getCache(String name) { Cache cache = cacheMap.get(name); if (cache != null) { return cache; } //如果不动态根据cacheName创建Cache的实现,且没有配置静态缓存 则返回空 if (!dynamic && cacheNames.isEmpty()) { return null; } //如果开启一级缓存 if (firstCacheNames.contains(name)) { //开启一级缓存 cache = new RedisCaffeineCache(name, stringKeyRedisTemplate, caffeineCache(name), redisCaffeineCacheProperties); }else { // 只开启二级缓存 即只开启redis缓存 cache = new RedisCaffeineCache(name, stringKeyRedisTemplate, null, redisCaffeineCacheProperties); } Cache oldCache = cacheMap.putIfAbsent(name, cache); return oldCache == null ? cache : oldCache; } /** * 构建一个caffeineCache 即构建一个一级缓存 * @param name * @return */ private com.github.benmanes.caffeine.cache.Cache caffeineCache(String name) { Caffeine<Object, Object> caffeineBuilder = Caffeine.newBuilder(); if(redisCaffeineCacheProperties.getCaffeine().getExpireAfterAccess() > 0) { caffeineBuilder.expireAfterAccess(redisCaffeineCacheProperties.getCaffeine().getExpireAfterAccess(), TimeUnit.MILLISECONDS); } Long expire = expires.get(name); if (expire != null && expire > 0) { caffeineBuilder.expireAfterWrite(expire, TimeUnit.MILLISECONDS); }else if (redisCaffeineCacheProperties.getCaffeine().getExpireAfterWrite() > 0){ caffeineBuilder.expireAfterWrite(redisCaffeineCacheProperties.getCaffeine().getExpireAfterAccess(), TimeUnit.MILLISECONDS); } if (redisCaffeineCacheProperties.getCaffeine().getRefreshAfterWrite() > 0) { caffeineBuilder.refreshAfterWrite(redisCaffeineCacheProperties.getCaffeine().getRefreshAfterWrite(), TimeUnit.MILLISECONDS); } if (redisCaffeineCacheProperties.getCaffeine().getInitialCapacity() > 0) { caffeineBuilder.initialCapacity(redisCaffeineCacheProperties.getCaffeine().getInitialCapacity()); } if (redisCaffeineCacheProperties.getCaffeine().getMaximumSize() > 0) { caffeineBuilder.maximumSize(redisCaffeineCacheProperties.getCaffeine().getMaximumSize()); } return caffeineBuilder.build(); } @Override public Collection<String> getCacheNames() { return this.cacheNames; } /** * 清除 多级缓存中的某一级缓存 * @param name * @param key * @param redisCaffeineCacheUniqueKey */ public void clearLocal(String name, Object key,String redisCaffeineCacheUniqueKey) { Cache cache = cacheMap.get(name); if(cache == null) { return ; } RedisCaffeineCache redisCaffeineCache = (RedisCaffeineCache) cache; //判断是否是自己发送的消息 如果是自己发送的就不需要清楚缓存了 if (StringUtils.isNoneEmpty(redisCaffeineCacheUniqueKey) && redisCaffeineCache.getUniqueKey().equals(redisCaffeineCacheUniqueKey)) { return; } redisCaffeineCache.clearLocal(key); } }
只是我自己的一种实现,具体根据自己的项目来进行配置
@Configuration @EnableConfigurationProperties(RedisCaffeineCacheProperties.class) @EnableCaching public class RedisCaffeineCacheAutoConfiguration { @Autowired private RedisCaffeineCacheProperties redisCaffeineCacheProperties; @Bean public RedisCaffeineCacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) { return new RedisCaffeineCacheManager(redisTemplate, redisCaffeineCacheProperties); } @Bean @ConditionalOnBean(name = {"redisTemplate", "cacheManager"}) public RedisMessageListenerContainer redisMessageListenerContainer(RedisTemplate<String, Object> redisTemplate,RedisCaffeineCacheManager redisCaffeineCacheManager) { RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisTemplate.getConnectionFactory()); CacheMessageListener cacheMessageListener = new CacheMessageListener(redisTemplate, redisCaffeineCacheManager); redisMessageListenerContainer.addMessageListener(cacheMessageListener, new ChannelTopic(redisCaffeineCacheProperties.getRedis().getTopic())); return redisMessageListenerContainer; } }
以上就是利用spring cache 实现多级缓存的核心,仅供参考。
这是我第一次接触了解这种缓存实现,不足之处请见谅。
关于设置锁的地方,建议使用Redisson操作会更方便。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。