此工具解决了Redis的缓存击穿、缓存穿透、缓存雪崩的问题,更多的可参考泛型与Function的使用!非常好的一种方式!

注意,下方代码依赖了Hutool工具包,以及引用了几个字符常量,自行换成任意字符即可!

介绍:

缓存击穿 queryWithPassThrough
缓存穿透 queryWithMutex
缓存雪崩 queryWithLogicalExpire
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;@Slf4j
@Component // 注册为IOC容器的Bean,使用直接Autowired
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;// 定义一个线程池,方便开启线程去执行操作private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}// 添加一个String类型的键值对public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}// 设置逻辑过期Key,以实现避免缓存击穿public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {// 设置逻辑过期HashMap<String, Object> hashMap = new HashMap();hashMap.put("data", value);hashMap.put("expireTime", LocalDateTime.now().plusSeconds(unit.toSeconds(time)));// 写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(hashMap));/**RedisData redisData = new RedisData();//redisData.setData(value);//redisData.setExpireTime();// 写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));**/}// 缓存穿透解决方案:正常查询,正常走。如果DB没有数据,则插入带有TTL的空值public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(json)) {// 3.存在,直接返回return JSONUtil.toBean(json, type);}// 判断命中的是否是空值if (json != null) {// 返回一个错误信息return null;}// 4.不存在,根据id查询数据库R r = dbFallback.apply(id);// 5.不存在,返回错误if (r == null) {// 将空值写入redisstringRedisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在,写入redisthis.set(key, r, time, unit);return r;}// 缓存雪崩的解决方案:询逻辑过期,如果过期了,那就重新插入进去public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isBlank(json)) {// 3.存在,直接返回return null;}// 4.命中,需要先把json反序列化为对象HashMap<String,Object> hashMap = JSONUtil.toBean(json, HashMap.class);R r = JSONUtil.toBean((JSONObject) hashMap.get("data"), type);/**RedisData redisData = JSONUtil.toBean(json, RedisData.class);R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);LocalDateTime expireTime = redisData.getExpireTime();**/LocalDateTime localDateTime = LocalDateTime.parse((CharSequence) hashMap.get("expireTime"), DateTimeFormatter.ofPattern("yyyy-MM-dd E HH:mm:ss"));// 5.判断是否过期,使用的是纯JDK的工具if (localDateTime.isAfter(LocalDateTime.now())) {// 5.1.未过期,直接返回店铺信息return r;}// 5.2.已过期,需要缓存重建// 6.缓存重建// 6.1.获取互斥锁String lockKey = "LOCK_SHOP_KEY" + id;boolean isLock = tryLock(lockKey);// 6.2.判断是否获取锁成功if (isLock) {// 6.3.成功,开启独立线程,实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() -> {try {// 查询数据库R newR = dbFallback.apply(id);// 重建缓存this.setWithLogicalExpire(key, newR, time, unit);} catch (Exception e) {throw new RuntimeException(e);} finally {// 释放锁unlock(lockKey);}});}// 6.4.返回过期的商铺信息return r;}// 缓存雪崩解决方案:互斥锁:自己会调用自己,如果Key不存在,则只有一个线程执行插入DB操作public <R, ID> R queryWithMutex(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {// 3.存在,直接返回return JSONUtil.toBean(shopJson, type);}// 判断命中的是否是空值if (shopJson != null) {// 返回一个错误信息return null;}// 4.实现缓存重建// 4.1.获取互斥锁String lockKey = "Operate:AddLock" + id;R r = null;try {boolean isLock = tryLock(lockKey);// 4.2.判断是否获取成功if (!isLock) {// 4.3.获取锁失败,休眠并重试Thread.sleep(50);return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);}// 4.4.获取锁成功,根据id查询数据库r = dbFallback.apply(id);// 5.不存在,返回错误if (r == null) {// 将空值写入redisstringRedisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在,写入redisthis.set(key, r, time, unit);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 7.释放锁unlock(lockKey);}// 8.返回return r;}// 尝试加锁private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}// 解锁private void unlock(String key) {stringRedisTemplate.delete(key);}
}
特殊说明: 以上文章,均是我实际操作,写出来的笔记资料,不会盗用别人文章!烦请各位,请勿直接盗用!转载记得标注来源!
收_心

永久会员

打赏 收藏 海报 链接

