多级缓存架构开发 一 (创建一个通用的缓存顶级接口)

实现思路:

                1) 采用caffeine作为一级缓存,caffeine是一个高性能的Java缓存库,采用的是Window TinyLfu回收策略,

提供了一个近乎最佳的缓存命中率;优点是数据就在应用内存中所以速度块;缺点是1)受应用内存限制,容量有限,

2)没有持久化,重启服务后缓存数据会丢失 3)分布式式环境下,缓存数据无法同步

                 2) 采用redis作为二级缓存,redis是一个高可用高性能的key-value数据库,支持多种数据类型,支持集群,

和应用服务器分开部署易于横向扩展;优点是 1) 支持多种数据类型,易于扩容 2)有持久化,重启应用服务器缓存数据

不会丢失 3) redis是一个集中式缓存,不存在和应用服务器之间数据同步的问题; 缺点是每次都需要访问redis,存在

IO浪费情况

总结:从上面的描述可以看出Caffeine和redis的优缺点正好相反,所以他们可以有效的互补。

 

实现步骤:

       1) 基本架构

              1.1 lombok 敏捷开发

              1.2 log4j 日志

              1.3) fastJson 和 jackson

       2) 定义缓存的顶级接口

package org.github.roger.cache;

import java.util.concurrent.Callable;

/**
 * 缓存的顶级接口
 */
public interface ICache {

    /**
     * @return 返回缓存的名称
     */
    String getName();

    /**
     * @return 返回真实的缓存对象
     */
    Object getRealCache();

    /**
     *  根据key获取其对应的缓存对象,如果没有就返回null
     * @param key
     * @return 返回缓存key对应的缓存对象
     */
    Object get(Object key);

    /**
     *  根据key获取其对应的缓存对象,并将返回的缓存对象转换成对应的类型
     *  如果没有就返回null
     * @param key
     * @param type  缓存对象的类型
     * @param <T>
     * @return
     */
    <T> T get(Object key,Class<T> type);

    /**
     *  根据key获取其对应的缓存对象,并将返回的缓存对象转换成对应的类型
     *  如果对应key不存在则调用valueLoader加载数据
     * @param key
     * @param valueLoader 加载缓存的回调方法
     * @param <T>
     * @return
     */
    <T> T get(Object key, Callable<T> valueLoader);

    /**
     * 将对应key-value放到缓存,如果key原来有值就直接覆盖
     *
     * @param key   缓存key
     * @param value 缓存的值
     */
    void put(Object key, Object value);

    /**
     * 如果缓存key没有对应的值就将值put到缓存,如果有就直接返回原有的值
     * 就相当于:
     * Object existingValue = cache.get(key);
     * if (existingValue == null) {
     *     cache.put(key, value);
     *     return null;
     * } else {
     *     return existingValue;
     * }
     * @param key   缓存key
     * @param value 缓存key对应的值
     * @return 因为值本身可能为NULL,或者缓存key本来就没有对应值的时候也为NULL,
     * 所以如果返回NULL就表示已经将key-value键值对放到了缓存中
     */
    Object putIfAbsent(Object key, Object value);

    /**
     * 在缓存中删除对应的key
     *
     * @param key 缓存key
     */
    void evict(Object key);

    /**
     * 清楚缓存
     */
    void clear();

}

     3.定义一个缓存顶级接口的抽象实现类,用于实现不同级别的缓存的公共方法

package org.github.roger.cache;

import com.alibaba.fastjson.JSON;
import org.github.roger.support.NullValue;
import org.springframework.util.Assert;

import java.util.concurrent.Callable;

public abstract class AbstractValueAdaptingCache implements ICache{

    //缓存名称
    private String name;

    public AbstractValueAdaptingCache(String name) {
        Assert.notNull(name,"缓存名称不能为空");
        this.name = name;
    }

    /**
     * 缓存对象是否允许为空
     * @return true 允许 false 不允许
     */
    public abstract boolean isAllowNullValues();

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

    public <T> T get(Object key, Class<T> type) {
        return (T) fromStoreValue(get(key));
    }

    protected Object fromStoreValue(Object storeValue) {
        if(isAllowNullValues() && storeValue instanceof NullValue){
            return null;
        }
        return storeValue;
    }

