Redis 实用小技巧——批量删除指定的 key

Mysql   2023-07-13 09:03   189   0  

日常工作当中经常会遇到删除 Redis key 的问题,如果是删除某个 key ,使用 DEL {keyname} 或者 EXPIRE {keyname} {ttl} 都可以实现。但如果想要一次性删除多个 key 应该怎么处理呢?你可能会想到直接使用 DEL key [key ...] 的方式来处理,但是当要删除的 key 有很多呢?或者我们事先并不能确认要删除的是哪些 key 呢?(比如通过搜索条件匹配出来的 key )

下面我们就来看看如何巧妙地处理这类问题。

场景一:删除所有的 key

如果需要执行初始化的操作,清理掉数据库所有的键,可以使用 FLUSHDB 或者 FLUSHALL 命令操作。

FLUSHDB:删除当前数据库中的所有 key 。FLUSHALL:删除当前连接所有数据库的所有 key 。

场景二:删除所有满足匹配条件的 key( key 数量较少或者测试环境)

可以在命令行环境下使用 redis-cli 命令在外部执行 KEYS {pattern} 命令,拿到结果以后通过 xargs 命令传递给 DEL 作为输入参数,进而删除匹配的 key 。具体命令如下:

redis-cli -h {hostname} -p {port} -a {password} -n {database} --raw keys "{pattern}" | xargs -I {} redis-cli -h {hostname} -p {port} -a {password} -n {database} DEL "{}"

但是这种操作是有限制的,主要受限于 KEYS 命令。因为 Redis 6 版本以下都是采用单线程处理请求,如果在 key 数量较大的情况下使用 KEYS 命令,会阻塞线程,导致其他客户端无法正常访问,这在生产环境是不可接受的(基于此原因,很多公司生产环境 KEYS 都是禁用的)。这就是接下来要说的第三种场景。

场景三:删除所有满足匹配条件的 key( key 数量较多或者生产环境)

为了解决场景二中的 KEYS 命令造成的线程阻塞问题,我们可以使用 SCAN 命令来解决。

让我们先来了解一下 SCAN 命令的使用。

SCAN 用于迭代当前数据库中的数据库键,用法如下:

SCAN cursor [MATCH pattern] [COUNT count]

简单概括一下: SCAN 命令就是通过游标的方式分步从数据库获取数据,每次以游标方式进行遍历(游标从上一次遍历结果中返回,初始游标为 0 ),结果会返回一个新游标和匹配的键集合(返回键的数量不不确定,小于等于 COUNT ),如果返回游标为 0 则视为遍历结束(不以遍历结果为空作为结束标识)。可以使用 MATCH 参数匹配模式,COUNT 参数限制返回的键的个数。

KEYS 命令相比,SCAN 命令虽然复杂度也是 O(n),但是它是通过游标分步进行的,不会阻塞线程。同时 redis-cli 命令本身支持 --scan--pattern 的参数,可以直接在命令行获取到匹配的结果。

修改后的命令如下:

redis-cli -h {hostname} -p {port} -a {password} -n {database} --raw --scan --pattern "{pattern}" | xargs -I {} redis-cli -h {hostname} -p {port} -a {password} -n {database} -L 100 DEL "{}"

注意这里并没有直接在 redis-cli 中使用 SCAN 命令,而是用 --scan 参数的方式调用,这是因为 SCAN 命令会返回两个参数(游标和结果),这不利于作为 xargs 的输入参数,而 --scan 参数只返回匹配的键,可以和 xargs 命令完美结合。而且可以给 xargs 指定输入参数的条数(-L),进一步限制每一次删除的键的个数(在没有指定 COUNT 参数情况下,默认值是 10 ,SCAN 每次会返回最多 10 个左右的数据,并非严格相等)。

到这里看似问题已经得到了解决,但是在实际场景中这种处理方式还是存在一些问题。

SCAN 命令虽然解决了线程阻塞的问题,但是也带来了效率的问题。假设数据库 key 的数量级在 10w+ 左右,需要删除的 key 数量级在 100+ 左右,这时候如果使用上述命令手动操作的话无疑是十分痛苦的,需要不断地重复执行( SCAN 命令并不是每次都可以返回匹配到的结果集,只要没有返回 0 游标,就需要继续遍历)。另外一次给 DEL 传递过多的参数也不是一种很好的选择,因为如果 key 比较大时,使用 DEL 删除本身也会造成线程阻塞,这样整个命令的阻塞时间就取决于 key 的数量和大小。

综上所述,主要有两个影响因素需要考虑:一个是如何在不阻塞线程的情况下,高效查询匹配的 key ;另一个是如何避免在执行删除操作的时候造成线程阻塞。

针对第一种情况,可以考虑通过脚本程序执行 SCAN 命令,这样就不必担心重复执行的效率问题了。针对 DEL 命令可能造成的阻塞问题,可以使用 EXPIRE 命令替换。以 PHP 语言为例,可以使用以下脚本进行处理:

use Predis\Client;$client = new Client();while (1) {list($iterator, $result) = $client->scan(0, ['MATCH' => 'PHP*', 'COUNT' => 50]);foreach ($result as $key) {$client->expire($key, mt_rand(0, 600));}if ($iterator == "0") {break;}}

当然,如果需要删除的 key 和 key 的总数数量级相差太大的话,使用 SCAN 命令遍历的效率还是差了些。这时可以借助 BGSAVE 生成 rdb 文件,然后再通过 rdb分析工具(rdbtools) 获取需要操作的 key ,借助程序进行过期处理。这个小技巧我们会在介绍「rdb文件」的应用场景的时候单独介绍。

你应该了解真相,真相会让你自由。

博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。
闲言碎语
老板说只要我们努力工作,明年他就可以换玛莎拉蒂了。
赞赏支持

如果觉得博客文章对您有帮助,异或土豪有钱任性,可以通过以下扫码向我捐助。也可以动动手指,帮我分享和传播。您的肯定,是我不懈努力的动力!感谢各位亲~