暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

如何优雅删除生产的缓存数据

杨遥 2020-05-21
603

作者 | 杨遥


随着我们项目的迭代升级,可能在发新版本的时候需要批量删除redis中的缓存数据,redis不像mysql一样,直接查询就能删除。这时候你可能首先想到的是keys *命令,我只能告诉你,你可以收拾东西回去了,辞职报告也不用写了。那么应该怎么合理的删除缓存呢,接着往下看:


背景


项目开发中,我们经常把热点数据放入缓存,并且为了防止缓存雪崩,我们还设置了永不过期,但是在项目迭代,升级过程中就有一些场景需要更新缓存。比如我们新增了数据库的字段信息,业务数据有变更等。那么我们是去更新缓存还是去删除缓存呢,个人认为更新缓存没有太大的必要性,我们只需要去删除缓存即可,下次查询自动存入数据库。


难点


一些不了解redis特性的小伙伴们张口就来,简单。先keys pattern模糊所有要删除的key,再删除即可。看似没有毛病的,却隐藏的杀机。开发环境,测试环境并大量小,缓存数据量少的时候当然没啥问题,于是乎就高高兴兴的提交了代码,上了生产环境你就知道为什么不能这么做了。很多公司的运维已经禁用keys *命令,具体为什么请自行百度,我们今天讲另外一个实现方案。


SCAN命令


我们都知道redis是单线程的,keys*是阻塞的方式,时间复杂度是O(N),因此在生产稍微不注意就阻塞进程,造成redis卡顿,对于并发量大的系统是绝对不允许的。scan命令则是以非阻塞的方式,大多数情况下是可以替代keys命令的,scan提供类似limit的功能告诉redis每次查询多少条,scan在控制台的命令如下:


    scan cursor [MATCH pattern] [COUNT count]  


    我的本地redis有3000条数据,其中有1000条是com:yangyao:开头的key,请看下面的scan语句,每次扫描10条,返回的不一定有10条,redis只能尽力去保证,每次scan后会返回一个游标,用于下次接着scan


      127.0.0.1:6379> scan 0 match com:yangyao:* count 10
      1) "1280"
      2) 1) "com:yangyao:test:35"
      2) "com:yangyao:test:622"
      3) "com:yangyao:test:476"
      4) "com:yangyao:test:896"
      127.0.0.1:6379> scan 1280 match com:yangyao:* count 10
      1) "640"
      2) 1) "com:yangyao:test:113"
      2) "com:yangyao:test:441"
      3) "com:yangyao:test:700"


      RedisTemplate实战


            /**
        * 根据key和count扫描
        *
        * @param keys
        * @param scanCount
        * @param consumer
        * @return key列表
        */
        private void scanKeys(String keys, Long scanCount, Consumer<byte[]> consumer) {
        this.redisTemplate.execute(new RedisCallback() {
        @Override
        public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
        try (Cursor<byte[]> cursor = redisConnection.scan( new ScanOptions.ScanOptionsBuilder().count(scanCount).match(keys+"*").build())) {
        cursor.forEachRemaining(consumer);
        } catch (IOException e) {
        e.printStackTrace();
        throw new RuntimeException(e);
        }
        return null;
        }
        });
        }

        写一个方法测试,实现思路是利用redisTemplate的execute执行scan扫描,将扫描的keys放入一个集合,再删除这些数据。

              @GetMapping(value = "/removeTestCache")
          public String removeTestCache(@RequestParam("prefixKey") String prefixKey,
          @RequestParam("scanCount") Long scanCount) {
          List<String> keyList = new ArrayList<>();
          //调用我们封装的方法
          this.scanKeys(prefixKey, scanCount, item -> {
          String key = new String(item, StandardCharsets.UTF_8);
          keyList.add(key);
          });
          Long result = this.redisTemplate.delete(keyList);
          return result + "条数据被删除";
          }


          测试


          输入完成的url,调用删除方法,我们这里传入2个参数,prefixKey是key的前缀,scanCount是每次扫描多少条数据,可以看到我们只需要调用一次,就可以把我们模糊的key都删除掉。很多人以为scanCount每次查询10条是不是需要多次调用,知道返回0,其实是不需要的哈。



          monitor


          我们只调用了一次,其实redis已经把所有的key扫描完了,看看redis为我们做了什么,可以再redis-cli命名输入monitor之后,再请求一次删除的命令,我这里拷贝了部分,其实就是为我们执行了多次scan命令,每次的count就是我们传入的10次,所有我们方法只需要调用一次就可以了

            127.0.0.1:6379> monitor
            OK
            1590071648.140816 [0 127.0.0.1:59965] "SCAN" "0" "MATCH" "com*" "COUNT" "10"
            1590071648.148479 [0 127.0.0.1:59965] "SCAN" "1280" "MATCH" "com*" "COUNT" "10"
            1590071648.149434 [0 127.0.0.1:59965] "SCAN" "640" "MATCH" "com*" "COUNT" "10"
            1590071648.150195 [0 127.0.0.1:59965] "SCAN" "3968" "MATCH" "com*" "COUNT" "10"
            1590071648.150849 [0 127.0.0.1:59965] "SCAN" "1856" "MATCH" "com*" "COUNT" "10"
            1590071648.151542 [0 127.0.0.1:59965] "SCAN" "544" "MATCH" "com*" "COUNT" "10"
            1590071648.152210 [0 127.0.0.1:59965] "SCAN" "2848" "MATCH" "com*" "COUNT" "10"
            1590071648.152874 [0 127.0.0.1:59965] "SCAN" "2400" "MATCH" "com*" "COUNT" "10"
            1590071648.153477 [0 127.0.0.1:59965] "SCAN" "2784" "MATCH" "com*" "COUNT" "10"
            1590071648.154229 [0 127.0.0.1:59965] "SCAN" "1552" "MATCH" "com*" "COUNT" "10"
            1590071648.154704 [0 127.0.0.1:59965] "SCAN" "3216" "MATCH" "com*" "COUNT" "10"
            1590071648.155499 [0 127.0.0.1:59965] "SCAN" "2128" "MATCH" "com*" "COUNT" "10"
            1590071648.156001 [0 127.0.0.1:59965] "SCAN" "208" "MATCH" "com*" "COUNT" "10"
            1590071648.156496 [0 127.0.0.1:59965] "SCAN" "3536" "MATCH" "com*" "COUNT" "10"
            1590071648.156971 [0 127.0.0.1:59965] "SCAN" "304" "MATCH" "com*" "COUNT" "10"


            文章转载自杨遥,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

            评论