    protected Object toStoreValue(Object userValue){
        if(isAllowNullValues() && userValue == null){
            return NullValue.INSTANCE;
        }
        return userValue;
    }


    /**
     * {@link #get(Object, Callable)} 方法加载缓存值的包装异常
     */
    public class LoaderCacheValueException extends RuntimeException {

        private final Object key;

        public LoaderCacheValueException(Object key, Throwable ex) {
            super(String.format("加载key为 %s 的缓存数据,执行被缓存方法异常", JSON.toJSONString(key)), ex);
            this.key = key;
        }

        public Object getKey() {
            return this.key;
        }
    }
}

     4.创建一个多级模式缓存的类,继承上面的抽象类

            4.1) 对每个级别的缓存创建配置项类,以及多级配置项类

package org.github.roger.settings;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.github.roger.enumeration.ExpireMode;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class FirstCacheSetting implements Serializable {

    /**
     * 缓存初始Size
     */
    private int initialCapacity = 10;

    /**
     * 缓存最大Size
     */
    private int maximumSize = 500;

    /**
     * 缓存有效时间
     */
    private int expireTime = 0;

    /**
     * 缓存时间单位
     */
    private TimeUnit timeUnit = TimeUnit.MILLISECONDS;

    /**
     * 缓存失效模式{@link ExpireMode}
     */
    private ExpireMode expireMode = ExpireMode.WRITE;

    public boolean isAllowNullValues() {
        return false;
    }
}
package org.github.roger.settings;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.github.roger.enumeration.ExpireMode;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class SecondaryCacheSetting implements Serializable {

    /**
     * 缓存有效时间
     */
    private long expiration = 0;

    /**
     * 缓存主动在失效前强制刷新缓存的时间
     */
    private long preloadTime = 0;

    /**
     * 时间单位 {@link TimeUnit}
     */
    private TimeUnit timeUnit = TimeUnit.MICROSECONDS;

    /**
     * 是否强制刷新(走数据库),默认是false
     */
    private boolean forceRefresh = false;

    /**
     * 是否使用缓存名称作为 redis key 前缀
     */
    private boolean usePrefix = true;

    /**
     * 是否允许存NULL值
     */
    boolean allowNullValue = false;

    /**
     * 非空值和null值之间的时间倍率,默认是1。allowNullValue=true才有效
     *
     * 如配置缓存的有效时间是200秒,倍率这设置成10,
     * 那么当缓存value为null时,缓存的有效时间将是20秒,非空时为200秒
     */
    int magnification = 1;
}
package org.github.roger.settings;

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
public class MultiLayeringCacheSetting implements Serializable {
    private static final String SPLIT = "-";
    /**
     * 内部缓存名,由[一级缓存有效时间-二级缓存有效时间-二级缓存自动刷新时间]组成
     */
    private String internalKey;

    boolean useFirstCache = true;

    private FirstCacheSetting firstCacheSetting;

    private SecondaryCacheSetting secondaryCacheSetting;

    public MultiLayeringCacheSetting(FirstCacheSetting firstCacheSetting, SecondaryCacheSetting secondaryCacheSetting) {
        this.firstCacheSetting = firstCacheSetting;
        this.secondaryCacheSetting = secondaryCacheSetting;
        internalKey();
    }

    @JSONField(serialize = false, deserialize = false)
    private void internalKey() {
        // 一级缓存有效时间-二级缓存有效时间-二级缓存自动刷新时间
        StringBuilder sb = new StringBuilder();
        if (firstCacheSetting != null) {
            sb.append(firstCacheSetting.getTimeUnit().toMillis(firstCacheSetting.getExpireTime()));
        }
        sb.append(SPLIT);
        if (secondaryCacheSetting != null) {
            sb.append(secondaryCacheSetting.getTimeUnit().toMillis(secondaryCacheSetting.getExpiration()));
            sb.append(SPLIT);
            sb.append(secondaryCacheSetting.getTimeUnit().toMillis(secondaryCacheSetting.getPreloadTime()));
        }
        internalKey = sb.toString();
    }
}

 

 

 

            4.2) 利用配置项类和步骤3创建的缓存顶级接口的抽象实现类 创建的创建多级模式缓存的类

