使用数据库实现缓存功能
編寫目的
在某些特殊的項目中,想實現(xiàn)緩存,但是不能使用中間件,存內(nèi)存又會導致內(nèi)存大幅度上升,怎么辦呢?
降低預期,將需要緩存的數(shù)據(jù)存儲在數(shù)據(jù)庫,如何設(shè)計一套數(shù)據(jù)庫緩存呢。
設(shè)計思路
一個KV形式緩存中間件需要有哪些基礎(chǔ)功能?
- 1、增加緩存(新增數(shù)據(jù)庫)
- 2、緩存覆蓋(修改數(shù)據(jù)庫)
- 3、緩存過期刪除(刪除數(shù)據(jù)庫數(shù)據(jù))
- 4、查詢緩存(查詢數(shù)據(jù)庫)
其實,就是對數(shù)據(jù)庫的增刪改查。但是緩存的數(shù)據(jù)一般情況是寫入和查詢比較頻繁的。
- 查詢優(yōu)化: 在字段KEY上建立唯一索引
- 插入優(yōu)化:使用隊列 + 定時任務異步入庫
- 緩存覆蓋:使用隊列 + 定時任務異步更新
- 過期刪除:使用隊列 + 定時任務異步刪除
表設(shè)計
主鍵,緩存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 '通用緩存表';功能實現(xiàn)
使用SpringBoot + Mybatis Plus 實現(xiàn)通用緩存功能
依賴引入
<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: 8081spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3306/test_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=trueusername: rootpassword: 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> {/*** 根據(jù)緩存key查詢緩存ID 如果ID為空 表示緩存不存在 不為空表示緩存存在* @param cacheKey 緩存key* @return 緩存的ID, 過期時間 為空表示緩存不存在*/DataCommonCache selectIdByKey(@Param("cacheKey") String cacheKey);/*** 根據(jù)緩存key查詢緩存ID 如果ID為空 表示緩存不存在 不為空表示緩存存在* @param cacheKey 緩存key* @return 緩存的ID, 過期時間,緩存值 為空表示緩存不存在*/DataCommonCache selectByKey(@Param("cacheKey") String cacheKey);/*** 根據(jù)緩存key查詢緩存ID 如果ID為空 表示緩存不存在 不為空表示緩存存在* @param cacheKey 緩存key* @return 緩存的ID 為空表示*/String selectValueByKey(@Param("cacheKey") String cacheKey);/*** 根據(jù)緩存key刪除數(shù)據(jù)* @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><!--根據(jù)緩存key獲取緩存信息,主要包含緩存ID和過期時間 結(jié)果為空表示沒有數(shù)據(jù)--><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><!--根據(jù)緩存的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用于增刪改差, 并實現(xiàn)了MybtaisPlus真正的批量新增和批量修改
數(shù)據(jù)源配置類DatasourceConfig
包含了數(shù)據(jù)源的配置和SqlSessionFactory配置,且注入了MybatisPlus的配置
/*** @Description 數(shù)據(jù)庫相關(guān)配置* @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();}@Beanpublic 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 {@Overridepublic List<AbstractMethod> getMethodList(Class<?> mapperClass) {List<AbstractMethod> methodList = super.getMethodList(mapperClass);methodList.add(new InsertBatchSomeColumn());methodList.add(new UpdateBatchMethod());return methodList;}}/*** 批量更新方法實現(xiàn),條件為主鍵,選擇性更新*/ @Slf4j public class UpdateBatchMethod extends AbstractMethod {@Overridepublic 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);// 第三個參數(shù)必須和RootMapper的自定義方法名一致return this.addUpdateMappedStatement(mapperClass, modelClass, "updateBatch", sqlSource);} }緩存實現(xiàn)
實現(xiàn)思路
- 1、配置一個可調(diào)度的線程池,用于異步隊列的調(diào)度
- 2、編寫一個基礎(chǔ)調(diào)度父類,實現(xiàn)調(diào)度的基本邏輯
- 3、編寫緩存插入,覆蓋,刪除的調(diào)度邏輯
- 4、將調(diào)度邏輯整合為一個緩存工具類
- 5、使用Controller接口測試緩存增刪改查
配置可調(diào)度的線程池
/*** @Description 通用配置及* @Author itdl* @Date 2022/08/09 17:57*/ @Configuration public class CommonConfig {@Bean("scheduledThreadPoolExecutor")public ScheduledThreadPoolExecutor scheduledThreadPoolExecutor() {//線程名String threadNameStr = "統(tǒng)一可調(diào)度線程-%d";//線程工廠類就是將一個線程的執(zhí)行單元包裝成為一個線程對象,比如線程的名稱,線程的優(yōu)先級,線程是否是守護線程等線程;// guava為了我們方便的創(chuàng)建出一個ThreadFactory對象,我們可以使用ThreadFactoryBuilder對象自行創(chuàng)建一個線程.ThreadFactory threadNameVal = new ThreadFactoryBuilder().setNameFormat(threadNameStr).build();// 單線程池return new ScheduledThreadPoolExecutor(// 核心線程池4,// 最大線程池threadNameVal,// 使用策略為拋出異常new ThreadPoolExecutor.AbortPolicy());} }編寫可調(diào)度的公共父類,實現(xiàn)調(diào)度的基本邏輯
@Slf4j public abstract class BaseCacheHelper<T> {@Resourceprivate ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;// 隊列private final BlockingQueue<T> QUEUE = new ArrayBlockingQueue<>(1024);// listener執(zhí)行次數(shù) 計數(shù)器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("================>>>通用隊列:{}:當前隊列存在數(shù)據(jù):{}", this.getClass().getName(), QUEUE.size());}@PostConstructpublic void consumer() {scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {try {// 隊列數(shù)量達到指定消費批次數(shù)量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);}/*** 消費數(shù)據(jù)*/protected void doConsumer() {// 這里面開始真正的寫磁盤if (ObjectUtils.isEmpty(eventStorageList)) {return;}// 批處理final StopWatch stopWatch = new StopWatch();stopWatch.start();log.info("=========>>>>消費數(shù)據(jù){}條", eventStorageList.size());for (T t : eventStorageList) {StopWatch subStopWatch = new StopWatch();subStopWatch.start();// 處理每一個消費者的邏輯 用于子類實現(xiàn)doHandleOne(t);subStopWatch.stop();}// 重置數(shù)據(jù)EXECUTE_COUNT.set(0);eventStorageList.clear();stopWatch.stop();log.info("=========>>>>通用隊列:{}:消費完成,總耗時:{}s<<<<=========", this.getClass().getName(), String.format("%.4f", stopWatch.getTotalTimeSeconds()));}/*** 消費一條數(shù)據(jù)** @param data 處理數(shù)據(jù)*/protected abstract void doHandleOne(T data);/*** 批次大小 默認每次消費100條** @return 此次大小*/protected Integer getBatchSize() {return 100;}/*** 批次大小 執(zhí)行完任務后 間隔多久再執(zhí)行 單位 毫秒 默認5秒** @return 此次大小*/protected Integer getPeriodTime() {return 1000;} }增刪改調(diào)度任務實現(xiàn)
@Slf4j @Component public class DbCacheHelper{@Slf4j@Componentpublic static class InsertCache extends BaseCacheHelper<DataCommonCache>{@Autowiredprivate DataCommonCacheMapper dataCommonCacheMapper;@Overrideprotected void doHandleOne(DataCommonCache data) {final String cacheKey = data.getCacheKey();log.info("=====================開始插入緩存數(shù)據(jù)cacheKey:{}===========================", cacheKey);try {dataCommonCacheMapper.insert(data);} catch (Exception e) {log.error("=======>>>>插入緩存數(shù)據(jù)失敗:{}", e.getMessage());e.printStackTrace();}log.info("=====================完成插入緩存數(shù)據(jù)cacheKey:{}===========================", cacheKey);}}@Slf4j@Componentpublic static class UpdateCache extends BaseCacheHelper<DataCommonCache>{@Autowiredprivate DataCommonCacheMapper dataCommonCacheMapper;@Overrideprotected void doHandleOne(DataCommonCache data) {final String cacheKey = data.getCacheKey();log.info("=====================開始覆蓋寫入緩存數(shù)據(jù)cacheKey:{}===========================", cacheKey);try {dataCommonCacheMapper.updateById(data);} catch (Exception e) {log.error("=======>>>>覆蓋寫入緩存數(shù)據(jù)失敗:{}", e.getMessage());e.printStackTrace();}log.info("=====================完成覆蓋寫入緩存數(shù)據(jù)cacheKey:{}===========================", cacheKey);}}@Slf4j@Componentpublic static class DeleteCache extends BaseCacheHelper<String>{@Autowiredprivate DataCommonCacheMapper dataCommonCacheMapper;@Overrideprotected void doHandleOne(String cacheKey) {log.info("=====================開始刪除緩存數(shù)據(jù)cacheKey:{}===========================", cacheKey);try {dataCommonCacheMapper.deleteByCacheKey(cacheKey);} catch (Exception e) {log.error("=======>>>>刪除緩存數(shù)據(jù)失敗:{}", e.getMessage());e.printStackTrace();}log.info("=====================完成刪除寫入緩存數(shù)據(jù)cacheKey:{}===========================", cacheKey);}} }緩存工具類編寫
在工具類里面實現(xiàn)緩存的邏輯
新增緩存思路
- 1、根據(jù)緩存key查詢緩存ID和過期時間
- 2、結(jié)果為空,表示沒有緩存,發(fā)送數(shù)據(jù)到緩存隊列,等待新增緩存隊列任務調(diào)度
- 3、結(jié)果不為空,繼續(xù)判斷過期時間,過期時間不為空,并且已經(jīng)過期了,則發(fā)送到過期刪除隊列,等待調(diào)度
- 4、沒有過期,真正查詢緩存的值, 比較值是是否更新,更新了發(fā)送更新數(shù)據(jù)到更新隊列,沒更新則不管
查詢緩存思路
- 1、根據(jù)緩存key查詢數(shù)據(jù)(包含緩存值)
- 2、結(jié)果為空,表示緩存不存在,直接返回null
- 3、結(jié)果不為空,判斷是否過期,過期則發(fā)送過期刪除到刪除隊列
- 4、返回查詢結(jié)果
刪除緩存思路
- 1、根據(jù)緩存key查詢數(shù)據(jù)(不包含緩存值)
- 2、為空則不需要刪除,不為空發(fā)送到刪除隊列,等待調(diào)度
代碼實現(xiàn)
@Component @Slf4j public class DbCacheUtil {@Autowiredprivate DataCommonCacheMapper dataCommonCacheMapper;@Autowiredprivate DbCacheHelper.InsertCache insertCache;@Autowiredprivate DbCacheHelper.UpdateCache updateCache;@Autowiredprivate DbCacheHelper.DeleteCache deleteCache;/*** 插入緩存數(shù)據(jù)* @param cacheKey 緩存key* @param cacheValue 緩存值* @param ttl 單位毫秒 緩存失效時間 小于0表示永不過期*/public synchronized void putCache(String cacheKey, String cacheValue, long ttl){// 根據(jù)緩存key查詢緩存 ID/過期時間等final DataCommonCache cache = dataCommonCacheMapper.selectIdByKey(cacheKey);if (cache == null){// 新增數(shù)據(jù)DataCommonCache commonCache = buildInsertData(cacheKey, cacheValue, ttl);// 發(fā)送給入庫隊列insertCache.producer(commonCache);return;}// 緩存設(shè)置了過期時間 并且緩存國企時間比當前時間小(過期了)if (cache.getCacheExpire() != null && cache.getCacheExpire().getTime() < System.currentTimeMillis()){// 發(fā)送刪除過期Key隊列deleteCache.producer(cacheKey);return;}// 都不是 表示需要覆蓋緩存 也就是更新緩存// 先判斷緩存的值是否和數(shù)據(jù)庫的值一致 一致則無需覆蓋final String cacheValueResult = dataCommonCacheMapper.selectValueByKey(cacheKey);if (StringUtils.equals(cacheValueResult, cacheValue)){log.info("=============>>>>緩存key:{}的value與數(shù)據(jù)庫一致,無需覆蓋", cacheValue);return;}// 發(fā)送一個覆蓋的請求final DataCommonCache dataCommonCache = buildInsertData(cacheKey, cacheValue, ttl);dataCommonCache.setCacheId(cache.getCacheId());updateCache.producer(dataCommonCache);}/*** 根據(jù)緩存從數(shù)據(jù)庫查詢* @param cacheKey 緩存key* @return 緩存值cacheValue 這里返回的值可能是已過期的 知道過期key刪除之后才會返回新的數(shù)據(jù)*/public synchronized String getCache(String cacheKey){// 根據(jù)緩存key查詢緩存 ID/過期時間等final DataCommonCache cache = dataCommonCacheMapper.selectByKey(cacheKey);if (cache == null){log.info("===========緩存不存在, 請請先調(diào)用putCache緩存===========");return null;}// 緩存設(shè)置了過期時間 并且緩存國企時間比當前時間小(過期了)if (cache.getCacheExpire() != null && cache.getCacheExpire().getTime() < System.currentTimeMillis()){// 發(fā)送刪除過期Key隊列deleteCache.producer(cacheKey);// 等待異步線程處理刪除過期,但是這里還是返回緩存數(shù)據(jù),從而減少數(shù)據(jù)庫的壓力,直到緩存刪除后再次查詢到結(jié)果在返回}log.info("================命中緩存cacheKey為:{}=================", cacheKey);// 不為空,返回數(shù)據(jù)庫return cache.getCacheValue();}/*** 根據(jù)key刪除緩存* @param cacheKey 緩存key*/public synchronized void deleteCache(String cacheKey){// 根據(jù)緩存key查詢緩存 ID/過期時間等final DataCommonCache cache = dataCommonCacheMapper.selectIdByKey(cacheKey);if (cache == null){log.info("===========緩存不存在 無需刪除===========");return;}// 發(fā)送刪除消息到隊列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 {@Autowiredprivate DbCacheUtil dbCacheUtil;/**緩存時間設(shè)置為5分鐘, 可以自行設(shè)置*/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
刪除接口:http://localhost:8081/dbCache/test/deleteCache?cacheKey=test_name
測試結(jié)果
2022-08-10 09:31:36.699 INFO 19572 --- [nio-8081-exec-2] com.itdl.cache.util.DbCacheUtil : ===========緩存不存在, 請請先調(diào)用putCache緩存=========== 2022-08-10 09:31:39.815 INFO 19572 --- [nio-8081-exec-3] com.itdl.cache.BaseCacheHelper : ================>>>通用隊列:com.itdl.cache.DbCacheHelper$InsertCache:當前隊列存在數(shù)據(jù):1 2022-08-10 09:31:40.812 INFO 19572 --- [ 統(tǒng)一可調(diào)度線程-1] com.itdl.cache.BaseCacheHelper : =========>>>>消費數(shù)據(jù)1條 2022-08-10 09:31:40.813 INFO 19572 --- [ 統(tǒng)一可調(diào)度線程-1] c.itdl.cache.DbCacheHelper$InsertCache : =====================開始插入緩存數(shù)據(jù)cacheKey:test_name=========================== 2022-08-10 09:31:40.851 INFO 19572 --- [ 統(tǒng)一可調(diào)度線程-1] c.itdl.cache.DbCacheHelper$InsertCache : =====================完成插入緩存數(shù)據(jù)cacheKey:test_name=========================== 2022-08-10 09:31:40.852 INFO 19572 --- [ 統(tǒng)一可調(diào)度線程-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:當前隊列存在數(shù)據(jù):1 2022-08-10 10:18:51.882 INFO 19572 --- [ 統(tǒng)一可調(diào)度線程-1] com.itdl.cache.BaseCacheHelper : =========>>>>消費數(shù)據(jù)1條 2022-08-10 10:18:51.882 INFO 19572 --- [ 統(tǒng)一可調(diào)度線程-1] c.itdl.cache.DbCacheHelper$DeleteCache : =====================開始刪除緩存數(shù)據(jù)cacheKey:test_name=========================== 2022-08-10 10:18:51.890 INFO 19572 --- [ 統(tǒng)一可調(diào)度線程-1] c.itdl.cache.DbCacheHelper$DeleteCache : =====================完成刪除寫入緩存數(shù)據(jù)cacheKey:test_name=========================== 2022-08-10 10:18:51.890 INFO 19572 --- [ 統(tǒng)一可調(diào)度線程-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 : ===========緩存不存在, 請請先調(diào)用putCache緩存===========項目地址
https://github.com/HedongLin123/db-cache-demo.git
總結(jié)
以上是生活随笔為你收集整理的使用数据库实现缓存功能的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2018年资深前辈的面试经验
- 下一篇: python字符串去除空格,python