Redis - 检查集合中多个值是否存在的替代方法

6

在我的应用程序中,我需要一组值,并且需要检查这些值中有多少存在于 Redis 中的集合中。

简单地说,我想要做的事情就是:

> Sadd myset field1
(integer) 1
> Sadd myset field2
(integer) 1
> Sadd myset field4
(integer) 1

> Sismember myset field1 field4 // which is not possible as of now.

由于我不能为SISMEMBER提供多个参数,我可能需要进行多次redis调用,这非常耗时。

我在考虑替代方案,如pipelining,但后来我想这将是一个很好的(hacky)实现方式:

> Hset myhash field1 "true"
(integer) 0
> Hset myhash field2 "true"
(integer) 0
> Hset myhash field4 "true"
(integer) 1

> Hmget myhash field1 field2 field3
1) "true"
2) "true"
3) (nil)

> Hmget myhash field1 field2 field3 field4
1) "true"
2) "true"
3) (nil)
4) "true"

Redis HMGET页面上是这样说的:

自2.0.0版本起可用。

时间复杂度:O(N),其中N是请求的字段数。

与多次调用SADD相比,这真的非常好,但我不确定我的理解是否正确,也不知道使用hmget这种方法是否有任何严重的缺点。

所以,我想了解使用hmget这种方法的缺点以及解决此问题的更好方法。

4个回答

2

这确实是一个有效的解决方案,但会有一定的浪费,因为您将需要维护一堆true值-即RAM开销。另外,Redis的Sets使用与Hashes相同的哈希表结构进行内部实现,所以您并不是离正确解太远 :)

虽然没有SISMEMBER的可变参数形式,但它很容易通过Lua脚本进行流程控制,因此您也可以考虑这种方法。例如像下面这样:

local r = {}

for _, m in pairs(ARGV) do
  r[#r+1] = redis.call('SISMEMBER', KEYS[1], m)
end

return r

谢谢您的回复,但我不能以编程方式完成这个任务,对吧?(实际上我想用Java完成) - Karthik
当然可以 - Redis的Lua脚本可以完美地从Java中调用。请阅读您客户端(Jedis?)的文档。 - Itamar Haber
是的,Jedis。实际上我正在使用Spring Data Redis。我会查看如何做到这一点。 - Karthik
谢谢 :) 我成功运行了lua脚本。这是我第一次听到lua这个名字,所以我不能完全理解你的脚本。我进行了一些搜索,并根据你的脚本编写了一个略微不同的脚本。请看一下并告诉我是否在所有情况下都有效。 - Karthik

1

这里有一种更简单的方法,使用Redisson框架在Java中实现:

RSet<String> set = redisson.getSet("mySet");

// uses lua-script under the hood
if (set.containsAll(Arrays.asList("obj1", "obj2", "obj3", "obj4"))) {
   // ...
}

1
从Redis 6.2开始,您可以使用SMISMEMBER命令检查给定集合中多个元素的存在性。它将以与请求顺序相同的方式返回元素的数组回复。您可以在客户端上获取数组的总和以获得集合中存在多少个这些值。
redis> SADD myset foo bar zet que
(integer) 4
redis> SMISMEMBER myset foo bar not
1) (integer) 1
2) (integer) 1
3) (integer) 0

1

在 @itamar 的回答后,我使用了lua脚本来完成这个任务。

我使用了以下脚本:

 local r = {}
 for i, m in pairs(KEYS) do
    r[i] = redis.call('SISMEMBER',ARGV[1],m)
 end
 return r

也许将来有人会用到这个,所以我写下了如何从java中调用这个脚本(Spring Data Redis 1.5.0jedis 2.6.2)。

    redisTemplate.opsForSet().add("mySet","3");
    redisTemplate.opsForSet().add("mySet","4");
    redisTemplate.opsForSet().add("mySet","35");
    redisTemplate.opsForSet().add("mySet", "6");
    List<String> list = new LinkedList<>();
    list.add("1"); 
    list.add("2");
    list.add("3");
    list.add("35");
    list.add("6");
    System.out.println(redisTemplate.execute(script, list,"mySet"));

打印以下内容:
   [0, 0, 1, 1, 1]

编辑 我不确定每个人都在说mySet是KEYS,list是ARGV,但在SPRING DATA REDIS中,execute函数定义如下:

public <T> T execute(RedisScript<T> script, List<K> keys, Object... args) {
    return this.scriptExecutor.execute(script, keys, args);
}

我不确定这对你是如何工作的 - 你正在KEYS上循环,但数组(mySet)中只有一个元素... - Itamar Haber
@ItamarHaber 的 execute 函数接受一个“键列表”和一些参数。这可能是特定于“Spring Data Redis”的,我没有直接尝试过“Jedis”。(mySet 是集合名称) - Karthik
这是正常的行为 - 我指的是你的Lua脚本在提供单个键时执行for循环的行为。 - Itamar Haber
1
你的 KEYS 和 ARGV 搞混了。mySet 是一个 KEY,而 list 包含了你的 ARGV。 - Ofir Luzon
是的,但是list不是Redis KEY,而mySet是。 它按照你编写的方式工作,但是你应该考虑在Redis键中使用KEYS和ARGV用于其他任何内容。 - Ofir Luzon
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接