本地緩存工具類

package com.common.helper;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.lang.Nullable;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * 本地緩存管理器
 * <pre>
 *     v1.1
 *       - getByNameSpace
 *       - isExpired(..) 判斷是否已經死亡, 包含物理死亡和邏輯死亡.
 *
 *     v1.0
 *       - 增長構造, 可指定線程數
 *
 *     v0.0.1
 *       - 測試期
 * </pre>
 *
 * @author Nisus Liu
 * @version 1.1
 * @email liuhejun108@163.com
 * @date 2018/11/7 17:55
 */
@Slf4j
public abstract class LocalCacheManager<T> {
    public static final String NAMESPACE_KEY_JOIN = "::";
    /**
     * 緩存池子
     */
    protected Map<String, T> caches = new ConcurrentHashMap<String, T>();
    /**
     * 清除緩存的定時延遲任務(劊子手)
     */
    protected Map<String, Headsman> headsmans = new ConcurrentHashMap<String, Headsman>();
    protected ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(5);

    public LocalCacheManager() {
    }

    /**
     * @param poolSize 指定用於清除定時任務的線程數
     */
    public LocalCacheManager(int poolSize) {
        scheduler.setCorePoolSize(poolSize);
    }


    /**
     * 獲取指定key的緩存值
     *
     * @param key
     * @return
     */
    public T get(@Nullable String nameSpace, String key) {
        if (StringUtil.isEmpty(key)) {
            return null;
        }
        if (!StringUtil.isEmpty(nameSpace)) {
            //key其實是原生key加上命名空間前綴
            key = joinNameSpaceAndKey(nameSpace, key);
        }
        if (isExpired(key)) return null;

        return caches.get(key);
    }

    /**
     * 判斷是否死亡(實際死亡/邏輯死亡)
     * <p>邏輯死亡: 時間上看已通過期, 可是因爲秦楚線程阻塞, 尚未來得及清除</p>
     *
     * @param nskey joinNameSpaceAndKey
     * @return
     */
    private boolean isExpired(String nskey) {
        //防止延遲清除任務阻塞, 致使生存時間偏差, 這裏實現邏輯清除
        Headsman headsman = headsmans.get(nskey);
        if (headsman != null) {
            //死亡時間 <= 當前時間, 應該設置爲邏輯死亡, 返回null
            if (headsman.triggerTime <= System.nanoTime()) {
                // ?已經邏輯死亡, 但實際沒有死亡的值是否要在這裏清除呢?
                return true;
            } else {
                return false;
            }
        } // else: 物理死亡
        return !caches.containsKey(nskey);
    }

    private String joinNameSpaceAndKey(String nameSpace, String key) {
        if (StringUtils.isBlank(key)) {
            log.debug("`key` is NULL, return");
            return null;
        }
        if (!StringUtil.isEmpty(nameSpace)) {
            return nameSpace + NAMESPACE_KEY_JOIN + key;
        }
        return key;
    }

    /**
     * 增長OR更新緩存
     *
     * @param key
     * @param value
     */
    public boolean put(@Nullable String nameSpace, @NotNull String key, T value) {
        key = joinNameSpaceAndKey(nameSpace, key);
        if (key == null) {
            return false;
        }
        caches.put(key, value);
        return true;
    }

