Redis 列表

Redis 列表简介

Redis 列表是字符串值的链接列表。Redis 列表经常用于

  • 实现堆栈和队列。
  • 为后台工作系统构建队列管理。

基本命令

  • LPUSH 将新元素添加到列表的头部;RPUSH 添加到尾部。
  • LPOP 从列表的头部移除并返回一个元素;RPOP 从列表的尾部执行相同的操作。
  • LLEN 返回列表的长度。
  • LMOVE 原子地将元素从一个列表移动到另一个列表。
  • LRANGE 从列表中提取元素范围。
  • LTRIM 将列表缩减到指定的元素范围。

阻塞命令

列表支持几个阻塞命令。例如

  • BLPOP 从列表头部移除并返回一个元素。如果列表为空,则命令将阻塞,直到有元素可用或达到指定超时时间。
  • BLMOVE 原子地将元素从源列表移动到目标列表。如果源列表为空,则命令将阻塞,直到有新元素可用。

查看完整的列表命令系列

示例

  • 将列表视为队列(先进先出)

  • 将列表视为堆栈(先进后出)

  • 检查列表的长度

  • 原子地从一个列表弹出元素并推送到另一个列表

  • 要限制列表的长度,可以调用LTRIM

什么是列表?

为了解释列表数据类型,最好从一些理论开始,因为信息技术人员经常以不恰当的方式使用“列表”一词。例如,“Python 列表”并不像名称所暗示的那样(链表),而是数组(实际上,Ruby 中的相同数据类型被称为数组)。

从一个非常通用的角度来看,列表只是一系列有序的元素:10,20,1,2,3 是一个列表。但是,使用数组实现的列表的属性与使用链表实现的列表的属性有很大的不同。

Redis 列表是通过链表实现的。这意味着,即使列表中有数百万个元素,在列表头部或尾部添加新元素的操作也是以恒定时间执行的。使用LPUSH命令在具有十个元素的列表头部添加新元素的速度与在具有 1000 万个元素的列表头部添加元素的速度相同。

缺点是什么?在使用数组实现的列表中,按索引访问元素非常快(恒定时间索引访问),而在使用链表实现的列表中则不太快(操作需要的工作量与访问元素的索引成正比)。

Redis 列表使用链表实现,因为对于数据库系统来说,能够以非常快的速度向非常长的列表添加元素至关重要。另一个强大的优势,正如您将在稍后看到的那样,是 Redis 列表可以在恒定时间内以恒定长度获取。

当需要快速访问大型元素集合的中间部分时,可以使用另一种数据结构,称为有序集合。有序集合在有序集合教程页面中介绍。

Redis 列表的入门

LPUSH命令将新元素添加到列表的左侧(头部),而RPUSH命令将新元素添加到列表的右侧(尾部)。最后,LRANGE命令从列表中提取元素范围

请注意,LRANGE采用两个索引,即要返回的范围的第一个元素和最后一个元素。这两个索引都可以为负数,告诉 Redis 从末尾开始计数:因此 -1 是最后一个元素,-2 是列表的倒数第二个元素,等等。

如您所见,RPUSH将元素附加到列表的右侧,而最后的LPUSH将元素附加到左侧。

这两个命令都是可变参数命令,这意味着您可以自由地在一个调用中将多个元素推送到列表中

在 Redis 列表上定义的一个重要操作是弹出元素的能力。弹出元素是指同时从列表中检索元素和将其从列表中删除的操作。您可以从左侧和右侧弹出元素,类似于您可以将元素推送到列表的两侧。我们将添加三个元素并弹出三个元素,因此在这个命令序列结束时,列表将为空,并且没有更多元素可以弹出

Redis 返回一个 NULL 值,表示列表中没有元素。

列表的常见用例

列表对许多任务很有用,以下两个非常具有代表性的用例:

  • 记住用户在社交网络中发布的最新更新。
  • 进程之间的通信,使用生产者 - 消费者模式,其中生产者将项目推送到列表中,而消费者(通常是工作线程)消费这些项目并执行操作。Redis 具有特殊的列表命令,使这种用例更可靠且更高效。

例如,流行的 Ruby 库resquesidekiq在后台使用 Redis 列表来实现后台作业。

流行的 Twitter 社交网络将用户发布的最新推文存储在 Redis 列表中。

为了逐步描述一个常见的用例,假设您的主页显示了图片分享社交网络中发布的最新图片,并且您希望加快访问速度。

  • 每次用户发布新图片时,我们都会使用LPUSH将它的 ID 添加到列表中。
  • 当用户访问主页时,我们使用LRANGE 0 9来获取最近发布的 10 个项目。

有上限的列表

在许多用例中,我们只希望使用列表来存储最新项目,无论它们是什么:社交网络更新、日志或其他任何内容。

Redis 允许我们使用列表作为有上限的集合,只记住最新的 N 个项目,并使用LTRIM命令丢弃所有最旧的项目。

