Redis 之用 scan 模糊匹配 key

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 之用 scan 模糊匹配 key相关的知识,希望对你有一定的参考价值。

参考技术A 在 redis 实际使用中,会遇到一个问题:如何从海量的 key 中找出满足特定前缀的 key 列表来?

redis 提供了一个简单暴力的指令 keys 用来列出所有满足特定正则字符串规则的 key。

这个指令有致命的弊端,在实际环境中最好不要使用:

我们可以通过配置设置禁用这些命令,在 redis.conf 中,在 SECURITY 这一项中,我们新增以下命令:

另外,对于FLUSHALL命令,需要设置配置文件中appendonly no,否则服务器是无法启动。

Redis 为了解决这个问题,它在 2.8 版本中加入了 scan 。scan 相比 keys 具备有以下特点:

SCAN 命令及其相关的 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都用于增量地迭代(incrementally iterate)一集元素(a collection of elements):

scan 参数提供了三个参数,第一个是 cursor 整数值 ,第二个是 key 的正则模式 ,第三个是 遍历的 limit hint 。第一次遍历时,cursor 值为 0,然后将返回结果中第一个整数值作为下一次遍历的 cursor。一直遍历到返回的 cursor 值为 0 时结束。

从上面的过程可以看到虽然提供的 limit 是 1000,但是返回的结果只有 10 个左右。因为这个 limit 不是限定返回结果的数量,而是限定服务器单次遍历的字典槽位数量(约等于)。

在 Redis 中所有的 key 都存储在一个很大的字典中,这个字典的结构和 Java 中的 HashMap 一样,是一维数组 + 二维链表结构,第一维数组的大小总是 2^n(n>=0),扩容一次数组大小空间加倍,也就是 n++。

scan 指令返回的游标就是第一维数组的位置索引,我们将这个位置索引称为槽 (slot)。如果不考虑字典的扩容缩容,直接按数组下标挨个遍历就行了。limit 参数就表示需要遍历的槽位数,之所以返回的结果可能多可能少,是因为不是所有的槽位上都会挂接链表,有些槽位可能是空的,还有些槽位上挂接的链表上的元素可能会有多个。每一次遍历都会将 limit 数量的槽位上挂接的所有链表元素进行模式匹配过滤后,一次性返回给客户端。

scan 的遍历顺序非常特别。它不是从第一维数组的第 0 位一直遍历到末尾,而是采用了高位进位加法来遍历。之所以使用这样特殊的方式进行遍历,是考虑到字典的扩容和缩容时避免槽位的遍历重复和遗漏。

高位进位法从左边加,进位往右边移动,同普通加法正好相反。但是最终它们都会遍历所有的槽位并且没有重复。

Java 中的 HashMap 有扩容的概念,当 loadFactor 达到阈值时,需要重新分配一个新的 2 倍大小的数组,然后将所有的元素全部 rehash 挂到新的数组下面。rehash 就是将元素的 hash 值对数组长度进行取模运算,因为长度变了,所以每个元素挂接的槽位可能也发生了变化。又因为数组的长度是 2^n 次方,所以取模运算等价于位与操作。

这里的 7, 15, 31 称之为字典的 mask 值,mask 的作用就是保留 hash 值的低位,高位都被设置为 0。

接下来我们看看 rehash 前后元素槽位的变化。

假设当前的字典的数组长度由 8 位扩容到 16 位,那么 3 号槽位 011 将会被 rehash 到 3 号槽位和 11 号槽位,也就是说该槽位链表中大约有一半的元素还是 3 号槽位,其它的元素会放到 11 号槽位,11 这个数字的二进制是 1011,就是对 3 的二进制 011 增加了一个高位 1。

抽象一点说,假设开始槽位的二进制数是 xxx,那么该槽位中的元素将被 rehash 到 0xxx 和 1xxx(xxx+8) 中。 如果字典长度由 16 位扩容到 32 位,那么对于二进制槽位 xxxx 中的元素将被 rehash 到 0xxxx 和 1xxxx(xxxx+16) 中。

