天天看點

基于資料庫實作通用異步緩存系統

編寫目的

在某些特殊的項目中,想實作緩存,但是不能使用中間件,存記憶體又會導緻記憶體大幅度上升,怎麼辦呢?

降低預期,将需要緩存的資料存儲在資料庫,如何設計一套資料庫緩存呢。

設計思路

一個KV形式緩存中間件需要有哪些基礎功能?

  • 1、增加緩存(新增資料庫)
  • 2、緩存覆寫(修改資料庫)
  • 3、緩存過期删除(删除資料庫資料)
  • 4、查詢緩存(查詢資料庫)

其實,就是對資料庫的增删改查。但是緩存的資料一般情況是寫入和查詢比較頻繁的。

  • 查詢優化: 在字段KEY上建立唯一索引
  • 插入優化:使用隊列 + 定時任務異步入庫
  • 緩存覆寫:使用隊列 + 定時任務異步更新
  • 過期删除:使用隊列 + 定時任務異步删除

表設計

主鍵,緩存KEY(唯一索引), 緩存值 , 過期時間

create table data_common_cache
(
    cache_id        bigint auto_increment comment '主鍵,生成序列号Id' primary key,
    cache_key       varchar(100)   not null comment '緩存的key 長度100 超過100的話通過編碼後縮短',
    cache_value     text  default null comment '緩存的值',
    cache_expire    datetime default null comment '過期時間',
    constraint udx_cache_key unique (cache_key)
) comment '通用緩存表';      

功能實作

使用SpringBoot + Mybatis Plus 實作通用緩存功能

依賴引入

<properties>
    <java.version>1.8</java.version>
    <mybatis-plus.version>3.4.0</mybatis-plus.version>
    <mybatis-plus-generator.version>3.3.2</mybatis-plus-generator.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!--mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>${mybatis-plus.version}</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>${mybatis-plus-generator.version}</version>
    </dependency>
    <!--mybatis-plus模闆生成-->
    <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity-engine-core</artifactId>
        <version>2.2</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.35</version>
    </dependency>

    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>30.0-jre</version>
    </dependency>
</dependencies>      

配置檔案

server:
  port: 8081

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    jdbc-url: jdbc:mysql://localhost:3306/test_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
    username: root
    password: root      

生成entity, mapper

實體類

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class DataCommonCache implements Serializable {

    private static final long serialVersionUID=1L;

    /**
     * 主鍵,生成序列号Id
     */
    @TableId(value = "cache_id", type = IdType.AUTO)
    private Long cacheId;

    /**
     * 緩存的key 長度100 超過100的話通過編碼後縮短
     */
    private String cacheKey;

    /**
     * 緩存的值
     */
    private String cacheValue;

    /**
     * 過期時間
     */
    private Date cacheExpire;
}      

mapper, 包含自定義SQL

@Mapper
public interface DataCommonCacheMapper extends BaseMapper<DataCommonCache> {
    /**
     * 根據緩存key查詢緩存ID 如果ID為空 表示緩存不存在 不為空表示緩存存在
     * @param cacheKey 緩存key
     * @return 緩存的ID, 過期時間 為空表示緩存不存在
     */
    DataCommonCache selectIdByKey(@Param("cacheKey") String cacheKey);

    /**
     * 根據緩存key查詢緩存ID 如果ID為空 表示緩存不存在 不為空表示緩存存在
     * @param cacheKey 緩存key
     * @return 緩存的ID, 過期時間,緩存值 為空表示緩存不存在
     */
    DataCommonCache selectByKey(@Param("cacheKey") String cacheKey);

    /**
     * 根據緩存key查詢緩存ID 如果ID為空 表示緩存不存在 不為空表示緩存存在
     * @param cacheKey 緩存key
     * @return 緩存的ID 為空表示
     */
    String selectValueByKey(@Param("cacheKey") String cacheKey);

    /**
     * 根據緩存key删除資料
     * @param cacheKey 緩存key
     */
    int deleteByCacheKey(@Param("cacheKey") String cacheKey);
}      

mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itdl.mapper.DataCommonCacheMapper">
    <delete id="deleteByCacheKey" parameterType="java.lang.String">
        delete from data_common_cache where cache_key = #{cacheKey}
    </delete>


    <!--根據緩存key擷取緩存資訊,主要包含緩存ID和過期時間 結果為空表示沒有資料-->
    <select id="selectIdByKey" parameterType="string" resultType="com.itdl.entity.DataCommonCache">
        select cache_id, cache_expire from data_common_cache where cache_key = #{cacheKey} limit 1
    </select>

    <!--包含緩存值-->
    <select id="selectByKey" parameterType="string" resultType="com.itdl.entity.DataCommonCache">
        select cache_id, cache_value, cache_expire from data_common_cache where cache_key = #{cacheKey} limit 1
    </select>

    <!--根據緩存的key擷取緩存的值-->
    <select id="selectValueByKey" parameterType="string" resultType="java.lang.String">
        select cache_value from data_common_cache where cache_key = #{cacheKey} limit 1
    </select>
</mapper>      

整合MybatisPlus

整合MybatisPlus用于增删改差, 并實作了MybtaisPlus真正的批量新增和批量修改

資料源配置類DatasourceConfig

包含了資料源的配置和SqlSessionFactory配置,且注入了MybatisPlus的配置

/**
 * @Description 資料庫相關配置
 * @Author itdl
 * @Date 2022/08/10 09:20
 */
@Configuration
@MapperScan(basePackages = "com.itdl.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class DatasourceConfig {


    @Bean(name = "dataSource")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() throws SQLException {
        return DataSourceBuilder.create().build();
    }


    @Bean("easySqlInjector")
    public EasySqlInjector easySqlInjector() {
        return new EasySqlInjector();
    }


    @Bean
    public GlobalConfig globalConfig(EasySqlInjector easySqlInjector){
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setSqlInjector(easySqlInjector);
        return globalConfig;
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource, GlobalConfig globalConfig) throws Exception {

        MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().
                getResources("classpath*:mapper/**/*.xml"));
        sessionFactoryBean.setPlugins(new PaginationInterceptor());

        //添加自定義sql注入接口
        sessionFactoryBean.setGlobalConfig(globalConfig);//添加自定義sql注入接口
        return sessionFactoryBean.getObject();
    }

}      
Mybatis 批量插入/更新配置
public class EasySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        methodList.add(new InsertBatchSomeColumn());
        methodList.add(new UpdateBatchMethod());
        return methodList;
    }

}

/**
 * 批量更新方法實作,條件為主鍵,選擇性更新
 */
@Slf4j
public class UpdateBatchMethod extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql = "<script>\n<foreach collection=\"list\" item=\"item\" separator=\";\">\nupdate %s %s where %s=#{%s} %s\n</foreach>\n</script>";
        String additional = tableInfo.isWithVersion() ? tableInfo.getVersionFieldInfo().getVersionOli("item", "item.") : "" + tableInfo.getLogicDeleteSql(true, true);
        String setSql = sqlSet(false, false, tableInfo, false, "item", "item.");
        String sqlResult = String.format(sql, tableInfo.getTableName(), setSql, tableInfo.getKeyColumn(), "item." + tableInfo.getKeyProperty(), additional);
        //log.debug("sqlResult----->{}", sqlResult);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass);
        // 第三個參數必須和RootMapper的自定義方法名一緻
        return this.addUpdateMappedStatement(mapperClass, modelClass, "updateBatch", sqlSource);
    }
}      

緩存實作

實作思路
  • 1、配置一個可排程的線程池,用于異步隊列的排程
  • 2、編寫一個基礎排程父類,實作排程的基本邏輯
  • 3、編寫緩存插入,覆寫,删除的排程邏輯
  • 4、将排程邏輯整合為一個緩存工具類
  • 5、使用Controller接口測試緩存增删改查
