spring boot 之 多级缓存实现

一、为什么使用多级缓存

        缓存的使用是解决高并发问题的一个重要途径,所以缓存很重要。一般情况下使用本地缓存,如ehcache,guava等就可以了,但是针对分布式、集群架构,本地缓存无法做到相互之间数据保持一致,如果使用redis缓存,则需要不断的去连接redis,这个中间也是有一定的资源消耗,在并发较小的时候这些消耗不影响系统,但是并发较大时就可能导致无法获取到redis连接,从而导致系统奔溃等等问题。

二、搭建自己的二级缓存

    要想搭建自己的二级缓存平台就需要了解spring boot的cache实现机制,cache的实现核心就是org.springframework.cache.Cache接口和org.springframework.cache.CacheManager接口,前者是缓存类,后者是管理缓存的,所以只要我们重写了这两个类,那么就可以实现缓存的重写。

1.重写cache

cache提供了一下方法

    String getName();

    Object getNativeCache();

    Cache.ValueWrapper get(Object var1);

     T get(Object var1, Class var2);

     T get(Object var1, Callable var2);

    void put(Object var1, Object var2);

    Cache.ValueWrapper putIfAbsent(Object var1, Object var2);

    void evict(Object var1);

    void clear();

这里我们只需要重写这些方法就行了,由于我们要实现二级缓存,所以要引入本地缓存和redis 缓存,也就是我们只需要在自定义的cache中实现本地缓存和redis 缓存同时使用就可以了。具体实现如下:

package com.wangcongming.cache.core.layering;

import com.alibaba.fastjson.JSON;
import com.wangcongming.cache.core.redis.cache.CustomizedRedisCache;
import com.wangcongming.cache.core.redis.listener.ChannelTopicEnum;
import com.wangcongming.cache.core.redis.listener.RedisPublisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.cache.support.NullValue;
import org.springframework.data.redis.core.RedisOperations;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * @author wangcongming
 * @Package com.wangcongming.cache.core.layering
 * @Description: 双缓存类  代码来源于网络,并在此基础做了修改
 * @date 2018/5/18 15:16
 */
public class LayeringCache extends AbstractValueAdaptingCache {
    Logger logger = LoggerFactory.getLogger(LayeringCache.class);

    /**
     * 缓存的名称
     */
    private final String name;

    /**
     * 是否使用一级缓存
     */
    private boolean usedFirstCache = true;

    /**
     * redi缓存
     */
    private final CustomizedRedisCache redisCache;

    /**
     * Caffeine缓存
     */
    private final CaffeineCache caffeineCache;

    RedisOperations redisOperations;

    /**
     * @param name              缓存名称
     * @param prefix            缓存前缀
     * @param redisOperations   操作Redis的RedisTemplate
     * @param expiration        redis缓存过期时间
     * @param allowNullValues   是否允许存NULL,默认是false
     * @param usedFirstCache    是否使用一级缓存,默认是true
     * @param caffeineCache     Caffeine缓存
     */
    public LayeringCache(String name, byte[] prefix, RedisOperations redisOperations,
                         long expiration, boolean allowNullValues, boolean usedFirstCache,
                         com.github.benmanes.caffeine.cache.Cache caffeineCache) {

        super(allowNullValues);
        this.name = name;
        this.usedFirstCache = usedFirstCache;
        this.redisOperations = redisOperations;
        this.redisCache = new CustomizedRedisCache(name, prefix, redisOperations, expiration, allowNullValues);
        this.caffeineCache = new CaffeineCache(name, caffeineCache, allowNullValues);
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public Object getNativeCache() {
        return this;
    }

    public CustomizedRedisCache getSecondaryCache() {
        return this.redisCache;
    }

    public CaffeineCache getFirstCache() {
        return this.caffeineCache;
    }

    @Override
    public ValueWrapper get(Object key) {
        ValueWrapper wrapper = null;
        if (usedFirstCache) {
            // 查询一级缓存
            wrapper = caffeineCache.get(key);
            logger.debug("查询一级缓存 key:{},返回值是:{}", key, JSON.toJSONString(wrapper));
        }

        if (wrapper == null) {
            // 查询二级缓存
            wrapper = redisCache.get(key);
//            caffeineCache.put(key, wrapper == null ? null : JSON.toJSONString(wrapper));
            logger.debug("查询二级缓存,并将数据放到一级缓存。 key:{},返回值是:{}", key, JSON.toJSONString(wrapper));
        }
        return wrapper;
    }

    @Override
    public  T get(Object key, Class type) {
        T value = null;
        if (usedFirstCache) {
            // 查询一级缓存
            value = caffeineCache.get(key, type);
            logger.debug("查询一级缓存 key:{},返回值是:{}", key, JSON.toJSONString(value));
        }

        if (value == null) {
            // 查询二级缓存
            value = redisCache.get(key, type);
            caffeineCache.put(key, value);
            logger.debug("查询二级缓存,并将数据放到一级缓存。 key:{},返回值是:{}", key, JSON.toJSONString(value));
        }
        return value;
    }

    @SuppressWarnings("unchecked")
	@Override
    public  T get(Object key, Callable valueLoader) {
        T value = null;
        if (usedFirstCache) {
//            Object o = caffeineCache.get(key).get();
            // 查询一级缓存,如果一级缓存没有值则调用getForSecondaryCache(k, valueLoader)查询二级缓存
            value = (T) caffeineCache.getNativeCache().get(key, k -> getForSecondaryCache(k, valueLoader));
//            this.put(key,value);
        } else {
//             直接查询二级缓存
            value = (T) getForSecondaryCache(key, valueLoader);
        }

        if (value instanceof NullValue) {
            return null;
        }
        return value;
    }

    @Override
    public void put(Object key, Object value) {
        if (usedFirstCache) {
            caffeineCache.put(key, value);
        }
        redisCache.put(key, value);
    }

    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        if (usedFirstCache) {
            caffeineCache.putIfAbsent(key, value);
        }
        return redisCache.putIfAbsent(key, value);
    }