观察这张图,我们发现采用高位进位加法的遍历顺序,rehash 后的槽位在遍历顺序上是相邻的。

假设当前要即将遍历 110 这个位置 (橙色),那么扩容后,当前槽位上所有的元素对应的新槽位是 0110 和 1110(深绿色),也就是在槽位的二进制数增加一个高位 0 或 1。这时我们可以直接从 0110 这个槽位开始往后继续遍历,0110 槽位之前的所有槽位都是已经遍历过的,这样就可以避免扩容后对已经遍历过的槽位进行重复遍历。

再考虑缩容,假设当前即将遍历 110 这个位置 (橙色),那么缩容后,当前槽位所有的元素对应的新槽位是 10(深绿色),也就是去掉槽位二进制最高位。这时我们可以直接从 10 这个槽位继续往后遍历,10 槽位之前的所有槽位都是已经遍历过的,这样就可以避免缩容的重复遍历。不过缩容还是不太一样,它会对图中 010 这个槽位上的元素进行重复遍历,因为缩融后 10 槽位的元素是 010 和 110 上挂接的元素的融合。

Java 的 HashMap 在扩容时会一次性将旧数组下挂接的元素全部转移到新数组下面。如果 HashMap 中元素特别多,线程就会出现卡顿现象。Redis 为了解决这个问题,它采用渐进式 rehash。

它会同时保留旧数组和新数组,然后在定时任务中以及后续对 hash 的指令操作中渐渐地将旧数组中挂接的元素迁移到新数组上。这意味着要操作处于 rehash 中的字典,需要同时访问新旧两个数组结构。如果在旧数组下面找不到元素,还需要去新数组下面去寻找。

scan 也需要考虑这个问题,对与 rehash 中的字典,它需要同时扫描新旧槽位,然后将结果融合后返回给客户端。

Redis模糊匹配并删除Key

文章目录

1. 登录可以连接Redis的机器,安装Redis

详情请参见redis-cli连接

2. 执行以下命令,删除模糊匹配到的Key

./redis-cli -h [$Addr] -a [$Password] keys "[$Key]*" | xargs ./redis-cli -h [$Addr] -a [$Password] del

说明:

  • [$Addr]为Redis连接地址。
  • [$Password]为Redis登录密码。
  • [$Key]为想要删除Key的前缀。
  • 您还可以根据实际情况添加参数,例如-n指定库。

3. 案例

  • redis中没有直接根据正则表达式删除key的命令,只有del key1命令
  • 但是redis中有通过正则表达式获取key的命令:keys “正则表达式”
  • 可以借助于xargs命令实现批量删除key,把查出来的key值当做参数传给del
  • redis-cli keys “xxxx*” | xargs del
redis-cli -h 192.169.1.71 -p 7001 -a 123456 keys xxxx* | xargs -r -t -n1 redis-cli -h 192.169.1.71 -p 7001 -a 123456 del
  • xargs命令后需加上参数-r,不然当keys的数量为0时,就会报错 (error) ERR wrong number of arguments for ‘del’ command
  • xargs命令后需加上参数-n1,不然当集群情况keys的数量大于1时,可能会报错 (error) CROSSSLOT Keys in request don’t hash to the same slot
  • 不加-t也可以,加上-t会输出每次删除的内容,不加则不输出删除的内容,但还是会输出每次删除的key的数量

单机redis免密情况下,模糊匹配删除key

redis-cli keys "*_long" | xargs redis-cli del

Redis 模糊搜索

  • keys *   匹配数据库中所有 key
  • keys h?llo   ?为单个占位符
  • keys h*llo   *为多字符占位符
  • keys h[ae]llo   匹配 hallo 和 hello ,但不匹配 hillo;特殊符号用 \\ 隔开。

4. 相关文章

以上是关于Redis 之用 scan 模糊匹配 key的主要内容,如果未能解决你的问题,请参考以下文章

Redis命令:scan实现模糊查询

Redis模糊匹配并删除Key

Python Redis中Scan遇到问题

redis 模糊删除key

redis做缓存,怎么取出全部key

Redis 模糊查询删除操作