天天看點

多級緩存架構開發 一 (建立一個通用的緩存頂級接口)

實作思路:

                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();
    }
}
           

繼續閱讀