【Redis】缓存工具封装

news/2024/10/30 15:31:10/

【Redis】缓存工具封装

文章目录

  • 【Redis】缓存工具封装
    • 1. 方法要求
      • 1.1 方法一
      • 1.2 方法二
      • 1.3 方法三
      • 1.4 方法四
    • 2. 完整工具类代码

StringRedisTemplate 封装成一个缓存工具类,方便以后重复使用。

1. 方法要求

在这个工具类中我们完成四个方法:

  • 方法①:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间
  • 方法②:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓存击穿问题
  • 方法③:根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
  • 方法④:根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题

我们新建一个类,先把大致框架写出来,方法的参数可以边写边完善,但是我的方法参数已经完善好了:

@Component
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}//方法一public void set(String key, Object value, Long time, TimeUnit unit) {}//方法二public void setWithLogicExpire(String key, Object value, Long time, TimeUnit unit) {}//方法三public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type,Long time, TimeUnit unit, Function<ID, R> dbFallback) {}//方法四public <R, ID> R queryWithLogicalExpire(String prefix, ID id, String lockPre, Class<R> type,Long time, TimeUnit unit, Function<ID, R> dbFallback) {}//线程池private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);//获取锁private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}//释放锁private void unLock(String key) {stringRedisTemplate.delete(key);}
}

接下来我们可以不断完善这些方法。


1.1 方法一

public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
}

1.2 方法二

public void setWithLogicExpire(String key, Object value, Long time, TimeUnit unit) {RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}

1.3 方法三

public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type,Long time, TimeUnit unit, Function<ID, R> dbFallback) {String key = keyPrefix + id;//1.从redis中查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isNotBlank(json)) {//2.1.存在return JSONUtil.toBean(json, type);}//2.2.不存在//判断是否为空值if (json != null) {//不为null,则必为空return null;}//3.查询数据库R r = dbFallback.apply(id);if (r == null) {//3.1.不存在,缓存空值stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);} else {//3.2.存在,缓存数据this.set(key, r, time, unit);}return r;
}

方法三用到了函数式编程,这里非常巧妙,顺便再贴一下调用方法是怎样调用的:

Shop shop = cacheClient.queryWithPassThrough(CACHE_SHOP_KEY,id,Shop.class,CACHE_SHOP_TTL,TimeUnit.MINUTES,this::getById);

1.4 方法四

public <R, ID> R queryWithLogicalExpire(String prefix, ID id, String lockPre, Class<R> type,Long time, TimeUnit unit, Function<ID, R> dbFallback) {//1.从redis查询商铺缓存String key = prefix + id;String json = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isBlank(json)) {//未命中,直接返回空return null;}//3.命中,判断是否过期RedisData redisData = JSONUtil.toBean(json, RedisData.class);R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {//3.1未过期,直接返回店铺信息return r;}//3.2.已过期,缓存重建//3.3.获取锁String lockKey = lockPre + id;boolean flag = tryLock(lockKey);if (flag) {//3.4.获取成功//4再次检查redis缓存是否过期,做double checkjson = stringRedisTemplate.opsForValue().get(key);//4.1.判断是否存在if (StrUtil.isBlank(json)) {//未命中,直接返回空return null;}//4.2.命中,判断是否过期redisData = JSONUtil.toBean(json, RedisData.class);r = JSONUtil.toBean((JSONObject) redisData.getData(), type);if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {//4.3.未过期,直接返回店铺信息return r;}//4.4过期,返回旧数据CACHE_REBUILD_EXECUTOR.submit(() -> {//5.重建缓存try {R r1 = dbFallback.apply(id);this.setWithLogicExpire(key, r1, time, unit);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁unLock(lockKey);}});}//7.获取失败,返回旧数据return r;
}

2. 完整工具类代码

