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 灵活支持多个缓存配置策略

自命为缓存之王的Caffeine

(很全面)SpringBoot 使用 Caffeine 本地缓存

本地缓存Caffeine详解+整合SpringBoot的@EnableCaching

本地缓存Caffeine详解+整合SpringBoot的@EnableCaching

SpringBoot 集成缓存性能之王 Caffeine