Redis 列表是一个有序的字符串序列,你可以从任一端推入或弹出。这种简单结构使其成为两种常见需求的自然选择:生产者添加工作、消费者取走的队列,以及最近项目的上限 feed。列表并非适用于所有排队问题(streams 能处理高要求场景),但对于简单的 FIFO 工作和“最新 N”feed,它们是快速、正确的工具。
这是 Redis 大师班的第 5 部分,承接 hashes。
推入与弹出
列表支持对两端的操作,左和右:
LPUSH queue:emails "job1" # 添加到头部(左)
RPUSH queue:emails "job2" # 添加到尾部(右)
LPOP queue:emails # 从头部移除并返回
RPOP queue:emails # 从尾部移除并返回
LLEN queue:emails # 长度
LRANGE queue:emails 0 -1 # 所有元素
Enter fullscreen mode Exit fullscreen mode
将一端的 push 与另一端的 pop 结合,就得到了队列。LPUSH 加 RPOP 是先入先出:最旧的项目先出来。LPUSH 加 LPOP 是后进先出,即栈。这些操作都是原子的,因此多个生产者和消费者可以安全地操作同一个列表。
一个简单的工作队列
基本的工作队列是生产者 LPUSH 工作、工作者 RPOP 取出:
# 生产者
LPUSH jobs:email '{"to":"[email protected]","template":"welcome"}'
# 工作者
RPOP jobs:email # 获取下一个任务,若为空则返回 nil
Enter fullscreen mode Exit fullscreen mode
普通 RPOP 的问题是空队列立即返回 nil,因此工作者必须循环轮询,消耗 CPU 并增加延迟。解决方案是阻塞变体:
BRPOP jobs:email 5 # 最多等待 5 秒获取任务,然后返回
Enter fullscreen mode Exit fullscreen mode
BRPOP 会阻塞直到有项目可用(或超时),因此工作者可以高效休眠,并在任务到达时立即唤醒。这将列表转变为真正的推送式工作队列,无需轮询。
可靠性差距
在使用列表作为任务队列前,需要了解一个诚实的限制。使用 RPOP 或 BRPOP 时,一旦工作者弹出任务,它就从列表中消失了。如果该工作者在处理过程中崩溃,任务将丢失,因为它既不在队列中,也未完成。普通列表提供至多一次传递,这对于可以丢弃的工作可以接受,但对于不能丢弃的工作则不合适。
经典的缓解方法是 LMOVE(或旧的 RPOPLPUSH),它以原子方式从队列中弹出并推入“处理中”列表:
LMOVE jobs:email jobs:processing RIGHT LEFT
Enter fullscreen mode Exit fullscreen mode
现在任务在工作者处理时位于 jobs:processing。成功时,工作者从那里移除它;崩溃时,它仍在处理列表中,可以恢复。这种可靠队列模式弥补了丢失的差距,但它是手动簿记。当你需要真正的传递保证、消费者组和确认时,Redis streams(系列后续内容)正是为此而构建的,通常是重要工作的更好选择。使用列表进行简单或尽力而为的队列,使用 streams 进行可靠的队列。
上限 feed:“最新 N”模式
列表的另一个重要用途是最近项目的有界列表:最近 10 条通知、最近活动、时间线。你在前面 LPUSH 新项目,然后将列表修剪到固定长度,使其永不无限增长:
LPUSH user:1:notifications "You have a new follower"
LTRIM user:1:notifications 0 9 # 仅保留最新的 10 条
Enter fullscreen mode Exit fullscreen mode
LTRIM 保留指定范围并丢弃其余部分。在前面推送并修剪到前 N 项,你将得到一个自我维护的“最新 N”列表,可以通过 LRANGE user:1:notifications 0 -1 廉价地读取。这是一个干净的模式,适用于活动 feed 和最近项目小部件,你只需要显示最新的少数项目。
列表何时适合或不适合作为队列
为保持选择清晰:
- 使用列表 进行简单的 FIFO/LIFO 队列、可以承受丢失的尽力而为的工作,以及上限的最近项目 feed。它最小且快速。
-
使用
LMOVE到处理列表,当你需要基本的崩溃恢复但不需要完整保证时。 - 使用 streams,当你需要可靠传递、确认、多个消费者组或重放时。列表并非为此而构建,强行使用会导致脆弱的自定义簿记。
列表是直接的有序序列结构:从任一端推入和弹出,阻塞等待工作,修剪以限制长度。对于简单队列和最近项目 feed,它们正是正确的选择,了解其可靠性限制会告诉你何时转向 streams。
接下来,我们将介绍集合:具有快速成员测试和在 Redis 内完成的集合代数的无序唯一值集合。
关键要点
- Redis 列表是一个有序序列,你可以从任一端推入和弹出;结合相反的两端可以得到 FIFO 队列。
- 使用
BRPOP(阻塞弹出),使工作者高效等待任务,而不是轮询空队列。 - 普通列表队列是至多一次:被工作者弹出的任务若随后崩溃则丢失。
-
LMOVE到处理列表添加基本崩溃恢复;对于真正的传递保证,请使用 streams。 -
LPUSH加LTRIM廉价地维护上限的“最新 N”feed,适合通知和最近活动。
常见问题
如何使用 Redis 列表构建队列?
生产者 LPUSH 项目到列表,工作者从另一端 RPOP(或 BRPOP)以实现 FIFO 顺序。使用阻塞 BRPOP,使工作者高效等待新任务,而不是轮询空列表。
使用列表作为任务队列的问题是什么?
普通 RPOP/BRPOP 立即移除任务,因此如果工作者在处理过程中崩溃,任务将丢失(至多一次传递)。使用 LMOVE 到处理列表进行恢复,或使用 Redis streams 获得真正的传递保证。
BRPOP 做什么?
它是一个阻塞弹出:它等待直到列表上有项目可用或超时,然后返回它。这使工作者可以休眠直到工作到达,而不是重复轮询,提供推送式队列。
如何在列表中仅保留最新的 N 项?
使用 LPUSH 推送新项目,然后运行 LTRIM key 0 N-1 仅保留最新的 N 项,丢弃其余。这廉价地维护自我上限的最近项目 feed。
我应该使用列表还是 stream 进行排队?
使用列表进行简单或尽力而为的队列和最近项目 feed。当你需要可靠传递、确认、消费者组或重放时,使用 stream,这些列表无法在不进行脆弱的手动簿记的情况下提供。
进一步阅读
本文最初发布于 amanksingh.com/blog/redis-lists-queues。
关于作者
Aman Kumar Singh 是印度诺伊达的一名团队负责人和高级软件工程师,撰写关于全栈工程、系统设计和使用 TypeScript、Next.js、NestJS、PostgreSQL 和 Redis 的生产 SaaS 的文章。
- 作品集: amanksingh.com
- GitHub: amansingh1501
- LinkedIn: amansingh1597
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.