Redis CacheClient相关推荐

  1. Redis作为缓存服务器

    1.ICache的Redis实现没有放在'Framework.Cache/Logic'中.如果是以前,我会认为这样不好.我会这样做,'Framework.Cache'项目引用Redis项目或直接从Nu ...

  2. Redis入门到实战(实战篇)缓存更新、穿透、雪崩、击穿!

    Redis基础篇 Java面试宝典-redis 实战篇Redis 开篇导读 亲爱的小伙伴们大家好,马上咱们就开始实战篇的内容了,相信通过本章的学习,小伙伴们就能理解各种redis的使用啦,接下来咱们来 ...

  3. Redis总结_实战篇

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 实战篇Redis 开篇导读 1.短信登录 1.1.导入黑马点评项目 1.1.1 .导入SQL 1.1.2.有关当前模型 1. ...

  4. 黑马点评Redis实战(短信登录;商户查询缓存)

    黑马点评 通过一个类似于大众点评的项目了解学习redis在实战项目中的使用,下面是项目中会涉及到的模块: 一.导入黑马点评项目 导入springboot项目,导入sql脚本到数据库,开启nginx,更 ...

  5. Redis学习笔记(实战篇)(自用)

    Redis学习笔记(实战篇)(自用) 本文根据黑马程序员的课程资料与百度搜索的资料共同整理所得,仅用于学习使用,如有侵权,请联系删除 文章目录 Redis学习笔记(实战篇)(自用) 1.基于Sessi ...

  6. redis缓存(redis缓存工具封装篇)

    1.0什么是缓存? 缓存(Cache),就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,一般从数据库中获取,存储于本地代码(例如: 例1:Static final ConcurrentHashMa ...

  7. 黑马Redis学习笔记 (基础篇+实战篇)

    目录 **一.初始Redis** **1.1SQL 和 NoSql的区别** **1.1.1结构化和非结构化** **1.1.2关联和非关联** **1.1.3查询方式** **1.1.4 事务** ...

  8. Redis实战之查询缓存

    Reids实战之查询缓存 对于一些不常变动却查询频率高的数据,对此进行数据库的访问会降低查询效率,此时可以使用redis缓存来解决,案例:查询商品信息 一 实现思路 可能出缓存穿透问题,以及缓存和数据 ...

  9. Redis(七) - 封装Redis工具类

    文章目录 一.封装Redis工具类 1. 使用构造方法注入StringRedisTemplate 2. 方法1:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL ...

最新文章

  1. C++ 产生0-2之间的随机数
  2. 新手建站注意点,你有注意到没?
  3. D3js(六):支持css的tooltips
  4. 如何复制CSDN上他人的博客文章到自己博客下
  5. 物理层、数据链路层网络设备工作原理
  6. JavaScript高级程序设计阅读笔记
  7. 一个程序员如何做到结构上胸有成竹
  8. linux卸载openJDK
  9. matlab consumption,Lesage matlab 空间
  10. SolrCloud集群的安装使用以及Zookeeper的介绍
  11. 数据科学包5- pandas基础之基础运算
  12. MATLAB模式识别基本操作函数解析
  13. python黑帽子学习
  14. 云原生技术开放日PPT大放送 | 五星级的云原生开发体验
  15. PowerBI使用Tabular Editor翻译报表模型<二>
  16. html5在线画板菱形怎么画,使用HTML5构建一个在线画板应用
  17. Python Open3D点云配准点对点,点对面ICP(Iterative Closest Point)
  18. Idea的GenerateAllSetter插件,快速填充对象属性
  19. SQLSERVER时间转换成字符串,去掉中划线
  20. OPC OPCUA OPCDA

热门文章

  1. 使用路由器搭建机器人局域网,进而远程机器人主机
  2. RE0:从零开始的服务器生活(一):双硬盘双系统+无线网卡驱动+最简单的Ubuntu16.04 Nvidia显卡驱动设置
  3. 计算机课程思政元素,《数据结构》课程思政元素的设计
  4. ndk开发流程,9次Android面试经验总结,已拿offer入职
  5. [UE]模块无法被加载,因此插件加载失败。可能存在系统错误,或模块未能正确设置
  6. js 在一个日期上面加上指定时间(几小时、几分钟、几秒)
  7. 使用Tableau绘制电影数量与评分的符号地图
  8. java 筛选文件后缀为.txt文件
  9. 求两个整数的商和余数(不用乘,除,取余)计算
  10. linux设置steam为中文,Linux下Steam中支持中文的办法