一個(gè)簡(jiǎn)單實(shí)用的函數(shù)式緩存工具類

一個(gè)簡(jiǎn)單實(shí)用的函數(shù)式緩存工具類:封裝了基本的緩存增刪查操作,提供了熱點(diǎn)數(shù)據(jù)集中失效和緩存穿透的統(tǒng)一解決方案,以及在此基礎(chǔ)上的開(kāi)發(fā)模型。

背景介紹

日常開(kāi)發(fā)中緩存使用越來(lái)越普遍而問(wèn)題也接憧而至,首先是開(kāi)發(fā)方式的繁瑣:查詢緩存->有就直接返回,沒(méi)有就查詢數(shù)據(jù)庫(kù)或者調(diào)用其他服務(wù)獲取,再保存至緩存中...如:

    /**
     * 獲取渠道下,字段值
     * <li></li>
     * @param channelNo: 渠道
     * @param field: 字段
     * @return: java.lang.String 值
     */
    private String getChannelConfigFieldValue(String channelNo,String field){
        ....
          //從緩存獲取
            String key = StringUtils.join(CHANNEL_CONFIG_KEY_PREFIX,channelNo);
            String result = RedisUtils.get(key);
            JSONObject jsonObject;
            if(StringUtils.isNotBlank(result)){
                jsonObject = JSON.parseObject(result);
            }else{
                //加載并緩存數(shù)據(jù)
                jsonObject = loadAndCacheChannelConfigInfo(channelNo);
            }
       ....
    }
    /**
     * 加載并緩存渠道配置信息
     * <li></li>
     * @param channelNo: 渠道號(hào)
     * @return: com.alibaba.fastjson.JSONObject
     */
    private JSONObject loadAndCacheChannelConfigInfo(String channelNo){
            .....
            //發(fā)起遠(yuǎn)程調(diào)用/訪問(wèn)數(shù)據(jù)庫(kù) 加載數(shù)據(jù)
            Map<String, Object> queryParam = new HashMap<>();
            ...
            log.info("加載并緩存渠道配置信息 param => {}", JSON.toJSONString(queryParam));
            String result = HttpClientUtils.sendHttpPostJson(getChannelInfoUrl, JSON.toJSONString(queryParam));
            ...
            JSONObject jsonObject = JSON.parseObject(result);
            ...
            //設(shè)置緩存
            String key = StringUtils.join(CHANNEL_CONFIG_KEY_PREFIX,channelNo);
            RedisUtils.set(key, result,CACHE_EXPIRE_TIME);
            log.info("加載并緩存渠道配置信息成功  key=> {}", key);
            return jsonObject;
            ...
    }

此類模板式的代碼出現(xiàn)在使用緩存的任何地方,且大多是CV大法而來(lái),既不美觀也不優(yōu)雅,還極易出錯(cuò)。其次,在使用緩存的過(guò)程中由于使用方法或者對(duì)問(wèn)題的處理不當(dāng),可能給數(shù)據(jù)庫(kù)或者依賴的服務(wù)造成嚴(yán)重的影響,常見(jiàn)的問(wèn)題如:熱點(diǎn)數(shù)據(jù)集中失效,緩存穿透等。所以需要將緩存開(kāi)發(fā)使用過(guò)程中的共性抽離出來(lái),抽象并封裝使之模板化,規(guī)范化,并最終形成一個(gè)可擴(kuò)展,易維護(hù),使用方便的工具或者工具集。

設(shè)計(jì)思路

  • 統(tǒng)一緩存開(kāi)發(fā)使用模式,提供/約定一套開(kāi)發(fā)方法/模型。

    動(dòng)靜分離:抽象公共部分,函數(shù)化變化部分。

  • 封裝常見(jiàn)問(wèn)題處理流程默認(rèn)實(shí)現(xiàn),并提供擴(kuò)展機(jī)制。

    SPI機(jī)制:內(nèi)部默認(rèn)實(shí)現(xiàn),外部可擴(kuò)展。

  • 資源使用細(xì)粒度保護(hù),減少資源競(jìng)爭(zhēng)。

    分布式鎖:redisson分布式鎖

知識(shí)準(zhǔn)備

一,函數(shù)式編程:

  1. 什么是函數(shù)式編程

    函數(shù)式編程并不是Java新提出的概念,屬于編程范式中的一種,它起源于一個(gè)數(shù)學(xué)問(wèn)題,我們并不需要過(guò)多的了解函數(shù)式編程的歷史,要追究它的歷史以及函數(shù)式編程,關(guān)于范疇論、柯里化早就讓人立馬放棄學(xué)習(xí)函數(shù)式編程了,對(duì)于函數(shù)式編程我們所要知道的是,它能將一個(gè)行為傳遞作為參數(shù)進(jìn)行傳遞。

  2. 函數(shù)式編程的目的

    Java8出現(xiàn)之前,我們關(guān)注的往往是某一類對(duì)象應(yīng)該具有什么樣的屬性,當(dāng)然這也是面向?qū)ο蟮暮诵?-對(duì)數(shù)據(jù)進(jìn)行抽象。但是java8出現(xiàn)以后,這一點(diǎn)開(kāi)始出現(xiàn)變化,似乎在某種場(chǎng)景下,更加關(guān)注某一類共有的行為(這似乎與之前的接口有些類似),這也就是java8提出函數(shù)式編程的目的。

對(duì)象-函數(shù).png
  1. Lambda表達(dá)式

    不得不提增加Lambda的目的,其實(shí)就是為了支持函數(shù)式編程,而為了支持Lambda表達(dá)式,才有了函數(shù)式接口。另外,為了在面對(duì)大型數(shù)據(jù)集合時(shí),為了能夠更加高效的開(kāi)發(fā),編寫(xiě)的代碼更加易于維護(hù),更加容易運(yùn)行在多核CPU上,java在語(yǔ)言層面增加了Lambda表達(dá)式。

  2. 函數(shù)式接口

    在Java中有一個(gè)接口中只有一個(gè)方法表示某特定方法并反復(fù)使用,例如Runnable接口中只有run方法就表示執(zhí)行的線程任務(wù)。Java8中對(duì)于這樣的接口有了一個(gè)特定的名稱——函數(shù)式接口。Java8中即使是支持函數(shù)式編程,也并沒(méi)有再標(biāo)新立異另外一種語(yǔ)法表達(dá)。所以只要是只有一個(gè)方法的接口,都可以改寫(xiě)成Lambda表達(dá)式。在Java8中新增了java.util.function用來(lái)支持Java的函數(shù)式編程,其中的接口均是只包含一個(gè)方法,與其他接口的區(qū)別:

    • 函數(shù)式接口中只能有一個(gè)抽象方法(我們?cè)谶@里不包括與Object的方法重名的方法);
    • 可以有從Object繼承過(guò)來(lái)的抽象方法,因?yàn)樗蓄惖淖罱K父類都是Object;
    • 接口中唯一抽象方法的命名并不重要,因?yàn)楹瘮?shù)式接口就是對(duì)某一行為進(jìn)行抽象,主要目的就是支持Lambda表達(dá)式。

    Java8還提供了@FunctionalInterface注解來(lái)幫助我們標(biāo)識(shí)函數(shù)式接口。另外需要注意的是函數(shù)式接口的目的是對(duì)某一個(gè)行為進(jìn)行封裝,某些接口可能只是巧合符合函數(shù)式接口的定義。java8的Function包下的類是為了支持基本類型而添加的接口,部分類圖如下:

Supplier.png
  1. 進(jìn)一步學(xué)習(xí)

    推薦大家閱讀《java8 in action》 以及《Java函數(shù)式編程》,前者是對(duì)java8新特性全面的闡述,深入淺出,注重實(shí)戰(zhàn),非常適合入門(mén)。后者則注重函數(shù)式編程背后的故事,教你如何構(gòu)建函數(shù)式數(shù)據(jù)結(jié)構(gòu),以及函數(shù)式編程范式如何幫助你編寫(xiě)更好的程序。

二,SPI機(jī)制

  1. java SPI 機(jī)制

    SPI(Service Provider Interface),是JDK內(nèi)置的一種 服務(wù)提供發(fā)現(xiàn)機(jī)制,可以用來(lái)啟用框架擴(kuò)展和替換組件,主要是被框架的開(kāi)發(fā)人員使用,比如java.sql.Driver接口,其他不同廠商可以針對(duì)同一接口做出不同的實(shí)現(xiàn),MySQL和PostgreSQL都有不同的實(shí)現(xiàn)提供給用戶,而Java的SPI機(jī)制可以為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)。Java中SPI機(jī)制主要思想是將裝配的控制權(quán)移到程序之外,在模塊化設(shè)計(jì)中這個(gè)機(jī)制尤其重要,其核心思想就是 解耦。

java-spi.png
  1. SPI機(jī)制的約定:

    • 在META-INF/services/目錄中創(chuàng)建以接口全限定名命名的文件該文件內(nèi)容為Api具體實(shí)現(xiàn)類的全限定名
    • 使用ServiceLoader類動(dòng)態(tài)加載META-INF中的實(shí)現(xiàn)類
    • 如SPI的實(shí)現(xiàn)類為Jar則需要放在主程序classPath中
    • Api具體實(shí)現(xiàn)類必須有一個(gè)不帶參數(shù)的構(gòu)造方法
  2. 不足

    • 不能按需加載,需要遍歷所有的實(shí)現(xiàn),并實(shí)例化,然后在循環(huán)中才能找到我們需要的實(shí)現(xiàn)。如果不想用某些實(shí)現(xiàn)類,或者某些類實(shí)例化很耗時(shí),它也被載入并實(shí)例化了,這就造成了浪費(fèi)。
    • 獲取某個(gè)實(shí)現(xiàn)類的方式不夠靈活,只能通過(guò) Iterator 形式獲取,不能根據(jù)某個(gè)參數(shù)來(lái)獲取對(duì)應(yīng)的實(shí)現(xiàn)類。
    • 多個(gè)并發(fā)多線程使用 ServiceLoader 類的實(shí)例是不安全的。
  3. 改造

    基于 xkernel

三,分布式鎖

這里用到分布式鎖 starter,詳見(jiàn):分布式鎖 starter

實(shí)施步驟

一,緩存模塊設(shè)計(jì)

整個(gè)模塊圍繞2個(gè)接口和1個(gè)工具類展開(kāi):緩存管理接口,緩存函數(shù)接口,緩存整合工具類。

  • 緩存管理接口主要定義常用緩存基本操作接口,如設(shè)置,獲取,刪除緩存等,默認(rèn)實(shí)現(xiàn)基于Redis。

  • 緩存函數(shù)接口則是基于Suppliper和Function2個(gè)函數(shù)式接口,分別定義了獲取單個(gè)對(duì)象和集合對(duì)象的4個(gè)常用接口,后續(xù)可根據(jù)實(shí)際需要擴(kuò)展,默認(rèn)實(shí)現(xiàn)基于緩存管理接口+分布式鎖 starter 。

  • 緩存整合工具類則組合了2個(gè)接口,對(duì)外統(tǒng)一提供緩存相關(guān)操作方法。

  • 緩存管理接口和緩存函數(shù)接口是模塊的擴(kuò)展點(diǎn),基于xkernel 提供的SPI機(jī)制,結(jié)合SpringBoot注解 ConditionalOnBean,ConditionalOnProperty實(shí)現(xiàn)。

    總體類結(jié)構(gòu)圖如下所示:

CacheManager.png

包結(jié)構(gòu)如下:

xService
 └── src
    ├── main  
    │   ├── java  
    │   │   └── com.javacoo 
    │   │   ├────── xservice
    │   │   │         ├──────cache
    │   │   │         │         ├── CacheFunction 緩存函數(shù)接口
    │   │   │         │         ├── CacheManager 緩存管理接口
    │   │   │         │         ├── CacheFactory 緩存工廠
    │   │   │         │         ├── CacheHolder 緩存對(duì)象持有者
    │   │   │         │         ├── config
    │   │   │         │         │     └── CacheConfig 緩存配置
    │   │   │         │         └── internal 接口內(nèi)部實(shí)現(xiàn)
    │   │   │         │               ├── redis
    │   │   │         │               └──   ├── CombinatorialFunction 函數(shù)組合對(duì)象
    │   │   │         │                     ├── RedisCacheFunction 緩存函數(shù)接口實(shí)現(xiàn)類
    │   │   │         │                     └── RedisCacheManager 緩存管理接口實(shí)現(xiàn)類
    │   │   │         └──────utils
    │   │   │                   └── CacheUtil 緩存工具類
    │   └── resource  
    │       ├── META-INF
    │             └── ext
    │                  └── internal
    │                          ├── com.javacoo.xservice.base.support.cache.CacheFunction
    │                          └── com.javacoo.xservice.base.support.cache.CacheManager
    └── test  測(cè)試

二,核心邏輯概述