package org.github.roger;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.github.roger.cache.AbstractValueAdaptingCache;
import org.github.roger.cache.ICache;
import org.github.roger.settings.MultiLayeringCacheSetting;

import java.util.concurrent.Callable;

@Slf4j
public class MultiLayeringCache extends AbstractValueAdaptingCache {


    private AbstractValueAdaptingCache firstCache;

    private AbstractValueAdaptingCache secondCache;

    private boolean useFirstCache = true;
    private MultiLayeringCacheSetting multilayeringCacheSetting;

    public MultiLayeringCache(String name, AbstractValueAdaptingCache firstCache, AbstractValueAdaptingCache secondCache, boolean useFirstCache,MultiLayeringCacheSetting multilayeringCacheSetting) {
        super(name);
        this.firstCache = firstCache;
        this.secondCache = secondCache;
        this.useFirstCache = useFirstCache;
        this.multilayeringCacheSetting = multilayeringCacheSetting;
    }

    public Object getRealCache() {
        return this;
    }

    public Object get(Object key) {
        Object storeValue = null;
        if(useFirstCache){
            storeValue = firstCache.get(key);
            log.info("查询一级缓存。 key={},返回值是:{}", key, JSON.toJSONString(storeValue));
        }
        if(storeValue == null){
            storeValue = secondCache.get(key);
            firstCache.putIfAbsent(key, storeValue);
            log.info("查询二级缓存,并将数据放到一级缓存。 key={},返回值是:{}", key, JSON.toJSONString(storeValue));
        }
        return fromStoreValue(storeValue);
    }

    @Override
    public <T> T get(Object key, Class<T> type) {
        if (useFirstCache) {
            Object result = firstCache.get(key, type);
            log.info("查询一级缓存。 key={},返回值是:{}", key, JSON.toJSONString(result));
            if (result != null) {
                return (T) fromStoreValue(result);
            }
        }

        T result = secondCache.get(key, type);
        firstCache.putIfAbsent(key, result);
        log.info("查询二级缓存,并将数据放到一级缓存。 key={},返回值是:{}", key, JSON.toJSONString(result));
        return result;
    }

    public <T> T get(Object key, Callable<T> valueLoader) {
        if (useFirstCache) {
            Object result = firstCache.get(key);
            log.info("查询一级缓存。 key={},返回值是:{}", key, JSON.toJSONString(result));
            if (result != null) {
                return (T) fromStoreValue(result);
            }
        }
        T result = secondCache.get(key, valueLoader);
        firstCache.putIfAbsent(key, result);
        log.info("查询二级缓存,并将数据放到一级缓存。 key={},返回值是:{}", key, JSON.toJSONString(result));
        return result;
    }

    public void put(Object key, Object value) {
        secondCache.put(key, value);
        // 删除一级缓存
        if (useFirstCache) {
            //TODO deleteFirstCache(key);
        }
    }

    public Object putIfAbsent(Object key, Object value) {
        Object result = secondCache.putIfAbsent(key, value);
        // 删除一级缓存
        if (useFirstCache) {
           //TODO deleteFirstCache(key);
        }
        return result;
    }

    public void evict(Object key) {
        // 删除的时候要先删除二级缓存再删除一级缓存,否则有并发问题
        secondCache.evict(key);
        // 删除一级缓存
        if (useFirstCache) {
            //TODO deleteFirstCache(key);
        }
    }

    public void clear() {
        // 删除的时候要先删除二级缓存再删除一级缓存,否则有并发问题
        secondCache.clear();
        if (useFirstCache) {
            //TODO
            /*// 清除一级缓存需要用到redis的订阅/发布模式,否则集群中其他服服务器节点的一级缓存数据无法删除
            RedisPubSubMessage message = new RedisPubSubMessage();
            message.setCacheName(getName());
            message.setMessageType(RedisPubSubMessageType.CLEAR);
            // 发布消息
            RedisPublisher.publisher(redisTemplate, new ChannelTopic(getName()), message);*/
        }
    }

    public ICache getFirstCache() {
        return firstCache;
    }

    public ICache getSecondCache() {
        return secondCache;
    }

    public boolean isAllowNullValues() {
        return secondCache.isAllowNullValues();
    }
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值