配置可排程的線程池
/**
 * @Description 通用配置及
 * @Author itdl
 * @Date 2022/08/09 17:57
 */
@Configuration
public class CommonConfig {
    @Bean("scheduledThreadPoolExecutor")
    public ScheduledThreadPoolExecutor scheduledThreadPoolExecutor() {
        //線程名
        String threadNameStr = "統一可排程線程-%d";
        //線程工廠類就是将一個線程的執行單元包裝成為一個線程對象,比如線程的名稱,線程的優先級,線程是否是守護線程等線程;
        // guava為了我們友善的建立出一個ThreadFactory對象,我們可以使用ThreadFactoryBuilder對象自行建立一個線程.
        ThreadFactory threadNameVal = new ThreadFactoryBuilder().setNameFormat(threadNameStr).build();
        // 單線程池
        return new ScheduledThreadPoolExecutor(
                // 核心線程池
                4,
                // 最大線程池
                threadNameVal,
                // 使用政策為抛出異常
                new ThreadPoolExecutor.AbortPolicy());
    }
}      
編寫可排程的公共父類,實作排程的基本邏輯
@Slf4j
public abstract class BaseCacheHelper<T> {
    @Resource
    private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;
    // 隊列
    private final BlockingQueue<T> QUEUE = new ArrayBlockingQueue<>(1024);
    // listener執行次數 計數器
    private final AtomicInteger EXECUTE_COUNT = new AtomicInteger();
    // 事件集合
    private final List<T> eventStorageList = Collections.synchronizedList(new ArrayList<>());

    /**
     * 判斷隊列是否為空
     */
    public boolean checkQueueIsEmpty() {
        return QUEUE.isEmpty();
    }

    /**
     * 入隊方法
     * @param datas 批量入隊
     */
    public void producer(List<T> datas) {
        for (T data : datas) {
            producer(data);
        }
    }

