今天有一个小功能点是关于批量删除redis指定的键。我以为几分钟既可以搞定的事情,没想到花了我一个钟的时间。

比如我redis存的键是:

{imei}l:topic
{imei}l:title
{imei}l:tag
{imei}l:url

它们都有共同的前缀:{imei}l

所以我一顿操作直接: delete {imei}l*

没想到redis直接给我返回个0,意思没有删除任何东西。

what?redis竟然不支持这样的模糊匹配删除。

非要我一个个删?倔强的我偏不。

于是我想到了,把这些带{imei}l前缀都先查询出来,然后再直接删除,我记得操作redis的客户端redisTemplate可以传可变参数的方法。

扫描redis的key,我想到了 keys 这个命令,但是被我否决了,keys * 这个命令千万别在生产环境乱用。特别是数据庞大的情况下,因为keys会引发Redis锁,并且增加Redis的CPU占用,很多公司的运维都是禁止了这个命令的。

当需要扫描key,匹配出自己需要的key时,可以使用 scan 命令。

代码如下:

private void deleteLongPortrait(String[] imeis) {
    List<String> list = new ArrayList<>();
    for (String imei : imeis) {
        String str = "{" + imei + "}l";
        list.add(str);
    }
    String join = String.join(",", list);
    Set<String> scan = redisUtil.scan(join);
    String[] array = scan.toArray(new String[scan.size()]);
    redisUtil.del(array);
}
public Set<String> scan(String matchKey) {
    Set<String> keys = stringRedisTemplate.execute((RedisCallback<Set<String>>) connection -> {
        Set<String> keysTmp = new HashSet<>();
        Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match("*" + matchKey + "*").count(1000).build());
        while (cursor.hasNext()) {
            keysTmp.add(new String(cursor.next()));
        }
        return keysTmp;
    });
    return keys;
}
public void del(String... key) {
    if (key != null && key.length > 0) {
        if (key.length == 1) {
            redisTemplate.delete(key[0]);
        } else {
            // 传入一个 Collection<String> 集合
            redisTemplate.delete(CollectionUtils.arrayToList(key));
        }
    }
}

主要业务:

  1. 前端传了imeis数组,然后对该imeis拼接成redis里存的键的格式。
  2. String.join(",", list),让该集合变成String字符串。
  3. scan操作,模糊匹配得到该模式下所有的key。
  4. scan.toArray(new String[scan.size()]),将set集合转换成String数组
  5. del(String… key),String数组作为可变参数传入del 方法签名。
  6. 成功删除该模式下所有的键。