首页 > 数据库 >Redis的分布式缓存问题

Redis的分布式缓存问题

时间:2024-06-02 16:59:32浏览次数:27  
标签:map 缓存 return Map Redis redis key null 分布式

击穿

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

处理方法:

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

穿透

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

处理方法:

  1. 数据库不存在,redis也存储一个 null(或者常熟),防止跳过redis

雪崩

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

处理方法:

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

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

    /**
     * 使用 redis 进行缓存
     * @return
     */
    @Override
    public Map<String, List<Catalog2VO>> getCatelog2JSON() {
        // 先查询redis,是否有数据存储、
        // 如果有拿去返回,如果没有进行存储
        System.out.println("属于双重判断的 第一个判断");
        String catelogs = stringRedisTemplate.opsForValue().get(CATEGORY_KEYS);
        // 等于 0 说明数据库暂时没有数据
        if (Objects.equals(catelogs,"0")) {
            return null;
        }
        // 等于 null,说明没有查询过数据库
        if (Strings.isNullOrEmpty(catelogs)){
            Map<String, List<Catalog2VO>> catelog2JSONForDB = null;
            System.out.println("添加锁功能,是为了防止缓存击穿");
            // 单体:synchronized (this) {...}
            // 分布式  stringRedisTemplate.opsForValue().setIfAbsent(...) 如果不存在则进行添加
            String uuid = UUID.randomUUID().toString();
            // 防止查询数据库存在问报错,而导致没有解锁的问题。所以加入超时时间
            if(stringRedisTemplate.opsForValue().setIfAbsent("catelogLock", uuid,30, TimeUnit.SECONDS)) {
                try{
                    catelog2JSONForDB = getCatelog2JSONForDB();
                    return catelog2JSONForDB;
                } finally {
                    System.out.println("解锁(原子性)");
                    String srcipts = "if redis.call('get',KEYS[1]) == ARGV[1]  then return redis.call('del',KEYS[1]) else  return 0 end ";
                    // 通过Redis的lua脚本实现 查询和删除操作的原子性
                    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容器 key就是一级分类的编号, value就是一级分类对应的二级分类的数据
        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;
    }

标签:map,缓存,return,Map,Redis,redis,key,null,分布式
From: https://www.cnblogs.com/zz-1q/p/18227312

相关文章

  • Redis 高级应用与性能优化
    目录1.Redis集群与高可用性Redis集群介绍高可用性方案与实践2.Redis性能优化与监控性能指标与监控工具Redis的性能优化策略实时监控与故障排查3.Redis实践场景与最佳实践缓存与缓存雪崩、击穿、穿透计数器和限流器的实现分布式锁的应用实际项目中的Redis......
  • Redis集群搭建实战(主从复制、哨兵、集群)
    目录1、安装Redis3.02、主从复制(读写分离)2.1主从架构2.1.1 启动实例2.1.2设置主从2.1.3测试2.2主从从架构2.2.1启动实例2.2.2测试2.3从库只读​编辑2.4复制的过程原理2.5无磁盘复制2.6复制架构中出现宕机情况,怎么办?3、哨兵(sentinel)3.1什么是哨兵3......
  • Redis数据存储和读写
    今天工作群里,有小伙伴问了一个问题,从Redis获取的数据,一会是0,一会是OK。这引起了我们对Redis数据存储和读写的疑问。以下是整理的一些技术研究内容。在Redis中,所有的数据存储都是基于字符串的。无论你插入的是String、int还是DateTime类型的数据,最终都会以字符串的形式存......
  • git分布式版本控制系统(六)
    目前世界上最先进的分布式版本控制系统官方网址:https://git-scm.com学习目标:1了解git前世今生2掌握git基础概念、基础操作3各种git问题处理4互联网常用gitflow(工作流程规范)5git代码提交规范6git分支管理及命名规范代码提交规范Commitmessage我......
  • 使用 Sleuth 和 Zipkin 实现分布式链路追踪
    SpringCloud微服务之间的调用关系,通常随着业务的不断扩张而变得越来越复杂。如果调用链路上任何一个服务出现问题或者网络超时,导致通过日志快速排查和定位问题非常困难。分布式链路追踪就可以轻松解决该场景所面临的问题,其中一种比较简单的方案是采用SpringCloudSleuthSprin......
  • 深入理解Redis事务、事务异常、乐观锁、管道
    Redis事务与MySQL事务不一样。原子性:MySQL有UndoLog机制,支持强原子性,和回滚。Redis只能保证事务内指令可以不被干扰的在同一批次执行,且没有机制保证全部成功则提交,部分失败则回滚。隔离性:MySQL的隔离性指多个事务可以并发执行,MySQL有MVCC机制。而Redis没有,Redis是事务提交前......
  • 【Redis】 使用Java操作Redis的客户端
    文章目录......
  • Linux上的redis的安装和配置
    上传redis压缩包到linux上解压文件命令: tar-zxvfredis-6.2.4.tar.gz 进入解压好的redis文件编译make安装到指定目录makeinstallPREFIX=/usr/local/redis从解压文件中复制配置文件到reids中cp/root/redis-6.2.4/redis.conf/usr/local/redis/bin/修改配置文件re......
  • Redis笔记——对象之 SET
    是什么?        Redis的Set是一个无序的、不重复的集合字符串集合。        如果底层数据编码为INTSET,其实是有序的,不过不推荐依赖这个,整体还是看作无序来使用为好。场景        无序集合场景。如关注了那些公众号,Set提供了查交集、并集的功能......
  • Redis笔记——底层数据结构之压缩列表
    是什么?        本质上就是紧凑的列表。        压缩列表在Redis中有两种编码方式,分别是ZIPLIST与LISTTPACK。LISTPACK从Redis5.0引入,直至Redis7.0完全替换了ZIPLIST,可以看作是ZIPLIST的进阶版。有什么作用?        在List文章中,提......