    /**
     * 更加OR更新緩存(可設置生存時間TTL,Time To Live)
     * <pre>
     *     Note: 注意避免併發對同一個 key 設置TTL, 結果比較隨機, 誰最後執行, 就以誰的TTL設置爲準.
     * </pre>
     *
     * @param key
     * @param value
     * @param ttl
     * @param unit
     */
    @Deprecated
    public void put(@Nullable String nameSpace, String key, T value, long ttl, TimeUnit unit) {
        String nsKey = joinNameSpaceAndKey(nameSpace, key);
        put(null, nsKey, value);
        long triggerTime = System.nanoTime() + unit.toNanos(ttl);

        //若此 key 已經有對應的殺死任務, 須要替換掉, 更新生存時間, 以最新的爲準
        Headsman headsman = headsmans.get(nsKey);
        if (headsman == null) {         //增長緩存
            headsman = new Headsman();
            headsmans.put(nsKey, headsman);
            headsman.task = new Runnable() {
                @Override
                public void run() {
                    //指定時間後清除此 nsKey
                    T rm = caches.remove(nsKey);
                    log.trace("cache expired, key: {}, value: {}", nsKey, rm);
                }
            };

        } else {                    //更新緩存
            //對於已經有設置過緩存的 nsKey, 任務用已有的, scheduledFuture 先取消舊的, 在new新的
            /*若是任務運行以前調用了該方法,那麼任務就不會被運行;
            若是任務已經完成或者已經被取消,那麼該方法方法不起做用;
            若是任務正在運行,而且 cancel 傳入參數爲 true,那麼便會去終止與 Future 關聯的任務。*/
            headsman.scheduledFuture.cancel(true);
        }
        headsman.scheduledFuture = new FutureTask(headsman.task, null);
        //時間轉換成毫秒
        headsman.triggerTime = triggerTime;

        scheduler.schedule(headsman.scheduledFuture, ttl, unit);
    }

    public void put(@Nullable String nameSpace, String key, T value, Duration ttl) {
        String nsKey = joinNameSpaceAndKey(nameSpace, key);
        put(null, nsKey, value);

        long triggerTime = System.nanoTime() + ttl.toNanos();

        //若此 key 已經有對應的殺死任務, 須要替換掉, 更新生存時間, 以最新的爲準
        Headsman headsman = headsmans.get(nsKey);
        if (headsman == null) {         //增長緩存
            headsman = new Headsman();
            headsmans.put(nsKey, headsman);
            headsman.task = new Runnable() {
                @Override
                public void run() {
                    //指定時間後清除此 nsKey
                    T rm = caches.remove(nsKey);
                    log.trace("cache expired, key: {}, value: {}", nsKey, rm);
                }
            };

        } else {                    //更新緩存
            //對於已經有設置過緩存的 nsKey, 任務用已有的, scheduledFuture 先取消舊的, 在new新的
            /*若是任務運行以前調用了該方法,那麼任務就不會被運行;
            若是任務已經完成或者已經被取消,那麼該方法方法不起做用;
            若是任務正在運行,而且 cancel 傳入參數爲 true,那麼便會去終止與 Future 關聯的任務。*/
            headsman.scheduledFuture.cancel(true);
        }
        headsman.scheduledFuture = new FutureTask(headsman.task, null);
        //時間轉換成毫秒
        headsman.triggerTime = triggerTime;

        scheduler.schedule(headsman.scheduledFuture, ttl.toMillis(), TimeUnit.MILLISECONDS);
    }


    public T evictCache(String nameSpace, String key) {
        if (key == null) {
            return null;
        }
        String nsKey = joinNameSpaceAndKey(nameSpace, key);
        Headsman hsm = headsmans.remove(nsKey);
        if (hsm != null) hsm.scheduledFuture.cancel(true);
        return caches.remove(nsKey);
    }


    /**
     * 清空當前命名空間下的全部value
     */
    public void evictCache(String nameSpace) {
        String prefix = nameSpace + NAMESPACE_KEY_JOIN;
        caches.forEach((k, v) -> {
            if (k.startsWith(prefix)) {
                evictCache(null, k);
            }
        });
    }

    public void evictAllCache() {
        caches.clear();
    }

    /**
     * 暴露指定命名空間下全部的緩存
     *
     * @param nameSpace
     * @return
     */
    public List<T> getByNameSpace(String nameSpace) {
        String prefix = nameSpace + NAMESPACE_KEY_JOIN;
        List<T> nsVals = new ArrayList<>();
        caches.forEach((k, v) -> {
            if (k.startsWith(prefix)) {
                if (!isExpired(k)) {
                    nsVals.add(v);
                }
            }
        });

        return nsVals;
    }


    class Headsman {
        /**
         * 死亡時間, 納秒值
         */
        public long triggerTime;
        /**
         * 對key更新緩存時, 舊的已有的會取消, 從新設置新的.
         */
        public FutureTask scheduledFuture;
        /**
         * 對於每一個 key, task是單例的
         */
        public Runnable task;
    }


}
相關文章
相關標籤/搜索