模塊核心是圍繞數(shù)據(jù)加載方案及熱點(diǎn)數(shù)據(jù)集中失效和緩存穿透問(wèn)題解決方案展開(kāi):數(shù)據(jù)加載方案主要是采取多重檢查機(jī)制,確保分布式,高并發(fā)條件下數(shù)據(jù)不多加載,不漏加載。熱點(diǎn)數(shù)據(jù)集中失效和緩存穿透問(wèn)題解決方案主要采用如下策略:

  • 熱點(diǎn)數(shù)據(jù)集中失效解決方案:redisson分布式鎖+隨機(jī)過(guò)期時(shí)間
  • 緩存穿透的解決方案:設(shè)置空數(shù)據(jù)特定值(根據(jù)業(yè)務(wù)場(chǎng)景特性:空數(shù)據(jù)的key數(shù)量有限、key重復(fù)請(qǐng)求概率較高),缺點(diǎn):需要存儲(chǔ)所有空數(shù)據(jù)的key,對(duì)于一些惡意攻擊,KEY不相同的情況,也起不了保護(hù)數(shù)據(jù)庫(kù)的作用。
  • 緩存穿透的解決備選方案:空數(shù)據(jù)的key各不相同、key重復(fù)請(qǐng)求概率低的場(chǎng)景而言,可使用BloomFilter。

加載數(shù)據(jù)并緩存流程圖:

加載數(shù)據(jù)并設(shè)置緩存.png

三,如何擴(kuò)展

基于xkernel 提供的SPI機(jī)制,擴(kuò)展非常方便,大致步驟如下:

  1. 實(shí)現(xiàn)緩存函數(shù)接口:如 com.xxxx.xxxx.MyCacheFunction
  2. 配置緩存函數(shù)接口:
    • 在項(xiàng)目resource目錄新建包->META-INF->services
    • 創(chuàng)建com.javacoo.xservice.base.support.cache.CacheFunction文件,文件內(nèi)容:實(shí)現(xiàn)類的全局限定名,如:
myCacheFunction=com.xxxx.xxxx.MyCacheFunction
  • 修改配置文件,添加如下內(nèi)容:
#緩存函數(shù)接口實(shí)現(xiàn)
app.config.cache.functionImpl = myCacheFunction

四,如何使用

由于過(guò)于簡(jiǎn)單,直接上代碼,如下所示:

    /**
     * 查詢緩存KEY:id
     */
    private static final String QUERY_CACHE_KEY = "query:%1$s";
    /**
     * 查詢緩存超時(shí)時(shí)間:1 分鐘
     */
    private static final int QUERY_CACHE_TIMEOUT = 60 * 1;
    /**
     * 緩存工具類
     */
    @Autowired
    protected CacheUtil cacheUtil;
    @Autowired
    private ExampleDao exampleDao;

    @Override
    public Optional<ExampleDto> getExampleInfo(String id) {
        AbstractAssert.isNotBlank(id, ErrorCodeConstants.SERVICE_GET_EXAMPLE_INFO_ID);
        // 從緩存中獲取數(shù)據(jù)
        String cacheKey = String.format(QUERY_CACHE_KEY,id);
        return Optional.ofNullable(cacheUtil.getCacheValueFunction(cacheKey,QUERY_CACHE_TIMEOUT,ExampleDto.class, getExampleInfoFunction,id));
    }

    /**
     * 獲取示例信息函數(shù)
     * <li></li>
     * @author duanyong@jccfc.com
     * @param id: id
     * @return: ExampleDto 示例信息
     */
    private Function<String, ExampleDto> getExampleInfoFunction = (String id)-> exampleDao.getExampleInfo(id);

