当前位置:网站首页>基于数据库实现通用异步缓存系统
基于数据库实现通用异步缓存系统
2022-08-11 11:30:00 【51CTO】
编写目的
在某些特殊的项目中,想实现缓存,但是不能使用中间件,存内存又会导致内存大幅度上升,怎么办呢?
降低预期,将需要缓存的数据存储在数据库,如何设计一套数据库缓存呢。
设计思路
一个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
'通用缓存表'
;
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
功能实现
使用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
>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
配置文件
生成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;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
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);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
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
>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
整合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();
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
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.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
缓存实现
实现思路
- 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());
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
编写可调度的公共父类,实现调度的基本逻辑
@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;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
增删改调度任务实现
@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.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
缓存工具类编写
在工具类里面实现缓存的逻辑
新增缓存思路
- 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;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
缓存测试接口
@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";
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
接口测试
新增接口: 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
测试结果
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缓存===========
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
项目地址
边栏推荐
猜你喜欢

参与openEuler社区不到1年,我成为了Maintainer……

【毕业设计】老人心率脉搏血压体征监测手表 - stm32 单片机 嵌入式 物联网

虚拟机使用 WinSCP & Putty

如何批量下载hugging face模型和数据集文件

Web3 Entrepreneur's Guide: How to Build a Decentralized Community for Your Product?

兴盛优选:时序数据如何高效处理?

PerfView project (first) : how to find hot spots function

2.MySQL ---- 修改数据库的字符集(日常小技巧)

C语言,怪题小谈

小目标绝技 | 用最简单的方式完成Yolov5的小目标检测升级!
随机推荐
重要消息丨.NET Core 3.1 将于今年12月13日结束支持
EXCLUSIVE INTERVIEW | INTELLIGENCE IS SPONTANED, NOT PLANNED: Evolution Fan, Former OpenAI Research Manager and UBC Associate Professor Jeff Clune
开发者时薪高达1200美元?一文带你走近Move语言的编程魅力!
六、一起学习Lua 循环
[Study Notes] Dual Theorem of Linear Programming
闪灯IC,可按要求开发各种规格闪灯IC,单片机方案开发
Jetpack Compose学习(9)——Compose中的列表控件(LazyRow和LazyColumn)
Web3 创业者指南:如何为你的产品构建一个去中心化社区?
TX12 + ExpressLRS RC configuration and control link problem summary 915 MHZ
VirtualLab:Ince-Gaussian光束产生涡旋阵列激光束的观测
沃土云创计划重磅来袭
SD8016原厂单电池锂离子电池和锂聚合物电池充电IC
神经网络可视化有3D版本了,美到沦陷!(已开源)
在这个数字化的时代,如何做好用户体验与应用性能管理
悠漓带你玩转C语言(详解操作符1)
【2022】【Thesis Notes】Ultra-thin THz deflection based on laser direct writing graphene oxide paper——
C-V2X八大误区澄清和发展辩思
fiddler双向认证
低延时实时音视频在5G远程操控场景的应用实践
分析 Flink 任务如何超过 YARN 容器内存限制