    @Override
    public void evict(Object key) {
        // 删除的时候要先删除二级缓存再删除一级缓存,否则有并发问题
        redisCache.evict(key);
        if (usedFirstCache) {
            // 删除一级缓存需要用到redis的Pub/Sub(订阅/发布)模式,否则集群中其他服服务器节点的一级缓存数据无法删除
            Map message = new HashMap<>();
            message.put("cacheName", name);
            message.put("key", key);
            // 创建redis发布者
            RedisPublisher redisPublisher = new RedisPublisher(redisOperations, ChannelTopicEnum.REDIS_CACHE_DELETE_TOPIC.getChannelTopic());
            // 发布消息
            redisPublisher.publisher(message);
        }
    }

    @Override
    public void clear() {
        redisCache.clear();
        if (usedFirstCache) {
            // 清除一级缓存需要用到redis的订阅/发布模式,否则集群中其他服服务器节点的一级缓存数据无法删除
            Map message = new HashMap<>();
            message.put("cacheName", name);
            // 创建redis发布者
            RedisPublisher redisPublisher = new RedisPublisher(redisOperations, ChannelTopicEnum.REDIS_CACHE_CLEAR_TOPIC.getChannelTopic());
            // 发布消息
            redisPublisher.publisher(message);
        }
    }

    @Override
    protected Object lookup(Object key) {
        Object value = null;
        if (usedFirstCache) {
            value = caffeineCache.get(key);
            logger.debug("查询一级缓存 key:{},返回值是:{}", key, JSON.toJSONString(value));
        }
        if (value == null) {
            value = redisCache.get(key);
            logger.debug("查询二级缓存 key:{},返回值是:{}", key, JSON.toJSONString(value));
        }
        return value;
    }

    /**
     * 查询二级缓存
     *
     * @param key
     * @param valueLoader
     * @return
     */
    private  Object getForSecondaryCache(Object key, Callable valueLoader) {
        T value = redisCache.get(key, valueLoader);
        logger.debug("查询二级缓存 key:{},返回值是:{}", key, JSON.toJSONString(value));
        return toStoreValue(value);
    }
}

从上述代码我们可以看到,在LayeringCache的构造方法中我们直接创建了CaffeineCache和redisCache(这里的rediscache进行了重写),然后在get方法中,先去取本地缓存,没有才会去查询redisCache

现在缓存已经有了,那么怎么去实现缓存的创建呢,这里就要使用cachemanager来实现缓存的管理。

2.重写CacheManager

cachemanager是来管理缓存的,他负责cache的创建

由于我们重写了cache,那么原有的cachemanager就无法再创建此cache,所以需要重写。

首先我们来看cachemanager的具体方法有哪些

package org.springframework.cache;

import java.util.Collection;

public interface CacheManager {
    Cache getCache(String var1);

    Collection getCacheNames();
}

我们可以看到cachemanager接口只提供了两个方法,一个是获取cache,一个是获取所有cache name。

也就是我们只需要重写这两个方法就可以了

具体实现如下:

private final ConcurrentMap cacheMap = new ConcurrentHashMap(16);
private Caffeine cacheBuilder = Caffeine.newBuilder()
        .expireAfterAccess(DEFAULT_EXPIRE_AFTER_WRITE, TimeUnit.HOURS)
        .initialCapacity(DEFAULT_INITIAL_CAPACITY)
        .maximumSize(DEFAULT_MAXIMUM_SIZE);
    public LayeringCacheManager(RedisOperations redisOperations) {
        this(redisOperations, Collections.emptyList());
    }

    public LayeringCacheManager(RedisOperations redisOperations, Collection cacheNames) {
        this(redisOperations, cacheNames, false);
    }

    public LayeringCacheManager(RedisOperations redisOperations, Collection cacheNames, boolean allowNullValues) {
        this.allowNullValues = allowNullValues;
        this.redisOperations = redisOperations;
        setCacheNames(cacheNames);
    }

    @Override
    public Cache getCache(String name) {
        Cache cache = this.cacheMap.get(name);
        if (cache == null && this.dynamic) {
            synchronized (this.cacheMap) {
                cache = this.cacheMap.get(name);
                if (cache == null) {
                    cache = createCache(name);
                    this.cacheMap.put(name, cache);
                }
            }
        }
        return cache;
    }

   protected Cache createCache(String name) {
        return new LayeringCache(name, (usePrefix ? cachePrefix.prefix(name) : null), redisOperations,
                getSecondaryCacheExpirationSecondTime(name), isAllowNullValues(), getUsedFirstCache(name),
                createNativeCaffeineCache(name));
    }

    @Override
    public Collection getCacheNames() {
        return Collections.unmodifiableSet(this.cacheMap.keySet());
    }
如此我们就实现了二级缓存


注:部分代码来源于网络


你可能感兴趣的:(spring boot 之 多级缓存实现)