Redis LMOVE怎样实现可靠的任务转移队列
来源:17golang原创
时间:2026-11-04 09:24:51 489浏览 收藏
如果消费者直接使用 RPOP 取走任务,进程可能在“任务已经从队列删除、业务还没有处理完成”的窗口崩溃,任务就没有可恢复位置。更稳妥的做法是把 Redis 列表拆成“待处理”和“处理中”两个状态:消费者用 LMOVE 一次性把任务转入处理中列表,成功后再用 LREM 删除。
本文只围绕一个任务转移队列展开,给出 Redis 官方命令语义、FIFO 方向选择、阻塞领取、成功门禁和超时回收的组合方式。Redis 官方文档地址:https://redis.io/docs/latest/commands/lmove/
1. 先把队列拆成待处理和处理中
可靠队列至少需要两个列表键:
queue:pending:生产者刚写入、尚未被某个消费者领取的任务。queue:processing:已经交给消费者,但业务结果还没有确认成功的任务。
生产者用 LPUSH 从左侧写入,消费者用 LMOVE queue:pending queue:processing RIGHT LEFT 从右侧取最旧任务,再从处理中列表左侧放入。这样既保留 FIFO 顺序,也让任务在处理期间仍然留在 Redis 中。任务值最好使用全局唯一 ID,而不是一段无法区分重试次数的普通文本。

2. 用 LMOVE 原子领取任务
LMOVE source destination wherefrom whereto 会从源列表指定的一端弹出一个元素,并把它推入目标列表指定的一端;整个转移由 Redis 作为一个命令完成。它的时间复杂度是 O(1),从源列表不存在时返回空值,不会凭空创建处理中任务。
# 左侧入队、右侧取最旧任务,保证 FIFO 顺序
redis-cli LPUSH queue:pending task:1001 task:1002
# 原子地把最旧任务从待处理队列转入处理中队列
redis-cli LMOVE queue:pending queue:processing RIGHT LEFT
# 查看两个状态列表,便于由消费者决定下一步
redis-cli LRANGE queue:pending 0 -1
redis-cli LRANGE queue:processing 0 -1
上例中,task:1001 会先被领取。不要把 LEFT LEFT、RIGHT RIGHT 等方向随意替换:源列表取哪一端决定消费顺序,目标列表放哪一端决定处理中任务的排列方式,回收策略也要使用同一套约定。
3. 用 BLMOVE 减少空轮询
如果消费者持续执行非阻塞的 LMOVE,空队列时会形成大量无效请求。Redis 提供了阻塞版本 BLMOVE,参数顺序相同,只是在末尾增加超时时间;超时单位为秒,使用 0 表示一直等待。
# 队列为空时最多等待 5 秒,避免消费者持续空轮询
redis-cli BLMOVE queue:pending queue:processing RIGHT LEFT 5
# 返回空值时由应用层安排下一次等待或退出
# 这里的注释描述处理策略,不是 Redis 返回日志
阻塞领取只解决“没有任务时如何等待”,不负责确认业务是否成功。消费者拿到任务后仍要执行自己的超时、取消、重试和幂等逻辑;如果部署在 Redis Cluster 中,还应把两个列表键设计到同一 hash slot,避免多键命令遇到跨槽限制。
4. 用 LREM 设置成功门禁
领取成功不能等同于业务成功。建议把任务处理拆成三个明确阶段:领取、执行业务、确认完成。只有业务写入结果并且确认成功后,才从 queue:processing 删除任务。
LREM key 1 value 的 1 表示从列表中删除第一个匹配值。任务 ID 必须足够唯一,否则同一个值重复入队时,删除可能命中错误任务。对于扣库存、发通知、写订单等不可重复副作用,处理器还要使用任务 ID 做幂等键;LMOVE 能避免转移中丢失,但不会自动提供业务层的“只执行一次”。
5. 失败处理与超时回收
消费者如果在领取后崩溃,任务会留在 queue:processing,这正是可靠队列可以恢复的依据。列表本身只保存顺序,不适合直接判断每个任务领取了多久,因此应额外保存领取时间、重试次数和租约状态,例如使用哈希保存任务元数据,再由回收者周期性找出超时任务。
生产环境中不要只凭列表位置回收,而应先核对任务 ID、租约版本和当前状态;否则一个慢任务可能被误判为超时,造成两个消费者同时处理。超过最大重试次数的任务可以转入死信列表,并保留最后错误摘要供人工复盘。

6. 建立触发、权限和通知复盘回路
如果把 LMOVE 放进自动化工作流,可以按下面的回路组织消费者:
- 触发场景:生产者将带唯一 ID 的任务写入
queue:pending,消费者由 BLMOVE 等待。 - 权限配置:调用账号至少需要允许列表写操作;Redis 文档将 LMOVE 归入
@write、@listACL 分类,实际 ACL 仍按环境最小化授权。 - 流水线阶段:LMOVE 领取、执行业务、提交结果、LREM 清理,四个阶段分别记录耗时。
- 门禁规则:没有成功结果就不能删除 processing 中的任务;重试超过阈值就进入死信列表。
- 失败处理:回收器按租约和版本判断是否回队列,避免慢任务与回收任务并发产生重复副作用。
- 通知复盘:当 processing 最老任务年龄、重试次数或死信数量超过阈值时通知值班人员,并保留任务 ID 便于定位。
建议至少记录 LLEN queue:pending、LLEN queue:processing、最老 processing 任务年龄、每分钟领取数、成功删除数和回收数。只看 pending 长度不够:pending 下降而 processing 长时间不下降,通常说明消费者拿到了任务但没有完成确认。
常见问题
LMOVE 和 RPOPLPUSH 有什么区别?
在“从右侧取、向左侧放”的方向组合下,LMOVE 可以表达原来的 RPOPLPUSH 语义;新代码使用 LMOVE 更直观,也可以明确指定源端和目标端。
LMOVE 能保证任务只执行一次吗?
不能。它保证列表转移是原子的,但消费者在业务副作用完成前崩溃时,任务会被重新处理,所以业务处理器必须支持幂等。
什么时候用 BLMOVE?
消费者需要等待新任务、又不希望持续轮询空列表时使用 BLMOVE;如果需要同时等待多个队列,应根据客户端能力选择合适的阻塞列表命令。
为什么不能只保留一个列表?
单列表直接弹出后,消费者崩溃会留下无法定位的任务丢失窗口。单独的 processing 列表让回收者知道哪些任务已经领取但没有完成。
可靠的 Redis 任务转移队列不是把 LMOVE 当成“自动重试按钮”,而是把原子转移、处理中状态、成功删除、租约回收和幂等执行组合成一条可观测的工作流。
-
117 收藏
-
321 收藏
-
426 收藏
-
171 收藏
-
113 收藏
-
234 收藏
-
487 收藏
-
303 收藏
-
404 收藏
-
363 收藏
-
422 收藏
-
459 收藏
-
481 收藏
-
498 收藏
-
255 收藏
-
111 收藏
-
186 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习