五,代碼實(shí)現(xiàn)

  1. 緩存管理接口

    /**
     * 緩存管理接口
     * <li></li>
     *
     * @author: duanyong@jccfc.com
     */
    @Spi(CacheConfig.DEFAULT_IMPL)
    public interface CacheManager {
        /**
         * 設(shè)置單個(gè)值
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @param value: 值
         * @return: boolean true->成功
         */
        boolean set(String key, Object value);
    
        /**
         * 設(shè)置單個(gè)值并設(shè)置失效時(shí)間
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @param value: 值
         * @param expireTime:緩存時(shí)間,單位秒
         * @return: boolean true->成功
         */
        boolean setAndExpire(String key, Object value, int expireTime);
    
        /**
         * 獲取單個(gè)值
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @return: T 返回對(duì)象
         */
        <T> T get(String key);
    
        /**
         * 批量設(shè)置hash
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @param hash: Map對(duì)象
         * @return: boolean true->成功
         */
        boolean hmSet(String key, Map<String, Object> hash);
    
        /**
         * 給hash字段設(shè)置值
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @param field: 字段
         * @param value: 值
         * @return: boolean true->成功
         */
        boolean hSet(String key, String field, Object value);
    
        /**
         * 獲取hash值
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @param field: 字段
         * @return: T 返回對(duì)象
         */
        <T> T hGet(String key, String field);
    
        /**
         * 如果緩存key不存在則設(shè)置緩存并設(shè)置失效時(shí)間,否則不做操作
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @param value: 值
         * @param time: 緩存時(shí)間,單位秒
         * @return: boolean true->成功
         */
        boolean setIfAbsent(final String key, Object value, int time);
    
        /**
         * 刪除緩存
         * <li></li>
         * @author duanyong@jccfc.com
         * @date 2021/7/14 9:13
         * @param key: 鍵
         * @return: boolean true->成功
         */
        boolean del(String key);
    
        /**
         * 刪除hash緩存
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @param fields: 字段
         * @return: long 值
         */
        long hashDel(String key, String... fields);
    
        /**
         * 自增
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @param liveTime: 天數(shù) 這個(gè)計(jì)數(shù)器的有效存留時(shí)間
         * @param delta: 自增量
         * @return: java.lang.Long
         */
       Long incr(String key, long liveTime, long delta);
    }
    
  2. 緩存管理接口實(shí)現(xiàn)類:

    /**
     * 緩存管理接口實(shí)現(xiàn)類
     * <li>基于redis實(shí)現(xiàn)</li>
     *
     * @author: duanyong@jccfc.com
     */
    @Slf4j
    public class RedisCacheManager implements CacheManager {
        /**
         * RedisTemplate
         */
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        /**
         * 設(shè)置單個(gè)值
         * <li></li>
         *
         * @param key : 鍵
         * @param value : 值
         * @author duanyong@jccfc.com
         * @return: boolean true->成功
         */
        @Override
        public boolean set(String key, Object value) {
            Objects.requireNonNull(key, "緩存Key不能為空!");
            try {
                redisTemplate.opsForValue().set(key, value);
                return true;
            } catch (Exception e) {
                log.error("[Redis緩存]設(shè)置緩存 key:{},value:{},失敗:{}", key, value, e);
                return false;
            }
        }
    
        /**
         * 設(shè)置單個(gè)值并設(shè)置失效時(shí)間
         * <li></li>
         *
         * @param key : 鍵
         * @param value : 值
         * @param expireTime :緩存時(shí)間,單位秒
         * @author duanyong@jccfc.com
         * @return: boolean true->成功
         */
        @Override
        public boolean setAndExpire(String key, Object value, int expireTime) {
            Objects.requireNonNull(key, "緩存Key不能為空!");
            try {
                //設(shè)置默認(rèn)時(shí)間24H
                expireTime = expireTime <= 0 ? 1 : expireTime;
                redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
                return true;
            } catch (Exception e) {
                log.error("[Redis緩存]設(shè)置緩存 key:{},value:{},失敗:{}", key, value, e);
                return false;
            }
        }
    
        /**
         * 獲取單個(gè)值
         * <li></li>
         *
         * @param key : 鍵
         * @author duanyong@jccfc.com
         * @return: T 返回對(duì)象
         */
        @Override
        public <T> T get(String key) {
            Objects.requireNonNull(key, "緩存Key不能為空!");
            try {
                return (T) redisTemplate.opsForValue().get(key);
            } catch (Exception e) {
                log.error("[Redis緩存]獲取緩存 key:{},失敗:{}", key, e);
                return null;
            }
        }
    
        /**
         * 批量設(shè)置hash
         * <li></li>
         *
         * @param key : 鍵
         * @param hash : Map對(duì)象
         * @author duanyong@jccfc.com
         * @return: boolean true->成功
         */
        @Override
        public boolean hmSet(String key, Map<String, Object> hash) {
            Objects.requireNonNull(key, "緩存Key不能為空!");
            try {
                redisTemplate.opsForHash().putAll(key, hash);
                return true;
            } catch (Exception e) {
                log.error("[Redis緩存]設(shè)置緩存hash key:{},value:{},失敗:{}", key, hash, e);
                return false;
            }
        }
    
        /**
         * 給hash字段設(shè)置值
         * <li></li>
         *
         * @param key : 鍵
         * @param field : 字段
         * @param value : 值
         * @author duanyong@jccfc.com
         * @return: boolean true->成功
         */
        @Override
        public boolean hSet(String key, String field, Object value) {
            Objects.requireNonNull(key, "緩存Key不能為空!");
            Objects.requireNonNull(field, "緩存哈希字段不能為空!");
            try {
                redisTemplate.opsForHash().put(key, field, value);
                return true;
            } catch (Exception e) {
                log.error("[Redis緩存]設(shè)置hash值, key:{}, value:{} 失敗. {}", key, field, e);
                return false;
            }
        }
    
        /**
         * 獲取hash值
         * <li></li>
         *
         * @param key : 鍵
         * @param field : 字段
         * @author duanyong@jccfc.com
         * @return: T 返回對(duì)象
         */
        @Override
        public <T> T hGet(String key, String field) {
            Objects.requireNonNull(key, "緩存Key不能為空!");
            Objects.requireNonNull(field, "緩存哈希字段不能為空!");
            try {
                return (T) redisTemplate.opsForHash().get(key, field);
            } catch (Exception e) {
                log.error("[Redis緩存]獲取緩存hash key:{},field:{} 失敗,{}", key, field, e);
                return null;
            }
        }
    
        /**
         * 如果緩存key不存在則設(shè)置緩存并設(shè)置失效時(shí)間,否則不做操作
         * <li></li>
         *
         * @param key : 鍵
         * @param value : 值
         * @param time : 緩存時(shí)間,單位秒
         * @author duanyong@jccfc.com
         * @return: boolean true->成功
         */
        @Override
        public boolean setIfAbsent(String key, Object value, int time) {
            Objects.requireNonNull(key, "緩存Key不能為空!");
            try {
                time = time <= 0 ? 1 : time;
                return redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
            } catch (Exception e) {
                log.error("[Redis緩存]設(shè)置緩存 key:{},value:{}失敗,{}", key, value, e);
                return false;
            }
        }
    
        /**
         * 刪除緩存
         * <li></li>
         *
         * @param key : 鍵
         * @author duanyong@jccfc.com
         * @return: boolean true->成功
         */
        @Override
        public boolean del(String key) {
            Objects.requireNonNull(key, "緩存Key不能為空!");
            try {
                return redisTemplate.delete(key);
            } catch (Exception e) {
                log.error("[Redis緩存]刪除緩存 key:{} 失敗.{}", key, e);
                return false;
            }
        }
    
        /**
         * 刪除hash緩存
         * <li></li>
         *
         * @param key : 鍵
         * @param fields : 字段
         * @author duanyong@jccfc.com
         * @return: long 值
         */
        @Override
        public long hashDel(String key, String... fields) {
            Objects.requireNonNull(key, "緩存Key不能為空!");
            Objects.requireNonNull(fields, "緩存哈希字段不能為空!");
            try {
                return redisTemplate.opsForHash().delete(key, fields);
            } catch (Exception e) {
                log.error("[Redis緩存]刪除緩存 key:{},fields:{} 失敗,{}", key, fields, e);
            }
            return -1;
        }
    
        /**
         * 自增
         * <li></li>
         *
         * @param key : 鍵
         * @param liveTime : 天數(shù) 這個(gè)計(jì)數(shù)器的有效存留時(shí)間
         * @param delta : 自增量
         * @author duanyong@jccfc.com
         * @return: java.lang.Long
         */
        @Override
        public Long incr(String key, long liveTime, long delta) {
            RedisAtomicLong entityIdCounter = new RedisAtomicLong("INCSEQ_"+key, redisTemplate.getConnectionFactory());
            Long increment = entityIdCounter.addAndGet(delta);
    
            //初始設(shè)置過(guò)期時(shí)間
            if ((null == increment || increment.longValue() == 0) && liveTime > 0) {
                entityIdCounter.expire(liveTime, TimeUnit.DAYS);
            }
            return increment;
        }
    }
    
  3. 緩存函數(shù)接口:

    /**
     * 緩存函數(shù)接口
     * @author: duanyong@jccfc.com
     * @since: 2021/7/14 11:08
     */
    @Spi(CacheConfig.DEFAULT_IMPL)
    public interface CacheFunction {
        /**
         * 根據(jù)參數(shù)獲取緩存
         * <li>Function版</li>
         * @author duanyong@jccfc.com
         * @param key: 緩存KEY
         * @param expireTime: 超時(shí)時(shí)間,單位 秒
         * @param clazz: 目標(biāo)對(duì)象類型
         * @param function: 執(zhí)行函數(shù)
         * @param p: 附加給function的參數(shù)
         * @return: java.util.Optional<T> 對(duì)象數(shù)據(jù)
         */
        <T,P> Optional<T> getCacheValueFunction(String key,int expireTime, Class<T> clazz, Function<P,T> function,P p);
        /**
         * 獲取緩存
         * <li>Supplier版</li>
         * @author duanyong@jccfc.com
         * @param key:緩存KEY
         * @param expireTime:超時(shí)時(shí)間,單位 秒
         * @param clazz:目標(biāo)對(duì)象類型
         * @param function:執(zhí)行函數(shù)
         * @return: java.util.Optional<T> 對(duì)象數(shù)據(jù)
         */
        <T> Optional<T> getCacheValueFunction(String key,int expireTime, Class<T> clazz, Supplier<T> function);
        /**
         * 根據(jù)參數(shù)獲取集合緩存
         * <li>Function版</li>
         * @author duanyong@jccfc.com
         * @param key: 緩存KEY
         * @param expireTime: 超時(shí)時(shí)間,單位 秒
         * @param clazz: 目標(biāo)對(duì)象類型
         * @param function: 執(zhí)行函數(shù)
         * @param p: 附加給function的參數(shù)
         * @return: java.util.Optional<java.util.List<T>> 對(duì)象集合數(shù)據(jù)
         */
        <P,T> Optional<List<T>> getCacheListValueFunction(String key,int expireTime, Class<T> clazz, Function<P,List<T>> function,P p);
        /**
         * 獲取集合緩存
         * <li>Supplier版本</li>
         * @author duanyong@jccfc.com
         * @param key: 緩存KEY
         * @param expireTime: 超時(shí)時(shí)間,單位 秒
         * @param clazz: 目標(biāo)對(duì)象類型
         * @param function: 執(zhí)行函數(shù)
         * @return: java.util.Optional<java.util.List<T>> 對(duì)象集合數(shù)據(jù)
         */
        <T> Optional<List<T>> getCacheListValueFunction(String key,int expireTime, Class<T> clazz,Supplier<List<T>> function);
    }
    
  4. 緩存函數(shù)接口實(shí)現(xiàn)類:

    /**
     * 緩存函數(shù)接口實(shí)現(xiàn)類
     * <li></li>
     * <li>1,熱點(diǎn)數(shù)據(jù)集中失效解決方案:redisson分布式鎖+隨機(jī)過(guò)期時(shí)間</li>
     * <li>2,緩存穿透的解決方案:設(shè)置空數(shù)據(jù)特定值(根據(jù)業(yè)務(wù)場(chǎng)景特性:空數(shù)據(jù)的key數(shù)量有限、key重復(fù)請(qǐng)求概率較高),缺點(diǎn):需要存儲(chǔ)所有空數(shù)據(jù)的key,對(duì)于一些惡意攻擊,KEY不相同的情況,也起不了保護(hù)數(shù)據(jù)庫(kù)的作用</li>
     * <li>3,緩存穿透的解決備選方案:空數(shù)據(jù)的key各不相同、key重復(fù)請(qǐng)求概率低的場(chǎng)景而言,可使用BloomFilter</li>
     * @author: duanyong@jccfc.com
     */
    @Slf4j
    public class RedisCacheFunction implements CacheFunction {
    
        /**
         * 根據(jù)參數(shù)獲取緩存
         * <li>Function版</li>
         *
         * @param key : 緩存KEY
         * @param expireTime : 超時(shí)時(shí)間,單位 秒
         * @param clazz : 目標(biāo)對(duì)象類型
         * @param function : 執(zhí)行函數(shù)
         * @param p : 附加給function的參數(shù)
         * @author duanyong@jccfc.com
         * @return: java.util.Optional<T> 對(duì)象數(shù)據(jù)
         */
        @Override
        public <T, P> Optional<T> getCacheValueFunction(String key, int expireTime, Class<T> clazz, Function<P, T> function, P p) {
            return getCacheValue(key,expireTime,clazz,CombinatorialFunction.<T,P>builder().function(function).p(p).build());
        }
    
        /**
         * 獲取緩存
         * <li>Supplier版</li>
         *
         * @param key :緩存KEY
         * @param expireTime :超時(shí)時(shí)間,單位 秒
         * @param clazz :目標(biāo)對(duì)象類型
         * @param function :執(zhí)行函數(shù)
         * @author duanyong@jccfc.com
         * @return: java.util.Optional<T> 對(duì)象數(shù)據(jù)
         */
        @Override
        public <T> Optional<T> getCacheValueFunction(String key, int expireTime, Class<T> clazz, Supplier<T> function) {
            return getCacheValue(key,expireTime,clazz,CombinatorialFunction.<T,Object>builder().supplier(function).build());
        }
    
        /**
         * 根據(jù)參數(shù)獲取集合緩存
         * <li>Function版</li>
         *
         * @param key : 緩存KEY
         * @param expireTime : 超時(shí)時(shí)間,單位 秒
         * @param clazz : 目標(biāo)對(duì)象類型
         * @param function : 執(zhí)行函數(shù)
         * @param p : 附加給function的參數(shù)
         * @author duanyong@jccfc.com
         * @return: java.util.Optional<java.util.List<T>> 對(duì)象集合數(shù)據(jù)
         */
        @Override
        public <P, T> Optional<List<T>> getCacheListValueFunction(String key, int expireTime, Class<T> clazz,
            Function<P, List<T>> function, P p) {
            return getCacheListValue(key,expireTime,clazz,CombinatorialFunction.<T,P>builder().listFunction(function).p(p).build());
        }
    
        /**
         * 獲取集合緩存
         * <li>Supplier版本</li>
         *
         * @param key : 緩存KEY
         * @param expireTime : 超時(shí)時(shí)間,單位 秒
         * @param clazz : 目標(biāo)對(duì)象類型
         * @param function : 執(zhí)行函數(shù)
         * @author duanyong@jccfc.com
         * @return: java.util.Optional<java.util.List<T>> 對(duì)象集合數(shù)據(jù)
         */
        @Override
        public <T> Optional<List<T>> getCacheListValueFunction(String key, int expireTime, Class<T> clazz,
            Supplier<List<T>> function) {
            return getCacheListValue(key,expireTime,clazz,CombinatorialFunction.<T,Object>builder().listSupplier(function).build());
        }
        /**
         * 獲取集合緩存
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 緩存KEY
         * @param expireTime: 超時(shí)時(shí)間,單位 秒
         * @param clazz: 目標(biāo)對(duì)象類型
         * @param combinatorialFunction:函數(shù)組合對(duì)象
         * @return: java.util.Optional<java.util.List<T>> 對(duì)象集合數(shù)據(jù)
         */
        public <P, T> Optional<List<T>> getCacheListValue(String key, int expireTime, Class<T> clazz,CombinatorialFunction<T,P> combinatorialFunction) {
            //獲取緩存
            List<T> records = getList(key, clazz);
            if (records != null && !records.isEmpty()) {
                return Optional.of(records);
            }
            //檢查是否是特定值-empty
            Object o = get(key);
            if(Constants.CACHE_EMPTY_VALUE.equals(o)){
                return Optional.empty();
            }
            //獲取鎖失敗
            if (!tryLock(key)) {
                log.error("獲取鎖失敗:key->{},直接返回.",key);
                return Optional.empty();
            }
            //獲取鎖成功
            try {
                //再檢查一次:當(dāng)其他等待線程獲取到鎖時(shí),緩存一般已經(jīng)有值,所以需要再次確認(rèn),以免重復(fù)查庫(kù)
                records = getList(key, clazz);
                if (records != null && !records.isEmpty()) {
                    return Optional.of(records);
                }
                //執(zhí)行目標(biāo)函數(shù)
                if(combinatorialFunction.getListSupplier() != null){
                    records = combinatorialFunction.getListSupplier().get();
                }else if(combinatorialFunction.getListFunction() != null && combinatorialFunction.getP() != null){
                    records = combinatorialFunction.getListFunction().apply(combinatorialFunction.getP());
                }
                if (records != null && !records.isEmpty()) {
                    //設(shè)置緩存
                    setObject(key, records,expireTime);
                    //再次獲取緩存,確保緩存成功
                    records = getList(key, clazz);
                    if (records != null && !records.isEmpty()) {
                        log.info("線程{},執(zhí)行目標(biāo)函數(shù)獲取數(shù)據(jù)并緩存,key={}",Thread.currentThread().getName(), key);
                        return Optional.of(records);
                    }
                }
                //緩存空字符串?dāng)?shù)據(jù),防止重復(fù)請(qǐng)求
                setAndExpire(key, Constants.CACHE_EMPTY_VALUE,expireTime);
                log.info("線程{},數(shù)據(jù)不存在,緩存空字符串?dāng)?shù)據(jù),防止重復(fù)請(qǐng)求,key={}",Thread.currentThread().getName(), key);
            } catch(Exception e){
                log.error("獲取集合緩存失敗:key->{}.",key,e);
                throw e;
            }finally {
                unlock(key);
            }
            return Optional.empty();
        }
        /**
         * 獲取單值緩存
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 緩存KEY
         * @param expireTime: 超時(shí)時(shí)間,單位 秒
         * @param clazz: 目標(biāo)對(duì)象類型
         * @param combinatorialFunction: 函數(shù)組合對(duì)象
         * @return: java.util.Optional<T> 對(duì)象數(shù)據(jù)
         */
        public <T, P> Optional<T> getCacheValue(String key, int expireTime, Class<T> clazz, CombinatorialFunction<T,P> combinatorialFunction) {
            //獲取緩存
            T record = getObject(key, clazz);
            if (record != null) {
                return Optional.of(record);
            }
            //檢查是否是特定值-empty
            Object o = get(key);
            if(Constants.CACHE_EMPTY_VALUE.equals(o)){
                return Optional.empty();
            }
            //獲取鎖失敗
            if (!tryLock(key)) {
                log.error("獲取鎖失敗:key->{},直接返回.",key);
                return Optional.empty();
            }
            try {
                //再檢查一次:當(dāng)其他等待線程獲取到鎖時(shí),緩存一般已經(jīng)有值,所以需要再次確認(rèn),以免重復(fù)查庫(kù)
                record = getObject(key, clazz);
                if(record != null){
                    return Optional.of(record);
                }
                //執(zhí)行目標(biāo)函數(shù)
                if(combinatorialFunction.getSupplier() != null){
                    record = combinatorialFunction.getSupplier().get();
                }else if(combinatorialFunction.getFunction() != null && combinatorialFunction.getP() != null){
                    record = combinatorialFunction.getFunction().apply(combinatorialFunction.getP());
                }
                if(record != null){
                    //設(shè)置緩存
                    setObject(key, record,expireTime);
                    //再次獲取緩存,確保緩存成功
                    record = getObject(key, clazz);
                    if(record != null){
                        log.info("線程{},執(zhí)行目標(biāo)函數(shù)獲取數(shù)據(jù)并緩存,key={}",Thread.currentThread().getName(), key);
                        return Optional.of(record);
                    }
                }
                //緩存空字符串?dāng)?shù)據(jù),防止重復(fù)請(qǐng)求
                setAndExpire(key, Constants.CACHE_EMPTY_VALUE,expireTime);
                log.info("線程{},數(shù)據(jù)不存在,緩存空字符串?dāng)?shù)據(jù),防止重復(fù)請(qǐng)求,key={}",Thread.currentThread().getName(), key);
            } catch(Exception e){
                log.error("根據(jù)參數(shù)獲取緩存失敗:key->{}.",key,e);
                throw e;
            } finally {
                unlock(key);
            }
            return Optional.empty();
        }
        /**
         * 加鎖
         * <li></li>
         * @author duanyong@jccfc.com
         * @param cacheKey: 緩存key
         * @return: boolean 是否成功
         */
        private boolean tryLock(String cacheKey){
            int timeout = Lock.TIMEOUT_SECOND;
            if(!LockHolder.getLock().isPresent()){
                log.error("不支持加鎖");
                return false;
            }
            boolean isLocked = LockHolder.getLock().get().tryLock(cacheKey, TimeUnit.SECONDS,0,timeout);
            if(isLocked){
                SwapAreaUtils.getSwapAreaData().setCacheKey(cacheKey);
                log.info("加鎖成功,KEY:{},自動(dòng)失效時(shí)間:{}秒",cacheKey,timeout);
            }
            return isLocked;
        }
        /**
         * 解鎖
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 緩存key
         * @return: void
         */
        private void unlock(String key){
            if(!LockHolder.getLock().isPresent()){
                return;
            }
            LockHolder.getLock().get().unlock(key);
        }
        /**
         * 獲取對(duì)象集合
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key:鍵
         * @param clazz:對(duì)象類型
         * @return: java.util.List<T>對(duì)象集合
         */
        private final <T> List<T> getList(final String key,final Class<T> clazz) {
            try{
                return FastJsonUtil.toList(get(key), clazz);
            }catch(Exception ex){
                log.error("獲取對(duì)象集合->JSON轉(zhuǎn)集合對(duì)象失敗",ex);
                //如果不是空值,說(shuō)明數(shù)據(jù)異常,需要?jiǎng)h除此數(shù)據(jù)
                if(!Constants.CACHE_EMPTY_VALUE.equals(get(key))){
                    del(key);
                }
            }
            return Collections.emptyList();
        }
        /**
         * 獲取對(duì)象
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @param clazz: 對(duì)象類型
         * @return: T 目標(biāo)對(duì)象
         */
        private final <T> T getObject(final String key,final Class<T> clazz) {
            try{
                return FastJsonUtil.toBean(get(key), clazz);
            }catch(Exception ex){
                //如果不是空值
                if(!Constants.CACHE_EMPTY_VALUE.equals(get(key))){
                    del(key);
                }
            }
            return null;
        }
        /**
         * 刪除緩存
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @return: boolean true->成功
         */
        private boolean del(String key) {
            if(getCacheManager() == null){
                return false;
            }
            return getCacheManager().del(key);
        }
        /**
         * 設(shè)置對(duì)象
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @param value: 對(duì)象
         * @param expireTime 超時(shí)時(shí)間,單位 秒
         */
        private void setObject(final String key, final Object value,final int expireTime) {
            setAndExpire(key, FastJsonUtil.toJSONString(value),expireTime);
        }
        /**
         * 設(shè)置單個(gè)值并設(shè)置失效時(shí)間
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @param value: 值
         * @param expireTime:緩存時(shí)間,單位秒
         * @return: boolean true->成功
         */
        private boolean setAndExpire(String key, Object value, int expireTime) {
            if(getCacheManager() == null){
                return false;
            }
            return getCacheManager().setAndExpire(key,value,expireTime);
        }
    
        /**
         * 獲取單個(gè)值
         * <li></li>
         * @author duanyong@jccfc.com
         * @param key: 鍵
         * @return: T 返回對(duì)象
         */
        private <T> T get(String key) {
            if(getCacheManager() == null){
                return null;
            }
            return getCacheManager().get(key);
        }
        /**
         * 獲取緩存管理器
         * <li></li>
         * @author duanyong@jccfc.com
         * @return: com.javacoo.xservice.base.support.cache.CacheManager
         */
        private CacheManager getCacheManager(){
            if(!CacheHolder.getCacheManager().isPresent()){
                log.error("不支持緩存");
                return null;
            }
            return CacheHolder.getCacheManager().get();
        }
    }
    
  5. 函數(shù)組合對(duì)象:

    /**
     * 函數(shù)組合對(duì)象
     * <li></li>
     *
     * @author: duanyong@jccfc.com
     */
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class CombinatorialFunction<T,P> {
        /**
         * 單值,提供者函數(shù)
         */
        private Supplier<T> supplier;
        /**
         * 單值,帶參數(shù)函數(shù)
         */
        private Function<P, T> function;
        /**
         * 集合,提供者函數(shù)
         */
        private Supplier<List<T>> listSupplier;
        /**
         * 集合,帶參數(shù)函數(shù)
         */
        private Function<P, List<T>> listFunction;
        /**
         * 參數(shù)
         */
        private P p;
    }
    
  6. 緩存管理對(duì)象持有者:

    /**
     * 緩存管理對(duì)象持有者
     * <li></li>
     * @author duanyong@jccfc.com
     */
    public class CacheHolder {
        /** 緩存管理接口對(duì)象*/
        static CacheManager cacheManager;
        /** 緩存函數(shù)接口對(duì)象*/
        static CacheFunction cacheFunction;
    
        public static Optional<CacheManager> getCacheManager() {
            return Optional.ofNullable(cacheManager);
        }
        public static Optional<CacheFunction> getCacheFunction() {
            return Optional.ofNullable(cacheFunction);
        }
    }
    
  7. 緩存管理接口工廠:

    /**
     * 緩存管理接口工廠
     * <li></li>
     * @author duanyong@jccfc.com
     */
    @Slf4j
    @Component
    @ConditionalOnBean(CacheConfig.class)
    public class CacheFactory {
        /** 緩存配置 */
        @Autowired
        private CacheConfig cacheConfig;
    
        @Bean
        public CacheManager createCacheManager() {
            log.info("初始化緩存管理,實(shí)現(xiàn)類名稱:{}",cacheConfig.getImpl());
            CacheHolder.cacheManager = ExtensionLoader.getExtensionLoader(CacheManager.class).getExtension(cacheConfig.getImpl());
            log.info("初始化緩存管理,緩存管理接口實(shí)現(xiàn)類:{}", CacheHolder.cacheManager);
            return CacheHolder.cacheManager;
        }
        @Bean
        public CacheFunction createCacheFunction() {
            log.info("初始化緩存函數(shù)接口,實(shí)現(xiàn)類名稱:{}",cacheConfig.getFunctionImpl());
            CacheHolder.cacheFunction = ExtensionLoader.getExtensionLoader(CacheFunction.class).getExtension(cacheConfig.getFunctionImpl());
            log.info("初始化緩存函數(shù)接口實(shí)現(xiàn)類:{}", CacheHolder.cacheFunction);
            return CacheHolder.cacheFunction;
        }
    }
    
  8. 工具類實(shí)現(xiàn):

    /**
     * 緩存工具類
     * <li>提供緩存基本操作</li>
     * <li>獲取單個(gè)對(duì)象緩存:Function版</li>
     * <li>獲取單個(gè)對(duì)象緩存:Supplier版</li>
     * <li>獲取集合類型緩存:Function版</li>
     * <li>獲取集合類型緩存:Supplier版</li>
     * @author duanyong@jccfc.com
     */
    @Component
    public class CacheUtil {
        /**
         * 設(shè)置單個(gè)值
         * @param key
         * @param value
         * @return
         */
        public boolean set(String key, Object value) {
            return getCacheManager() == null ? false : getCacheManager().set(key,value);
        }
    
        /**
         * 設(shè)置單個(gè)值并設(shè)置失效時(shí)間
         * @param key
         * @param value
         * @param expireTime 緩存時(shí)間,單位秒
         * @return
         */
        public boolean setAndExpire(String key, Object value, int expireTime) {
            return getCacheManager() == null ? false : getCacheManager().setAndExpire(key,value,expireTime);
        }
    
        /**
         * 獲取單個(gè)值
         * @param key
         * @return
         */
        public <T> T get(String key) {
            return getCacheManager() == null ? null : getCacheManager().get(key);
        }
    
        /**
         * 批量設(shè)置hash
         * @param key
         * @param hash
         * @return
         */
        public boolean hmSet(String key, Map<String, Object> hash) {
            return getCacheManager() == null ? false : getCacheManager().hmSet(key,hash);
        }
    
        /**
         * 給hash字段設(shè)置值
         * @param key
         * @param field
         * @param value
         * @return
         */
        public boolean hSet(String key, String field, Object value) {
            return getCacheManager() == null ? false : getCacheManager().hSet(key,field,value);
        }
    
        /**
         * 獲取hash值
         *  引用見(jiàn){@link RedisTemplate}
         * @param key
         * @param field
         * @return
         */
        public <T> T hGet(String key, String field) {
            return getCacheManager() == null ? null : getCacheManager().hGet(key,field);
        }
    
        /**
         * 如果緩存key不存在則設(shè)置緩存并設(shè)置失效時(shí)間,否則不做操作
         * @param key
         * @param value
         * @param time 緩存時(shí)間,單位秒
         * @return
         */
        public boolean setIfAbsent(final String key, Object value, int time) {
            return getCacheManager() == null ? false : getCacheManager().setIfAbsent(key,value,time);
        }
    
        /**
         * 刪除緩存
         * @param key
         * @return
         */
        public boolean del(String key) {
            return getCacheManager() == null ? false : getCacheManager().del(key);
        }
    
        /**
         * 刪除hash緩存
         * @param key
         * @param fields
         * @return
         */
        public long hashDel(String key, String... fields) {
            return getCacheManager() == null ? -1 : getCacheManager().hashDel(key,fields);
        }
    
        /**
         * redis 自增
         * @param key
         * @param liveTime 天數(shù) 這個(gè)計(jì)數(shù)器的有效存留時(shí)間
         * @param delta 自增量
         * @return
         */
        public long incr(String key, long liveTime, long delta) {
            return getCacheManager() == null ? -1 : getCacheManager().incr(key,liveTime,delta);
        }
    
        /**
         * 獲取緩存
         * <p>
         * 說(shuō)明:Function版
         * </p>
         * @author DuanYong
         * @param key 緩存KEY
         * @param expireTime 超時(shí)時(shí)間,單位 秒
         * @param clazz 目標(biāo)對(duì)象類型
         * @param function 執(zhí)行函數(shù)
         * @param p 附加給function的參數(shù)
         * @return 目標(biāo)對(duì)象
         */
        public <T,P> T getCacheValueFunction(String key,int expireTime, Class<T> clazz, Function<P,T> function,P p) {
            return getCacheFunction() == null ? null : getCacheFunction().getCacheValueFunction(key,expireTime,clazz,function,p).orElse(null);
        }
        /**
         * 獲取緩存
         * <p>
         * 說(shuō)明:Supplier版
         * </p>
         * @author DuanYong
         * @param key 緩存KEY
         * @param clazz 目標(biāo)對(duì)象類型
         * @param function 執(zhí)行函數(shù)
         * @return 目標(biāo)對(duì)象
         */
        public <T> T getCacheValueFunction(String key,int expireTime, Class<T> clazz, Supplier<T> function) {
            return getCacheFunction() == null ? null : getCacheFunction().getCacheValueFunction(key,expireTime,clazz,function).orElse(null);
        }
        /**
         * 獲取集合緩存
         * <p>說(shuō)明:Function版本</p>
         * @author DuanYong
         * @param key 緩存KEY
         * @param clazz 目標(biāo)對(duì)象類型
         * @param function 執(zhí)行函數(shù)
         * @param p 附加給function的參數(shù)
         * @return 目標(biāo)對(duì)象
         */
        public <P,T> List<T> getCacheListValueFunction(String key,int expireTime, Class<T> clazz, Function<P,List<T>> function,P p) {
            return getCacheFunction() == null ? Collections.emptyList() : getCacheFunction().getCacheListValueFunction(key,expireTime,clazz,function,p).orElse(Collections.emptyList());
        }
        /**
         * 獲取集合緩存
         * <p>說(shuō)明:Supplier版本</p>
         * @author DuanYong
         * @param key 緩存KEY
         * @param clazz 目標(biāo)對(duì)象類型
         * @param function 執(zhí)行函數(shù)
         * @return 目標(biāo)對(duì)象
         */
        public <T> List<T> getCacheListValueFunction(String key,int expireTime, Class<T> clazz,Supplier<List<T>> function) {
            return getCacheFunction() == null ? Collections.emptyList() : getCacheFunction().getCacheListValueFunction(key,expireTime,clazz,function).orElse(Collections.emptyList());
        }
        /**
         * 獲取緩存管理器
         * <li></li>
         * @author duanyong@jccfc.com
         * @return: com.javacoo.xservice.base.support.cache.CacheManager
         */
        private CacheManager getCacheManager(){
            if(!CacheHolder.getCacheManager().isPresent()){
                log.error("不支持緩存");
                return null;
            }
            return CacheHolder.getCacheManager().get();
        }
        /**
         * 獲取緩存函數(shù)接口
         * <li></li>
         * @author duanyong@jccfc.com
         * @return: com.javacoo.xservice.base.support.cache.CacheFunction
         */
        private CacheFunction getCacheFunction(){
            if(!CacheHolder.getCacheManager().isPresent()){
                log.error("不支持緩存");
                return null;
            }
            if(!CacheHolder.getCacheFunction().isPresent()){
                log.error("不支持緩存函數(shù)");
                return null;
            }
            return CacheHolder.getCacheFunction().get();
        }
    

應(yīng)用場(chǎng)景

  • 適用于大多數(shù)并發(fā)較少,數(shù)據(jù)一致性要求不高的場(chǎng)景。

結(jié)果驗(yàn)證及局限性

  • 統(tǒng)一了緩存使用模式,簡(jiǎn)化了開(kāi)發(fā)。
  • 此方案在并發(fā)較少,數(shù)據(jù)一致性要求不高的場(chǎng)景效果較好。
  • 高并發(fā),數(shù)據(jù)一致性要求高的場(chǎng)景其緩存設(shè)計(jì)方案可參考: OpenResty+Lua+Redis+Canal實(shí)現(xiàn)多級(jí)緩存架構(gòu)

后續(xù)規(guī)劃

  • 完善緩存相關(guān)問(wèn)題解決方案。
一些信息
路漫漫其修遠(yuǎn)兮,吾將上下而求索
碼云:https://gitee.com/javacoo
QQ群:164863067
作者/微信:javacoo
郵箱:xihuady@126.com
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容