Redis 列表

Redis 列表简介

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

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

基本命令

  • LPUSH 向列表的头部添加一个新元素;RPUSH 向尾部添加。
  • LPOP 从列表的头部移除并返回一个元素;RPOP 做同样的事情,但是从列表的尾部。
  • LLEN 返回列表的长度。
  • LMOVE 原子地将元素从一个列表移动到另一个列表。
  • LRANGE 从列表中提取一系列元素。
  • LTRIM 将列表缩减到指定的元素范围。

阻塞命令

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

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

查看完整的列表命令系列

示例

  • Treat a list like a queue (first in, first out):

  • Treat a list like a stack (first in, last out):

  • Check the length of a list:

  • Atomically pop an element from one list and push to another:

  • To limit the length of a list you can call 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 ↑