LTRIM命令类似于LRANGE,但它不是显示指定的元素范围,而是将此范围设置为新的列表值。给定范围之外的所有元素都将被删除。

例如,如果您在修理自行车列表的末尾添加自行车,但只想关注最久的那 3 个

上面的LTRIM命令告诉 Redis 只保留索引 0 到 2 的列表元素,其他所有元素都将被丢弃。这允许一个非常简单但有用的模式:一起执行列表推送操作和列表修剪操作,以便添加新元素并丢弃超过限制的元素。使用LTRIM与负数索引可以用来只保留 3 个最近添加的元素

上面的组合添加新元素并仅保留列表中最新的 3 个元素。使用LRANGE,您可以访问最上面的项目,而无需记住任何旧数据。

注意:虽然LRANGE从技术上讲是一个 O(N) 命令,但访问列表头部或尾部的较小范围是一个恒定时间操作。

列表上的阻塞操作

列表有一个特殊的功能,使其适合于实现队列,并且通常作为进程间通信系统的构建块:阻塞操作。

假设您想使用一个进程将项目推送到列表中,并使用另一个进程来对这些项目进行某种工作。这是通常的生产者/消费者设置,可以用以下简单方法实现

  • 要将项目推送到列表中,生产者调用LPUSH
  • 要从列表中提取/处理项目,消费者调用RPOP

但是,有时列表可能是空的,没有可处理的项目,因此RPOP只返回 NULL。在这种情况下,消费者被迫等待一段时间并使用RPOP重新尝试。这被称为轮询,在这种情况下不是一个好主意,因为它有几个缺点

  1. 迫使 Redis 和客户端处理无用的命令(列表为空时所有请求都不会完成实际工作,它们只会返回 NULL)。
  2. 增加了项目处理的延迟,因为工作线程在收到 NULL 后会等待一段时间。为了缩短延迟,我们可以减少调用RPOP之间的等待时间,这样会放大问题 1,即对 Redis 的无用调用次数更多。

因此,Redis 实现了名为BRPOPBLPOP的命令,它们分别是RPOPLPOP的版本,如果列表为空,它们能够阻塞:它们只有在列表中添加了新元素或达到用户指定的超时时间后才会返回给调用者。

这是一个BRPOP调用的示例,我们可以在工作线程中使用它

这意味着:“等待列表bikes:repairs中的元素,但如果 1 秒后没有元素可用,则返回”。

请注意,您可以使用 0 作为超时时间来无限期地等待元素,并且您还可以指定多个列表,而不仅仅是一个列表,以便同时等待多个列表,并在第一个列表收到元素时得到通知。

关于BRPOP需要注意的几点

  1. 客户端以有序的方式提供服务:第一个阻塞等待列表的客户端将在其他客户端推送元素时首先得到服务,依此类推。
  2. 返回值与RPOP不同:它是一个包含两个元素的数组,因为它还包含键的名称,因为BRPOPBLPOP能够阻塞等待来自多个列表的元素。
  3. 如果达到超时时间,则返回 NULL。

关于列表和阻塞操作,您还需要了解一些其他内容。我们建议您阅读有关以下内容的更多信息

  • 可以使用LMOVE构建更安全的队列或循环队列。
  • 还有一种阻塞变体的命令,称为BLMOVE

自动创建和删除键

到目前为止,在我们的示例中,我们从未在推送元素之前创建空列表,或者在列表不再包含元素时删除空列表。Redis 负责在列表为空时删除键,或者在键不存在且我们试图向其添加元素时创建空列表,例如,使用LPUSH

这并非列表独有,它适用于所有由多个元素组成的 Redis 数据类型 - 流、集合、有序集合和哈希。

基本上,我们可以用三条规则概括这种行为

  1. 当我们向聚合数据类型添加元素时,如果目标键不存在,则在添加元素之前会创建一个空聚合数据类型。
  2. 从聚合数据类型中移除元素时,如果值为空,则键会自动销毁。Stream 数据类型是此规则的唯一例外。
  3. 调用只读命令(例如 LLEN(返回列表的长度))或写入命令删除元素,使用空键,始终产生与键持有与命令期望找到的类型相同的空聚合类型相同的结果。

规则 1 的示例

但是,如果键存在,我们不能对错误的类型执行操作

规则 2 的示例

在所有元素都被弹出后,键就不再存在。

规则 3 的示例

限制

Redis 列表的最大长度为 2^32 - 1(4,294,967,295)个元素。

性能

访问列表头或尾部的列表操作为 O(1),这意味着它们非常高效。但是,操作列表中元素的命令通常为 O(n)。例如,LINDEXLINSERTLSET。在执行这些命令时,尤其是在操作大型列表时,请谨慎操作。

替代方案

当您需要存储和处理不确定的事件序列时,请考虑 Redis 流 作为列表的替代方案。

了解更多

RATE THIS PAGE
Back to top ↑