spring cache实现二级缓存(Caffeine+Redis)

spring cache实现二级缓存(Caffeine+Redis),第1张

为什么需要二级缓存

redis会需要网络通信,本地缓存不需要,如果采用二级缓存会提高效率

本地缓存也可以叫做应用缓存,网络开销很小

如果采用用redis,会涉及到请求,有网络上的开销

本地缓存常用选型

本人现在没有过多研究选型相关,参考链接如下:
Java本地缓存技术选型(Guava Cache、Caffeine、Encache)
本地缓存选型(Guava/Caffeine/Ohc)及性能对比

我本次使用Caffeine

springboot中集成spring cache,已经有了多种缓存方式的实现,例如Redis、Caffeine、JCache、EhCache等

使用缓存流程
  1. 从一级缓存读出数据,如果存在则直接进行业务
  2. 不存在则读取二级缓存,如果存在则更新一级缓存
  3. 不存在则从数据库读,然后依次更新二级缓存、一级缓存,然后业务处理
spring cache

springcache本身并没有提供缓存的实现,但是他提供了一套的接口、代码规范、配置、注解等,这样很容易就可以整合各种缓存,springcache主要包含了两个核心的接口来统一管理缓存相关的实现。分别是:

  • org.springframework.cache.Cache 定义了通用缓存 *** 作的接口
  • org.springframework.cache.CacheManager 是一个spring的缓存管理器,主要用于管理不同的缓存

引入pom


    org.springframework.boot
    spring-boot-starter-cache

Caffeine

我们使用Caffeine来作为本地的缓存,只需要引入


    com.github.ben-manes.caffeine
    caffeine

实现步骤
  • 定义缓存的配置文件
  • 实现Cache 接口
  • 实现CacheManager接口
  • 增加spring boot配置类
具体实现 定义缓存的配置文件

定义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;
    }
}
实现Cache 接口

对多级缓存的具体 *** 作实现,例如对缓存的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 
     * @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());
    }
}
实现CacheManager接口

主要是对多级缓存的管理,核心方法就是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);

    }
}
增加spring boot配置类

只是我自己的一种实现,具体根据自己的项目来进行配置

@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 *** 作会更方便。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://www.outofmemory.cn/langs/791716.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-05
下一篇 2022-05-05

发表评论

登录后才能评论

评论列表(0条)

保存