Redis列表/集合中的唯一值

17

我想列出redis中现有产品的清单,但首先我想检查产品名称是否已经存在(重复检查)。

我的清单目前可以接受重复项,所以:有人可以帮我展示如何在清单中添加唯一值吗?

7个回答

40

不要使用列表,使用集合。集合是存储唯一对象的容器。每个对象只能在集合中出现一次。在这里查看与集合相关的命令:http://redis.io/commands/#set

以下是使用 redis-cli 的示例(我们尝试两次添加“产品一”,但它只在产品列表中出现一次):

$ redis-cli
127.0.0.1:6379> sadd products "Product One"
(integer) 1
127.0.0.1:6379> sadd products "Product Two"
(integer) 1
127.0.0.1:6379> sadd products "Product Three"
(integer) 1
127.0.0.1:6379> sadd products "Product One"
(integer) 0
127.0.0.1:6379> smembers products
1) "Product Three"
2) "Product One"
3) "Product Two"
127.0.0.1:6379>

9
如果您需要有序的“列表”,请使用 Sorted Set :) - Itamar Haber
我需要订单。 - Yin

4

为什么不直接在调用Redis.lrem方法之前先判断一下是否存在该项呢?如果存在则删除,否则不做任何操作。可以像这样:

def push_item_to_the_list(LIST_KEY, item)
   Redis.lrem(LIST_KEY, 0, item)
   Redis.lpush(LIST_KEY, item)
end

2
我的物品位置是这样改变的。 - Yin

1

这是我(鲁莽的)解决Redis List唯一性的方案。(Ruby实现)

def push_item_to_the_list(LIST_KEY, item)
 insert_status = Redis.linsert(LIST_KEY, 'before', item, item)

 if insert_status == -1
   Redis.lpush(LIST_KEY, item)
 else
   Redis.lrem(LIST_KEY, 1, item)
 end
end

每当您想要将项目推送插入到列表中时,请检查LINSERT命令是否能够将此项放在相同的项目后面(这是我知道的唯一检查项是否已在Redis列表中的方法)。
如果LINSERT返回状态-1,则表示它无法在列表中找到该项 - 一切正常(现在您可以将其推送或插入)。
如果LINSERT返回其他值(在另一种情况下为列表的大小),则表示它已经能够找到该项并且已经能够插入另一个项目,紧随前一个项目。这意味着您有(至少一个)重复项。您现在可以删除其中之一。

0
我在寻找一个列表中的独特性时找到了这篇帖子,因为我想要刷新列表并保持队列顺序,同时避免重复项。 这是一个列表和Set/SortedSet的混合体,所以简单地进行转换是不够的,我需要解决列表中的独特性或Set中的FIFO问题。
我发现在列表中实现独特性非常简单,一旦我停止寻找Redis类型来处理这个问题。有几个人在评论中提到了这一点,所以我提供以下解决方案:
TLDR:使用现有的列表值来过滤掉重复的输入值
注意:这些示例是用Ruby编写的,但应该很容易应用到其他编程语言中。
命令行示例:
def items_to_queue
  ["1", "2", "3", "4", "5"]
end

redis = Redis.new
# build the queue
redis.rpush(:my_queue, items_to_queue)
# verify the values are as expected
redis.lrange(:my_queue, 0, -1) # => ["1", "2", "3", "4", "5"]
# remove some values
redis.lpop(:my_queue) # => "1"
redis.lpop(:my_queue) # => "2"

# While uniqueness is not a built in function of lists,
# we can use their values to filter the incoming values 
queued_values = redis.lrange(:my_queue, 0, -1) # => ["3", "4", "5"]
unqueued_values = items_to_queue - queued_values # => ["1", "2"]
# add the unqueued items to the queue
redis.rpush(:my_queue, unqueued_values)
# verify the values are as expected
redis.lrange(:my_queue, 0, -1) # => ["3", "4", "5", "1", "2"]

明确表达: 如果items_to_queue被定义为返回整数而不是字符串[1, 2, 3, 4, 5],上述代码将无法工作。由于Redis返回的值是字符串,过滤将无法起作用。在这种情况下,该代码的最终输出将是:["3", "4", "5", "1", "2", "3", "4", "5"]。如果你处理类型差异,请相应地处理。

对象示例(我的实现):

  def items_to_queue
    ["1", "2", "3", "4", "5"]
  end

  class RedisInstance
    cattr_accessor(*%i[redis])
    # to solve for a production error explained here: https://ogirginc.github.io/en/heroku-redis-ssl-error
    self.redis = Redis.new(ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE })
  end

  class RedisList < RedisInstance
    attr_accessor(*%i[key])

    def initialize(key)
      self.key = key
    end

    def add_values(values, distinct: false)
      values -= self.values if distinct

      rpush(key, values) if values.present? # prevent Redis::CommandError if values ends up empty / nil
    end

    def first_value
      lpop(key)
    end
    alias next_value first_value

    # not used in this example but here for consistency
    def last_value 
      rpop(key)
    end

    def values(min: 0, max: -1)
      lrange(key, min, max)
    end

    private

    delegate(*Redis::Commands::Lists.instance_methods, to: :redis)
  end

  list = RedisList.new(:my_queue) => #<Redisable::List @key=:my_queue>
  list.add_values(items_to_queue, distinct: true)
  list.values # => ["1", "2", "3", "4", "5"]
  list.next_value # => "1"
  list.next_value # => "2"
  list.add_values(items_to_queue, distinct: true)
  list.values # => ["3", "4", "5", "1", "2"]

0
我提出了一个选项,可以创建一个独特的列表,其中的元素不会失去它们的位置。这个选项比lpush、lrem、rpop更快。同时,它支持sadd、spop的功能。
PHP代码示例:
public function addJobs(array $jobs): int
{
    foreach ($jobs as $key => $job) {
        $hash = hash('md4', $job);
        if (0 === $this->client->hsetnx('QUEUE-HASHES', $hash, 1)) {
            unset($jobs[$key]);
        }
    }

    return $this->client->rpush('QUEUE', $jobs);
}

public function popJobs(int $count): array
{
    if ($count < 1) {
        throw new \InvalidArgumentException('Jobs count must be greater than zero');
    }
    $index = $count - 1;
    $jobs = $this->client->lrange('QUEUE', 0, $index);
    if (\count($jobs)) {
        $this->client->ltrim('QUEUE', $count, -1);
        $hashes = [];
        foreach ($jobs as $job) {
            $hashes[] = hash('md4', $job);
        }
        $this->client->hdel('QUEUE-HASHES', $hashes);
    }

    return $jobs;
}

0

如果您需要维护顺序和唯一性,可以使用排序集合

127.0.0.1:6379> zadd products 1 "Product One"
(integer) 1
127.0.0.1:6379> zadd products 2 "Product Two"
(integer) 1
127.0.0.1:6379> zadd products 3 "Product Tree"
(integer) 1
127.0.0.1:6379> zadd products 4 "Product Four"
(integer) 1
127.0.0.1:6379> zrange products 0 -1
1) "Product One"
2) "Product Two"
3) "Product Tree"
4) "Product Four"

3
在Redis中,有一种获取具有唯一项的队列的方法吗?就是一个排序集合,但我想从前面弹出一些内容,并继续弹出。 :-) - adrian

0
redis 5 ZPOPMIN https://redis.io/commands/zpopmin 从存储在键中的有序集合中删除并返回最低分数的最多计数成员。
当未指定时,计数的默认值为1。指定比排序集的基数更高的计数值不会产生错误。当返回多个元素时,具有最低分数的元素将首先出现,其次是具有更大分数的元素。

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