Redis 实用小技巧——批量删除指定的 key
日常工作当中经常会遇到删除 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 "{}"
说明:
redis-cli
是访问 Redis 的客户端命令,用法是:redis-cli [OPTIONS] [cmd [arg [arg ...]]]
。hostname
:服务器主机,port
:服务器端口,a
:密码(无密码可缺省),n
:数据库编号。- 低版本的 Redis 会在返回结果中加上数字编号,使用
--raw
参数可以去掉结果编号。xargs -I {}
参数可以避免 key 中存在空格导致的参数拆分异常问题
但是这种操作是有限制的,主要受限于 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;
}
}
说明:
- 这里使用的是
predis/predis
composer 包,安装方式:composer require predis/predis
。- 不使用遍历返回的结果集作为 while 的判断条件是因为
SCAN
命令结束的标志是返回值为 0 的游标。- 使用
EXPIRE
设置过期的时候,过期时间采用了随机数的方式,是为了防止在删除 key 的数量过多时,同一时间集中过期引起雪崩现象。
当然,如果需要删除的 key 和 key 的总数数量级相差太大的话,使用 SCAN
命令遍历的效率还是差了些。这时可以借助 BGSAVE
生成 rdb
文件,然后再通过 rdb分析工具(rdbtools)
获取需要操作的 key ,借助程序进行过期处理。这个小技巧我们会在介绍「rdb文件」的应用场景的时候单独介绍。
本作品采用《CC 协议》,转载必须注明作者和本文链接
收藏 关注 点赞来一波
讲的还不错,很实用。
非常好 :+1:
:+1: :+1: :+1: :+1: :+1:
:+1: :+1: :+1:
很棒,最近一直在找这个解决办法。