@Component
@Slf4j
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}public void setWithLogicExpire(String key, Object value, Long time, TimeUnit unit) {RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type,Long time, TimeUnit unit, Function<ID, R> dbFallback) {String key = keyPrefix + id;//1.从redis中查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isNotBlank(json)) {//2.1.存在return JSONUtil.toBean(json, type);}//2.2.不存在//判断是否为空值if (json != null) {//不为null,则必为空return null;}//3.查询数据库R r = dbFallback.apply(id);if (r == null) {//3.1.不存在,缓存空值stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);} else {//3.2.存在,缓存数据this.set(key, r, time, unit);}return r;}public <R, ID> R queryWithLogicalExpire(String prefix, ID id, String lockPre, Class<R> type,Long time, TimeUnit unit, Function<ID, R> dbFallback) {//1.从redis查询商铺缓存String key = prefix + id;String json = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isBlank(json)) {//未命中,直接返回空return null;}//3.命中,判断是否过期RedisData redisData = JSONUtil.toBean(json, RedisData.class);R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {//3.1未过期,直接返回店铺信息return r;}//3.2.已过期,缓存重建//3.3.获取锁String lockKey = lockPre + id;boolean flag = tryLock(lockKey);if (flag) {//3.4.获取成功//4再次检查redis缓存是否过期,做double checkjson = stringRedisTemplate.opsForValue().get(key);//4.1.判断是否存在if (StrUtil.isBlank(json)) {//未命中,直接返回空return null;}//4.2.命中,判断是否过期redisData = JSONUtil.toBean(json, RedisData.class);r = JSONUtil.toBean((JSONObject) redisData.getData(), type);if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {//4.3.未过期,直接返回店铺信息return r;}//4.4过期,返回旧数据CACHE_REBUILD_EXECUTOR.submit(() -> {//5.重建缓存try {R r1 = dbFallback.apply(id);this.setWithLogicExpire(key, r1, time, unit);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁unLock(lockKey);}});}//7.获取失败,返回旧数据return r;}private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);//获取锁private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}//释放锁private void unLock(String key) {stringRedisTemplate.delete(key);}
}

http://www.ppmy.cn/news/12224.html

相关文章

初识网络:IP、端口、网络协议、TCP-IP五层模型

目录 一、了解IP地址&#xff1a; 二、了解端口号: 三、网络协议 网络协议的三要素: ①语法 ②语义 ③时序 四、协议的分层(TCP/IP五层模型&#xff09; ①应用层 ②传输层&#xff08;也称为运输层&#xff09; ③网络层 ④数据链路层 ⑤物理层 五、A用户通过QQ给B发送一…

linux系统中使用QT实现摄像头功能的方法

大家好&#xff0c;今天主要和大家聊一聊&#xff0c;如何使用QT中的Camera的功能和实现。 目录 第一&#xff1a;摄像头资源简介 第二&#xff1a;环境搭建要求 第三&#xff1a;代码编译实现要求 第一&#xff1a;摄像头资源简介 开发板上有一路“CSI”摄像头接口&#xf…

2022.12 青少年机器人技术等级考试理论综合试卷(六级)

2022年12月青少年机器人技术等级考试理论综合试卷&#xff08;六级&#xff09; 一、 单选题(共 20 题&#xff0c; 共 80 分) 1.TCP/IP 四层模型中&#xff0c; 用于传送应用层数据包的是&#xff1f; &#xff08; &#xff09; A.应用层 B.传输层 C.网络层 D.网络接口层 标…

parquet

一、parquet结构 Row Group ​ --Column Chunk&#xff1a;一列对应一个Column Chunk ​ – Page&#xff1a;压缩和编码的单元&#xff0c;parquet的 min/max 索引是针对于page的&#xff0c;存在了文件的页脚。以前的版本是存储Column Chunk和Page的索引&#xff0c;导致在…

LeetCode622.设计循环队列

设计循环队列1.题目描述2.思路3.代码实现以及分析3.1 创建结构体3.2创建一个具体的循环队列3.3判断是否为空 和 判断是否为满4. 进队列 和 出队列5.取队首和队尾元素6.释放空间7.总结1.题目描述 设计循环队列 2.思路 环形队列的抽象图 我们这里使用数组模拟实现循环队列&…

TCP/IP网络编程(4)——基于 TCP 的服务端/客户端(1)

文章目录第 4 章 基于 TCP 的服务端/客户端&#xff08;1&#xff09;4.1 理解 TCP 和 UDP4.1.1 TCP/IP 协议栈4.1.2 链路层4.1.3 IP 层4.1.4 TCP/UDP 层4.1.5 应用层4.1.6 生活小例子4.2 实现基于 TCP 的服务器/客户端4.2.1 TCP 服务端的默认函数的调用程序4.2.2 进入等待连接…

57 mac 中 SIGINFO 信号, jdk8 支持, 但是 jdk9 不支持?

前言 问题来自于文章 shell脚本 后台启动 程序1 “tail -f log“, ctrl c 导致程序1中断 中的测试用例 Test07Signal2ParentProcess, 可以看到 我当时标记了一个 "todo, not work in hostpostVM9" 然后 问题是这样的, 我同一台机器, 然后 jdk8 带上 SIGINFO 去执行…

时间序列模型SCINet(代码解析)

前言 SCINet模型&#xff0c;精度仅次于NLinear的时间序列模型&#xff0c;在ETTh2数据集上单变量预测结果甚至比NLinear模型还要好。在这里还是建议大家去读一读论文&#xff0c;论文写的很规范&#xff0c;很值得学习&#xff0c;论文地址SCINet模型Github项目地址&#xff…