    /**
     * 入隊方法
     * @param data 單個入隊
     */
    public void producer(T data) {
        try {
            if (QUEUE.contains(data)){
                return;
            }
            // 入隊 滿了則等待
            QUEUE.put(data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("================>>>通用隊列:{}:目前隊列存在資料:{}", this.getClass().getName(), QUEUE.size());
    }


    @PostConstruct
    public void consumer() {
        scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
            try {
                // 隊列數量達到指定消費批次數量
                if (EXECUTE_COUNT.get() >= getBatchSize()) {
                    doConsumer();
                } else {
                    while (EXECUTE_COUNT.get() < getBatchSize() && QUEUE.size() != 0) {
                        // 加入事件
                        final T take = QUEUE.take();
                        eventStorageList.add(take);
                        EXECUTE_COUNT.incrementAndGet();
                    }

                    // 隊列為空了  同樣需要處理,及時沒有滿
                    if (EXECUTE_COUNT.get() < getBatchSize() && QUEUE.size() == 0) {
                        doConsumer();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 3000, getPeriodTime(), TimeUnit.MILLISECONDS);
    }

    /**
     * 消費資料
     */
    protected void doConsumer() {
        // 這裡面開始真正的寫磁盤
        if (ObjectUtils.isEmpty(eventStorageList)) {
            return;
        }
        // 批處理
        final StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("=========>>>>消費資料{}條", eventStorageList.size());
        for (T t : eventStorageList) {
            StopWatch subStopWatch = new StopWatch();
            subStopWatch.start();
            // 處理每一個消費者的邏輯 用于子類實作
            doHandleOne(t);
            subStopWatch.stop();
        }
        // 重置資料
        EXECUTE_COUNT.set(0);
        eventStorageList.clear();
        stopWatch.stop();
        log.info("=========>>>>通用隊列:{}:消費完成,總耗時:{}s<<<<=========", this.getClass().getName(), String.format("%.4f", stopWatch.getTotalTimeSeconds()));
    }

    /**
     * 消費一條資料
     *
     * @param data 處理資料
     */
    protected abstract void doHandleOne(T data);


    /**
     * 批次大小 預設每次消費100條
     *
     * @return 此次大小
     */
    protected Integer getBatchSize() {
        return 100;
    }

    /**
     * 批次大小 執行完任務後 間隔多久再執行 機關 毫秒 預設5秒
     *
     * @return 此次大小
     */
    protected Integer getPeriodTime() {
        return 1000;
    }
}      

增删改排程任務實作

@Slf4j
@Component
public class DbCacheHelper{
    @Slf4j
    @Component
    public static class InsertCache extends BaseCacheHelper<DataCommonCache>{
        @Autowired
        private DataCommonCacheMapper dataCommonCacheMapper;

        @Override
        protected void doHandleOne(DataCommonCache data) {
            final String cacheKey = data.getCacheKey();
            log.info("=====================開始插入緩存資料cacheKey:{}===========================", cacheKey);
            try {
                dataCommonCacheMapper.insert(data);
            } catch (Exception e) {
                log.error("=======>>>>插入緩存資料失敗:{}", e.getMessage());
                e.printStackTrace();
            }
            log.info("=====================完成插入緩存資料cacheKey:{}===========================", cacheKey);
        }
    }


    @Slf4j
    @Component
    public static class UpdateCache extends BaseCacheHelper<DataCommonCache>{
        @Autowired
        private DataCommonCacheMapper dataCommonCacheMapper;
        @Override
        protected void doHandleOne(DataCommonCache data) {
            final String cacheKey = data.getCacheKey();
            log.info("=====================開始覆寫寫入緩存資料cacheKey:{}===========================", cacheKey);
            try {
                dataCommonCacheMapper.updateById(data);
            } catch (Exception e) {
                log.error("=======>>>>覆寫寫入緩存資料失敗:{}", e.getMessage());
                e.printStackTrace();
            }
            log.info("=====================完成覆寫寫入緩存資料cacheKey:{}===========================", cacheKey);
        }
    }


    @Slf4j
    @Component
    public static class DeleteCache extends BaseCacheHelper<String>{
        @Autowired
        private DataCommonCacheMapper dataCommonCacheMapper;
        @Override
        protected void doHandleOne(String cacheKey) {
            log.info("=====================開始删除緩存資料cacheKey:{}===========================", cacheKey);
            try {
                dataCommonCacheMapper.deleteByCacheKey(cacheKey);
            } catch (Exception e) {
                log.error("=======>>>>删除緩存資料失敗:{}", e.getMessage());
                e.printStackTrace();
            }
            log.info("=====================完成删除寫入緩存資料cacheKey:{}===========================", cacheKey);
        }
    }
}      
緩存工具類編寫

在工具類裡面實作緩存的邏輯

新增緩存思路

  • 1、根據緩存key查詢緩存ID和過期時間
  • 2、結果為空,表示沒有緩存,發送資料到緩存隊列,等待新增緩存隊列任務排程
  • 3、結果不為空,繼續判斷過期時間,過期時間不為空,并且已經過期了,則發送到過期删除隊列,等待排程
  • 4、沒有過期,真正查詢緩存的值, 比較值是是否更新,更新了發送更新資料到更新隊列,沒更新則不管

查詢緩存思路

  • 1、根據緩存key查詢資料(包含緩存值)
  • 2、結果為空,表示緩存不存在,直接傳回null
  • 3、結果不為空,判斷是否過期,過期則發送過期删除到删除隊列
  • 4、傳回查詢結果

删除緩存思路

  • 1、根據緩存key查詢資料(不包含緩存值)
  • 2、為空則不需要删除,不為空發送到删除隊列,等待排程

代碼實作

@Component
@Slf4j
public class DbCacheUtil {

    @Autowired
    private DataCommonCacheMapper dataCommonCacheMapper;

    @Autowired
    private DbCacheHelper.InsertCache insertCache;
    @Autowired
    private DbCacheHelper.UpdateCache updateCache;
    @Autowired
    private DbCacheHelper.DeleteCache deleteCache;

    /**
     * 插入緩存資料
     * @param cacheKey 緩存key
     * @param cacheValue 緩存值
     * @param ttl 機關毫秒 緩存失效時間 小于0表示永不過期
     */
    public synchronized void putCache(String cacheKey, String cacheValue, long ttl){
        // 根據緩存key查詢緩存  ID/過期時間等
        final DataCommonCache cache = dataCommonCacheMapper.selectIdByKey(cacheKey);
        if (cache == null){
            // 新增資料
            DataCommonCache commonCache = buildInsertData(cacheKey, cacheValue, ttl);
            // 發送給入庫隊列
            insertCache.producer(commonCache);
            return;
        }

        // 緩存設定了過期時間 并且緩存國企時間比目前時間小(過期了)
        if (cache.getCacheExpire() != null && cache.getCacheExpire().getTime() < System.currentTimeMillis()){
            // 發送删除過期Key隊列
            deleteCache.producer(cacheKey);
            return;
        }

        // 都不是 表示需要覆寫緩存 也就是更新緩存

        // 先判斷緩存的值是否和資料庫的值一緻 一緻則無需覆寫
        final String cacheValueResult = dataCommonCacheMapper.selectValueByKey(cacheKey);
        if (StringUtils.equals(cacheValueResult, cacheValue)){
            log.info("=============>>>>緩存key:{}的value與資料庫一緻,無需覆寫", cacheValue);
            return;
        }

        // 發送一個覆寫的請求
        final DataCommonCache dataCommonCache = buildInsertData(cacheKey, cacheValue, ttl);
        dataCommonCache.setCacheId(cache.getCacheId());
        updateCache.producer(dataCommonCache);
    }


    /**
     * 根據緩存從資料庫查詢
     * @param cacheKey 緩存key
     * @return 緩存值cacheValue 這裡傳回的值可能是已過期的 知道過期key删除之後才會傳回新的資料
     */
    public synchronized String getCache(String cacheKey){
        // 根據緩存key查詢緩存  ID/過期時間等
        final DataCommonCache cache = dataCommonCacheMapper.selectByKey(cacheKey);
        if (cache == null){
            log.info("===========緩存不存在, 請請先調用putCache緩存===========");
            return null;
        }

        // 緩存設定了過期時間 并且緩存國企時間比目前時間小(過期了)
        if (cache.getCacheExpire() != null && cache.getCacheExpire().getTime() < System.currentTimeMillis()){
            // 發送删除過期Key隊列
            deleteCache.producer(cacheKey);
            // 等待異步線程處理删除過期,但是這裡還是傳回緩存資料,進而減少資料庫的壓力,直到緩存删除後再次查詢到結果在傳回
        }
        log.info("================命中緩存cacheKey為:{}=================", cacheKey);
        // 不為空,傳回資料庫
        return cache.getCacheValue();
    }


    /**
     * 根據key删除緩存
     * @param cacheKey 緩存key
     */
    public synchronized void deleteCache(String cacheKey){
        // 根據緩存key查詢緩存  ID/過期時間等
        final DataCommonCache cache = dataCommonCacheMapper.selectIdByKey(cacheKey);
        if (cache == null){
            log.info("===========緩存不存在 無需删除===========");
            return;
        }

        // 發送删除消息到隊列
        deleteCache.producer(cacheKey);
    }


    private DataCommonCache buildInsertData(String cacheKey, String cacheValue, long ttl) {
        final DataCommonCache commonCache = new DataCommonCache();
        commonCache.setCacheKey(cacheKey);
        commonCache.setCacheValue(cacheValue);
        // 失效時間為目前是時間 + ttl時間
        Date expireTime = null;
        if (ttl > 0){
            expireTime = new Date(System.currentTimeMillis() + ttl);
        }
        commonCache.setCacheExpire(expireTime);
        return commonCache;
    }
}      
緩存測試接口
@RestController
@RequestMapping("/dbCache")
public class DbCacheController {
    @Autowired
    private DbCacheUtil dbCacheUtil;

    /**緩存時間設定為5分鐘, 可以自行設定*/
    private static final Long ttl = 300 *  1000L;

    @GetMapping("/test/putCache")
    public String putCache(@RequestParam("cacheKey") String cacheKey, @RequestParam("cacheValue")  String cacheValue){
        dbCacheUtil.putCache(cacheKey, cacheValue, ttl);
        return "success";
    }

    @GetMapping("/test/getCache")
    public String getCache(@RequestParam("cacheKey") String cacheKey){
        return dbCacheUtil.getCache(cacheKey);
    }

    @GetMapping("/test/deleteCache")
    public String deleteCache(@RequestParam("cacheKey") String cacheKey){
        dbCacheUtil.deleteCache(cacheKey);
        return "success";
    }
}      
接口測試

新增接口:​​http://localhost:8081/dbCache/test/putCache?cacheKey=test_name&cacheValue=張三​​

查詢接口:​​http://localhost:8081/dbCache/test/getCache?cacheKey=test_name​​

2022-08-10 09:31:36.699  INFO 19572 --- [nio-8081-exec-2] com.itdl.cache.util.DbCacheUtil          : ===========緩存不存在, 請請先調用putCache緩存===========
2022-08-10 09:31:39.815  INFO 19572 --- [nio-8081-exec-3] com.itdl.cache.BaseCacheHelper           : ================>>>通用隊列:com.itdl.cache.DbCacheHelper$InsertCache:目前隊列存在資料:1
2022-08-10 09:31:40.812  INFO 19572 --- [      統一可排程線程-1] com.itdl.cache.BaseCacheHelper           : =========>>>>消費資料1條
2022-08-10 09:31:40.813  INFO 19572 --- [      統一可排程線程-1] c.itdl.cache.DbCacheHelper$InsertCache   : =====================開始插入緩存資料cacheKey:test_name===========================
2022-08-10 09:31:40.851  INFO 19572 --- [      統一可排程線程-1] c.itdl.cache.DbCacheHelper$InsertCache   : =====================完成插入緩存資料cacheKey:test_name===========================
2022-08-10 09:31:40.852  INFO 19572 --- [      統一可排程線程-1] com.itdl.cache.BaseCacheHelper           : =========>>>>通用隊列:com.itdl.cache.DbCacheHelper$InsertCache:消費完成,總耗時:0.0383s<<<<=========
2022-08-10 09:31:42.296  INFO 19572 --- [nio-8081-exec-4] com.itdl.cache.util.DbCacheUtil          : ================命中緩存cacheKey為:test_name=================
2022-08-10 10:18:51.256  INFO 19572 --- [nio-8081-exec-8] com.itdl.cache.BaseCacheHelper           : ================>>>通用隊列:com.itdl.cache.DbCacheHelper$DeleteCache:目前隊列存在資料:1
2022-08-10 10:18:51.882  INFO 19572 --- [      統一可排程線程-1] com.itdl.cache.BaseCacheHelper           : =========>>>>消費資料1條
2022-08-10 10:18:51.882  INFO 19572 --- [      統一可排程線程-1] c.itdl.cache.DbCacheHelper$DeleteCache   : =====================開始删除緩存資料cacheKey:test_name===========================
2022-08-10 10:18:51.890  INFO 19572 --- [      統一可排程線程-1] c.itdl.cache.DbCacheHelper$DeleteCache   : =====================完成删除寫入緩存資料cacheKey:test_name===========================
2022-08-10 10:18:51.890  INFO 19572 --- [      統一可排程線程-1] com.itdl.cache.BaseCacheHelper           : =========>>>>通用隊列:com.itdl.cache.DbCacheHelper$DeleteCache:消費完成,總耗時:0.0079s<<<<=========
2022-08-10 10:19:01.817  INFO 19572 --- [nio-8081-exec-9] com.itdl.cache.util.DbCacheUtil          : ===========緩存不存在, 請請先調用putCache緩存===========      

項目位址

繼續閱讀