Redis的分布式缓存问题

穿透

  数据库与redis都不存在的key,由于莫名原因存在大量请求,导致请求跳过redis而访问DB

处理方法:

  1. 数据库不存在,redis也存储一个 null(或者常熟),并设置一个较短的过期时间,防止跳过redis
  2. 布隆过滤器

击穿

  Redis曾存在的key,由于过期时间而被删除,导致请求跳过redis而访问DB

处理方法:

  1. 不设置过期时间,永远存在
  2. 使用锁,synchronized、分布式锁

雪崩

  多个不同的key,由于设置了相同的过期时间,导致多个key在同一时间而被删除,从而导致DB压力过大

处理方法:

  1. 对每个key的过期时间,通过RandomUtils.nextInt(10)+1创建一个随机时间

代码功能实现:穿透、击穿、雪崩

/** * 使用 redis 进行缓存 * @return */ @Override public Map<String, List<Catalog2VO>> getCatelog2JSON() { System.out.println("为了严谨性,添加双重判断的 第一个判断"); String catelogs = stringRedisTemplate.opsForValue().get(CATEGORY_KEYS); if (Objects.equals(catelogs,"0")) { return null; } if (Strings.isNullOrEmpty(catelogs)){ Map<String, List<Catalog2VO>> catelog2JSONForDB = null; System.out.println("添加锁功能,是为了防止缓存击穿"); String uuid = UUID.randomUUID().toString(); // 防止查询数据库存在问报错,而导致没有解锁的问题。所以加入超时时间 // 使用 nx命令,保证判断与赋值为原子性,属于分布式锁 if(stringRedisTemplate.opsForValue().setIfAbsent("catelogLock", uuid,30, TimeUnit.SECONDS)) { try{ catelog2JSONForDB = getCatelog2JSONForDB(); return catelog2JSONForDB; } finally { System.out.println("缓存击穿添加的锁,现在进行解锁(原子性)"); // 通过Redis的lua脚本实现 查询和删除操作的原子性 String srcipts = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end "; stringRedisTemplate.execute(new DefaultRedisScript<Long>(srcipts,Long.class) ,Arrays.asList("lock"),uuid); } } else { // 睡眠一段时间,然后重新获取内容 // 就是以递归的方式进行循环调用 try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } return getCatelog2JSON(); } } // 获取到数据,并返回 Map<String, List<Catalog2VO>> map = JSONObject.parseObject(catelogs, new TypeReference<Map<String, List<Catalog2VO>>>() { }); return map; } /** * 查询出所有的二级和三级分类的数据 * 并封装为Map<String, Catalog2VO>对象 * * 进行优化 * @return */ private Map<String,List<Catalog2VO>> getCatelog2JSONForDB() { System.out.println("为了严谨性,添加双重判断的 第二个判断"); String catelogJson = stringRedisTemplate.opsForValue().get(CATEGORY_KEYS); if ("0".equals(catelogJson)) { // 查询报错结果 return null; } else if (!Strings.isNullOrEmpty(catelogJson)) { // 查询正常结果 Map<String, List<Catalog2VO>> map = JSONObject.parseObject(catelogJson, new TypeReference<Map<String, List<Catalog2VO>>>() { }); return map; } System.out.println("开始查询数据库------------->"); // 获取所有的分类的数据 List<CategoryEntity> list = this.list(); // 获取一级分类 List<CategoryEntity> leve1Category = queryByParenCid(list, 0l); Map<String, List<Catalog2VO>> map = leve1Category.stream().collect(Collectors.toMap( key -> key.getCatId().toString() , value -> { // 省略具体业务 return null; } )); System.out.println("防止缓存穿透与雪崩------------->"); if (map == null || map.size() == 0) { // 1.添加较短时间线,是为了防止缓存穿透 stringRedisTemplate.opsForValue().set(CATEGORY_KEYS, "0", 5, TimeUnit.SECONDS); } else { // 随机数的过期时间,是为了防止缓存雪崩 stringRedisTemplate.opsForValue().set(CATEGORY_KEYS, JSONObject.toJSONString(map), RandomUtils.nextInt(10)+1, TimeUnit.HOURS); } return map; }

__EOF__

本文作者之士咖啡
本文链接https://www.cnblogs.com/zz-1q/p/18227312.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   之士咖啡  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示