【问题标题】:how to get keys which does not match a particular pattern in redis?如何获取与redis中的特定模式不匹配的键?
【发布时间】:2015-07-08 16:02:47
【问题描述】:

在 Redis 中,keys user* 将打印所有以 user 开头的键。 例如:

keys user*
1) "user2"
2) "user1"

现在,我希望打印所有不以 user 开头的键。 我怎么能这样做?

【问题讨论】:

  • 你的用例是什么?
  • 我想删除与给定模式不匹配的键。我得到了用于删除与给定模式匹配的键的 lua 脚本。我不知道如何获取与特定模式不匹配的键。
  • 说我要存储带有日期的密钥,如 set 27_Apr_2015_result_1 "sachin" set 28_Apr_2015_result_2 "Dravid" set 29_Apr_2015_result_1 "David" 现在我不希望我的 redis 存储存储在 29 之前的所有内容。唯一的方法我能做的是获取与 29_Apr_2015* 不匹配的密钥。这是我的用例@Tim Cooper
  • @KarthikeyanGopall 你能告诉我们删除匹配模式的键的 Lua 脚本吗?将其改为相反的做法可能是一个很好的基础。
  • script="redis.call('del',unpack(redis.call('keys', '%s')))";当这个函数被调用时,%s 将被模式替换为@Pit

标签: redis


【解决方案1】:

重要提示:始终使用SCAN 而不是(邪恶KEYS

Redis 的模式匹配在功能上有所限制(请参阅 util.c 中的 stringmatchlen 的实现),并且不提供您寻求 ATM 的内容。也就是说,请考虑以下可能的路线:

  1. 扩展 stringmatchlen 以满足您的要求,可能会将其作为 PR 提交。
  2. 考虑一下您要执行的操作 - 获取密钥子集总是效率低下,除非您为它们编制索引,请考虑改为跟踪所有非用户密钥的名称(例如,在 Redis 集中)。
  3. 如果您真的坚持要扫描整个键空间并匹配否定模式,实现这一目标的一种方法是使用一点 Lua 魔法。

考虑以下数据集和脚本:

127.0.0.1:6379> dbsize
(integer) 0
127.0.0.1:6379> set user:1 1
OK
127.0.0.1:6379> set use:the:force luke
OK
127.0.0.1:6379> set non:user a
OK

Lua(另存为scanregex.lua):

local re = ARGV[1]
local nt = ARGV[2]

local cur = 0
local rep = {}
local tmp

if not re then
  re = ".*"
end

repeat
  tmp = redis.call("SCAN", cur, "MATCH", "*")
  cur = tonumber(tmp[1])
  if tmp[2] then
    for k, v in pairs(tmp[2]) do
      local fi = v:find(re) 
      if (fi and not nt) or (not fi and nt) then
        rep[#rep+1] = v
      end
    end
  end
until cur == 0
return rep

输出 - 第一次正则匹配,第二次补码:

foo@bar:~$ redis-cli --eval scanregex.lua , "^user"
1) "user:1"
foo@bar:~$ redis-cli --eval scanregex.lua , "^user" 1
1) "use:the:force"
2) "non:user"

【讨论】:

  • 但我想要一个原子操作,这就是我选择键而不是扫描的原因。我认为可能有某种方法可以获得与键命令模式不匹配的键。现在才知道这是不可能的。感谢您的解释@Itamar Haber
  • Lua 脚本是原子的
  • 如果要搜索固定字符串,可以使用ARGV[1]:gsub('(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])', '%%%1')转义特殊字符。
【解决方案2】:

@Karthikeyan Gopall 你在上面的评论中指出了这一点,这为我节省了很多时间。谢谢!

您可以通过以下方式以各种组合使用它来获得您想要的东西:

redis.domain.com:6379[1]> set "hello" "foo"
OK
redis.domain.com:6379[1]> set "hillo" "bar"
OK
redis.domain.com:6379[1]> set "user" "baz"
OK
redis.domain.com:6379[1]> set "zillo" "bash"
OK
redis.domain.com:6379[1]> scan 0
1) "0"
2) 1) "zillo"
   2) "hello"
   3) "user"
   4) "hillo"
redis.domain.com:6379[1]> scan 0 match "[^u]*"
1) "0"
2) 1) "zillo"
   2) "hello"
   3) "hillo"
redis.domain.com:6379[1]> scan 0 match "[^u^z]*"
1) "0"
2) 1) "hello"
   2) "hillo"
redis.domain.com:6379[1]> scan 0 match "h[^i]*"
1) "0"
2) 1) "hello"

【讨论】:

    【解决方案3】:

    根据redis keys documentation该命令支持glob样式模式,不是正则表达式

    如果您查看文档,您会看到“!”与正则表达式相反,字符并不特殊。

    这是我在自己的数据库中运行的一个简单测试:

    redis 127.0.0.1:6379> set a 0
    OK
    redis 127.0.0.1:6379> set b 1
    OK
    redis 127.0.0.1:6379> keys *
    1) "a"
    2) "b"
    redis 127.0.0.1:6379> keys !a   
    (empty list or set)                       // I expected "b" here
    redis 127.0.0.1:6379> keys !b
    (empty list or set)                       // I expected "a" here
    redis 127.0.0.1:6379> keys [!b]
    1) "b"
    redis 127.0.0.1:6379> keys [b]
    1) "b"
    redis 127.0.0.1:6379> keys [ab]
    1) "a"
    2) "b"
    redis 127.0.0.1:6379> keys ![b]
    (empty list or set)
    

    所以我只是不认为您试图通过 keys 命令实现的目标。

    此外,keys 命令不太适合生产环境,因为它会锁定整个 redis 数据库。

    我建议使用 scan 命令获取所有密钥,将它们存储在本地,然后使用 LUA 删除它们

    【讨论】:

    • 我猜这是我留下的唯一选项 :( 想到可能有其他方法来解决这个问题。但是你可以尝试用 ^ 而不是 ! 来做这个。这只适用于一个角色而不是整个字符串。这实际上是主要问题。例如:你有像你好,你好,你好这样的键。如果我执行键 [^he]* 它应该显示你好和你好,但它什么也不显示,因为它需要仅对第一个字符进行否定。
    【解决方案4】:

    这是使用本机 redis 命令实现此目的的技巧(不需要 Lua 脚本或任何东西)。

    如果您能够控制插入新密钥的时间(您想要保留的密钥,删除问题中的所有其他内容),您可以:

    1. 在设置新密钥之前,将所有现有密钥(按模式或所有内容)设置为立即过期 (see how)
    2. 加载新密钥

    Redis 会自动删除所有旧密钥,而您只剩下所需的新密钥。

    【讨论】:

      猜你喜欢
      • 2013-10-24
      • 2023-03-21
      • 2019-12-08
      • 2021-05-26
      • 2015-07-08
      • 2015-11-05
      • 2019-05-11
      • 1970-01-01
      • 2019-05-28
      相关资源
      最近更新 更多