springboot,redis,caffeine二级缓存搭建
Posted zhenghuasheng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot,redis,caffeine二级缓存搭建相关的知识,希望对你有一定的参考价值。
准备工作
- 1,springboot项目搭建 (open)
- 2,redis 集成
redis服务redis.conf 修改配置 notify-keyspace-events Ex
或使用客户端命令设置:config set notify-keyspace-events Ex
redis maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
redis相关配置
spring:
redis:
database: 0
host: 127.0.0.1
password: 123566
jedis:
pool:
max-active: 20
max-idle: 8
max-wait: 6000
min-idle: 0
port: 6379
timeout: 10000
/**
* @author zhenghuasheng
* @date 2016/5/9
*/
@Configuration
public class RedisConfig extends CachingConfigurerSupport
@Bean
public KeyGenerator localKeyGenerator()
return (target, method, params) ->
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName() + "#");
sb.append(method.getName() + ":");
for (Object obj : params)
if (obj != null)
sb.append(obj.toString());
return sb.toString();
;
/**
* 因加入二级缓存,本地缓存采用caffeine,cacheManage不能重复定义
* @return
*/
// @Bean
// public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisConnectionFactory connectionFactory)
// /* 默认配置, 默认超时时间为30s */
// RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration
// .defaultCacheConfig()
// .entryTtl(Duration.ofMinutes(30))// 设置缓存的默认过期时间,也是使用Duration设置.对注解形式生效
// .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
// .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
// .disableCachingNullValues(); // 不缓存空值
//
// RedisCacheManager cacheManager = RedisCacheManager.builder(RedisCacheWriter.lockingRedisCacheWriter
// (connectionFactory)).cacheDefaults(defaultCacheConfig).transactionAware().build();
// return cacheManager;
//
//redis键序列化使用StrngRedisSerializer
private RedisSerializer<String> keySerializer()
return new StringRedisSerializer();
//redis值序列化使用json序列化器
private RedisSerializer<Object> valueSerializer()
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
om.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
return new GenericJackson2JsonRedisSerializer(om);
private RedisSerializer<Object> jackson2JonSerializer()
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
om.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
jackson2JsonRedisSerializer.setObjectMapper(om);
return jackson2JsonRedisSerializer;
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory)
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
template.setKeySerializer(keySerializer());
template.setHashKeySerializer(keySerializer());
template.setValueSerializer(valueSerializer());
template.setHashValueSerializer(valueSerializer());
template.afterPropertiesSet();
return template;
// @Bean
// public JedisConnectionFactory redisConnectionFactory()
// JedisConnectionFactory factory = new JedisConnectionFactory();
// factory.setPassword(redisPassword);
// factory.setPoolConfig(new JedisPoolConfig());
factory.setHostName(host);
factory.setPort(port);
factory.setTimeout(timeout); //设置连接超时时间
// return factory;
//
caffeine引入
1,maven依赖
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.7.0</version>
</dependency>
2,缓存配置
caffeine配置
#caffeine配置
spring:
caffeine:
expireAfterAccess: 3600000
expireAfterWrite: 5000
refreshAfterWrite: 5000
initialCapacity: 100
maximumSize: 1000
maximumWeight: 5000
相应的配置文件:
@ConfigurationProperties(prefix = "spring.cache.caffeine")
public class CaffeineProperties
/**
* 访问后过期时间,单位毫秒
*/
private long expireAfterAccess;
/**
* 写入后过期时间,单位毫秒
*/
private long expireAfterWrite;
/**
* 写入后刷新时间,单位毫秒
*/
private long refreshAfterWrite;
/**
* 初始化大小
*/
private int initialCapacity;
/**
* 最大缓存对象个数,超过此数量时之前放入的缓存将失效
*/
private long maximumSize;
/**
* 由于权重需要缓存对象来提供,对于使用spring cache这种场景不是很适合,所以暂不支持配置
*/
private long maximumWeight;
//省略get,set方法
配置类注入配置 CaffeineProperties
@Configuration
@EnableConfigurationProperties(CaffeineProperties.class)
public class CaffeineAutoConfiguration
@Resource
private CaffeineProperties caffeineProperties;
@Bean
public com.github.benmanes.caffeine.cache.Cache<Object, Object> cache()
Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();
if(caffeineProperties.getExpireAfterAccess() > 0)
cacheBuilder.expireAfterAccess(caffeineProperties.getExpireAfterAccess(), TimeUnit.MILLISECONDS);
if(caffeineProperties.getExpireAfterWrite() > 0)
cacheBuilder.expireAfterWrite(caffeineProperties.getExpireAfterWrite(), TimeUnit.MILLISECONDS);
if(caffeineProperties.getInitialCapacity() > 0)
cacheBuilder.initialCapacity(caffeineProperties.getInitialCapacity());
if(caffeineProperties.getMaximumSize() > 0)
cacheBuilder.maximumSize(caffeineProperties.getMaximumSize());
if(caffeineProperties.getRefreshAfterWrite() > 0)
cacheBuilder.refreshAfterWrite(caffeineProperties.getRefreshAfterWrite(), TimeUnit.MILLISECONDS);
cacheBuilder.recordStats();
return cacheBuilder.build();
redis+caffeine 二级缓存配置读取
@ConfigurationProperties(prefix = "spring.cache.multi")
public class RedisCaffeineProperties
/**
* 是否存储空值,默认true,防止缓存穿透
*/
private boolean cacheNullValues = true;
/**
* 缓存key的前缀
*/
private String cachePrefix;
/**
* 缓存更新时通知其他节点的topic名称
*/
private String topic;
/**
* 全局过期时间,单位毫秒,默认不过期
*/
private long defaultExpiration = 0;
#spring cache 二级缓存配置
spring:
cache:
multi:
cacheNames: user
cacheNullValues: true
cachePrefix: qt_
defaultExpiration: 86400000
topic: cache:redis:caffeine:topic
二级缓存cache + cacheManage配置类:
public class RedisCaffeineCache extends AbstractValueAdaptingCache
private final Logger logger = LoggerFactory.getLogger(RedisCaffeineCache.class);
private String name;
private RedisTemplate<Object, Object> redisTemplate;
private Cache<Object, Object> caffeineCache;
private String cachePrefix;
private long defaultExpiration = 0;
private Map<String, Long> expires;
private String topic;
private Map<String, ReentrantLock> keyLockMap = new ConcurrentHashMap<String, ReentrantLock>();
public RedisCaffeineCache(String name, RedisTemplate<Object, Object> redisTemplate, Cache<Object, Object> caffeineCache, RedisCaffeineProperties redisCaffeineProperties)
super(redisCaffeineProperties.isCacheNullValues());
this.name = name;
this.redisTemplate = redisTemplate;
this.caffeineCache = caffeineCache;
this.cachePrefix = redisCaffeineProperties.getCachePrefix();
this.defaultExpiration = redisCaffeineProperties.getDefaultExpiration();
this.expires = redisCaffeineProperties.getExpires();
this.topic = redisCaffeineProperties.getTopic();
@Override
public String getName()
return this.name;
@Override
public Object getNativeCache()
return this;
@Override
public <T> T get(Object key, Callable<T> valueLoader)
Object value = lookup(key);
if(value != null)
return (T) value;
ReentrantLock lock = keyLockMap.get(key.toString());
if(lock == null)
logger.debug("create lock for key : ", key);
lock = new ReentrantLock();
keyLockMap.putIfAbsent(key.toString(), lock);
try
lock.lock();
value = lookup(key);
if(value != null)
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.getCause());
finally
lock.unlock();
@Override
public void put(Object key, Object value)
Object cacheKey = getKey(key);
if (!super.isAllowNullValues() && value == null)
this.evict(key);
return;
long expire = getExpire();
if(expire > 0)
redisTemplate.opsForValue().set(cacheKey, toStoreValue(value), expire, TimeUnit.MILLISECONDS);
else
redisTemplate.opsForValue().set(cacheKey, toStoreValue(value));
push(new CacheMessage(this.name, cacheKey));
caffeineCache.put(cacheKey, value);
@Override
public ValueWrapper putIfAbsent(Object key, Object value)
Object cacheKey = getKey(key);
Object prevValue = null;
// 考虑使用分布式锁,或者将redis的setIfAbsent改为原子性操作
synchronized (key)
prevValue = redisTemplate.opsForValue().get(cacheKey);
if(prevValue == null)
long expire = getExpire();
if(expire > 0)
redisTemplate.opsForValue().set(cacheKey, toStoreValue(value), expire, TimeUnit.MILLISECONDS);
else
redisTemplate.opsForValue().set(cacheKey, toStoreValue(value));
push(new CacheMessage(this.name, cacheKey));
caffeineCache.put(cacheKey, toStoreValue(value));
return toValueWrapper(prevValue);
@Override
public void evict(Object key)
Object cacheKey = getKey(key);
// 先清除redis中缓存数据,然后清除caffeine中的缓存,避免短时间内如果先清除caffeine缓存后其他请求会再从redis里加载到caffeine中
redisTemplate.delete(cacheKey);
push(new CacheMessage(this.name, cacheKey));
caffeineCache.invalidate(cacheKey);
@Override
public void clear()
// 先清除redis中缓存数据,然后清除caffeine中的缓存,避免短时间内如果先清除caffeine缓存后其他请求会再从redis里加载到caffeine中
Set<Object> keys = redisTemplate.keys(this.name.concat(":*"));
for(Object key : keys)
redisTemplate.delete(key);
push(new CacheMessage(this.name, null));
caffeineCache.invalidateAll();
@Override
protected Object lookup(Object key)
Object cacheKey = getKey(key);
Object value = caffeineCache.getIfPresent(cacheKey);
if(value != null)
logger.info("get cache from caffeine, the key is : ", cacheKey);
return value;
value = redisTemplate.opsForValue().get(cacheKey);
if(value != null)
logger.info("get cache from redis and put in caffeine, the key is : ", cacheKey);
caffeineCache.put(cacheKey, value);
return value;
private Object getKey(Object key)
return this.name.concat(":").concat(StringUtils.isEmpty(cachePrefix) ? key.toString() : cachePrefix.concat(":").concat(key.以上是关于springboot,redis,caffeine二级缓存搭建的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot&Caffeine 灵活支持多个缓存配置策略
(很全面)SpringBoot 使用 Caffeine 本地缓存
本地缓存Caffeine详解+整合SpringBoot的@EnableCaching