Redis RPOPLPUSH 命令详细教程
RPOPLPUSH 原子地把源列表表尾(右侧)的元素弹出,并推入目标列表表头(左侧),同时返回被搬运的元素。它自 Redis 1.2.0 起提供,是早期实现"可靠队列"与"列表轮询"的核心命令。自 6.2.0 起 RPOPLPUSH 被标记为弃用,官方建议迁移到功能更完整的 LMOVE;新项目应直接使用 LMOVE,仅在兼容旧代码时保留 RPOPLPUSH。
资料合集:https://pan.quark.cn/s/10e98d308913、https://pan.quark.cn/s/f56bc69c5338
一、概览与语法
redis
RPOPLPUSH source destination
| 项目 | 说明 |
|---|---|
| 数据类型 | List |
| 支持版本 | Redis 1.2.0 起;6.2.0 起弃用,建议改用 LMOVE |
| source | 源列表 Key,从其表尾(右侧)弹出元素 |
| destination | 目标列表 Key,把元素推入其表头(左侧),不存在时自动创建 |
| 返回值 | Bulk 字符串,被弹出并推入的元素;source 不存在或为空时返回空值,不创建 source |
| 时间复杂度 | O(1) |
| ACL | @write、@list、@slow |
| 命令标记 | write、denyoom |
官方说明:原子地返回并移除存储于 source 的列表的最后一个元素,并把该元素推入存储于 destination 的列表的第一个位置。若 source 不存在或为空,则不执行任何操作并返回空值。$TRAE_REF
从方向上看,RPOPLPUSH source destination 完全等价于 LMOVE source destination RIGHT LEFT,两者只是名称与历史不同。
二、基础示例
以下命令在测试实例的 redis-cli 中执行。文中结果是预期说明,未实际连接 Redis 运行。示例沿用官方文档的数据与结果。
redis
DEL tutorial:{rpoplpush}:mylist tutorial:{rpoplpush}:myotherlist
RPUSH tutorial:{rpoplpush}:mylist "one"
RPUSH tutorial:{rpoplpush}:mylist "two"
RPUSH tutorial:{rpoplpush}:mylist "three"
RPOPLPUSH tutorial:{rpoplpush}:mylist tutorial:{rpoplpush}:myotherlist
LRANGE tutorial:{rpoplpush}:mylist 0 -1
LRANGE tutorial:{rpoplpush}:myotherlist 0 -1
预期结果:三条 RPUSH 分别返回 1、2、3;RPOPLPUSH 返回 "three"(mylist 的表尾元素被弹出);LRANGE 显示 mylist 只剩 "one"、"two",myotherlist 为 "three"。myotherlist 此前不存在,被自动创建。
三、与 LMOVE 的关系及弃用说明
| 对比项 | RPOPLPUSH | LMOVE |
|---|---|---|
| 提供版本 | 1.2.0 起 | 6.2.0 起 |
| 弹出端 | 固定 RIGHT(表尾) | 可选 LEFT/RIGHT |
| 推入端 | 固定 LEFT(表头) | 可选 LEFT/RIGHT |
| 当前状态 | 6.2.0 起弃用,仅保留兼容 | 官方推荐替代命令 |
| 等价关系 | 等于 LMOVE src dst RIGHT LEFT |
更通用 |
迁移方式很直接:把 RPOPLPUSH src dst 改写为 LMOVE src dst RIGHT LEFT,语义与返回值完全一致。若需要其他方向组合(如头取头放),则只能用 LMOVE。本文后续示例继续使用 RPOPLPUSH 以便对照旧代码,实际新代码请优先写 LMOVE。
四、同 Key 时的列表旋转
当 source 与 destination 是同一个 Key 时,RPOPLPUSH 把表尾元素搬到表头,实现列表的循环旋转:
redis
DEL tutorial:{rpoplpush}:rot
RPUSH tutorial:{rpoplpush}:rot "a" "b" "c"
RPOPLPUSH tutorial:{rpoplpush}:rot tutorial:{rpoplpush}:rot
LRANGE tutorial:{rpoplpush}:rot 0 -1
预期结果:RPUSH 返回 3;RPOPLPUSH 返回 "c";LRANGE 返回 "c"、"a"、"b"。表尾的 "c" 被移到表头,列表长度保持不变,这即是"循环列表"模式的实现方式,可用于轮询调度多个客户端依次取不同元素。
五、可靠队列模式
RPOPLPUSH 最重要的用途是构建比 LPOP/RPOP 更可靠的任务队列:消费者不直接丢弃元素,而是把待处理任务转移到一张"处理中"列表里;处理成功后再从该列表删除,处理失败或崩溃时任务仍留在处理中列表,可被重新投递。这是官方文档记载的经典用法之一。
redis
DEL tutorial:{rpoplpush}:queue tutorial:{rpoplpush}:processing
LPUSH tutorial:{rpoplpush}:queue "job1" "job2" "job3"
RPOPLPUSH tutorial:{rpoplpush}:queue tutorial:{rpoplpush}:processing
LREM tutorial:{rpoplpush}:processing 1 "job1"
LRANGE tutorial:{rpoplpush}:queue 0 -1
LRANGE tutorial:{rpoplpush}:processing 0 -1
预期结果:LPUSH 返回 3。LPUSH 按参数顺序把 job1、job2、job3 依次推到表头,列表变为 job3、job2、job1,最早入队的 "job1" 落在表尾;RPOPLPUSH 从表尾弹出 "job1" 并推入 processing 表头,返回 "job1";LREM 返回 1,表示 "job1" 处理完成后被移出 processing;最后 LRANGE 显示 queue 为 "job3"、"job2",processing 为空数组。若消费者在处理 "job1" 时崩溃,元素会一直留在 processing 列表,便于恢复时重新处理。
这里采用 LPUSH 入队 + RPOPLPUSH 出队,两者方向相反,构成先进先出的可靠队列。
需要注意的是,LPOP/RPOP 一旦弹出元素,元素就从 Redis 消失,消费者崩溃即永久丢失;RPOPLPUSH 的处理中列表模式正是为解决这一点而设计。若要阻塞等待,还存在 BRPOPLPUSH,但其同样已被 BLMOVE 取代。
六、返回值与空结果
| 情形 | 返回 |
|---|---|
| source 存在且有元素 | 被弹出并推入 destination 的元素(Bulk 字符串) |
| source 不存在 | 空值,且不创建 source,destination 也不受影响 |
| source 为空列表 | 不会出现(列表弹空即删 Key),表现为 source 不存在的情形 |
返回空值不代表出错,只表示当前没有可搬运的元素。业务消费端应据此进入休眠或退避,而不是重试报错。
redis
DEL tutorial:{rpoplpush}:empty tutorial:{rpoplpush}:dst
RPOPLPUSH tutorial:{rpoplpush}:empty tutorial:{rpoplpush}:dst
LRANGE tutorial:{rpoplpush}:dst 0 -1
预期结果:RPOPLPUSH 返回空值(source 不存在);LRANGE 返回空数组,确认 destination 并未被创建------只有真正搬运到元素时才会创建目标 Key。
七、边界情况与错误处理
| 场景 | 行为 |
|---|---|
| source 不存在或为空 | 返回空值,不创建 source |
| destination 不存在 | 自动创建并推入 |
| source 或 destination 类型不是 List | 报 WRONGTYPE |
| source 与 destination 相同 | 列表旋转,长度不变 |
| 缺少 source 或 destination 参数 | 报语法错误 |
| 集群中两 Key 不同槽 | 报 CROSSSLOT |
redis
SET tutorial:{rpoplpush}:str text
RPUSH tutorial:{rpoplpush}:mylist2 "x"
RPOPLPUSH tutorial:{rpoplpush}:str tutorial:{rpoplpush}:mylist2
预期结果:SET 返回 OK;RPUSH 返回 1;最后一条因 source 是 String 类型报 WRONGTYPE,类型检查先于任何搬运,mylist2 不受影响。
八、与相近命令的区别
| 命令 | 是否搬运到另一列表 | 方向 | 阻塞 | 状态 |
|---|---|---|---|---|
| RPOPLPUSH | 是 | 固定 RIGHT→LEFT | 否 | 6.2.0 起弃用 |
| BRPOPLPUSH | 是 | 固定 RIGHT→LEFT | 是(带 timeout) | 6.2.0 起弃用 |
| LMOVE | 是 | 四方向可选 | 否 | 官方推荐 |
| BLMOVE | 是 | 四方向可选 | 是 | 官方推荐 |
| RPOP/LPOP | 否 | 弹出后元素即消失 | 否 | 常用 |
| LREM | 否(按值删除) | --- | 否 | 常用 |
核心区别在于"是否转移到另一列表":RPOP/LPOP 弹出即丢弃,可靠性依赖消费者自身;RPOPLPUSH/LMOVE 把元素暂存到处理中列表,提供了失败重投递的可能。
九、客户端示例
前提为已安装 redis-py 并准备好本地测试实例。redis-py 的 rpoplpush(src, dst) 返回被搬运的元素或 None。
python
import redis
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
src = "tutorial:{rpoplpush}:py_src"
dst = "tutorial:{rpoplpush}:py_dst"
try:
r.delete(src, dst)
r.rpush(src, "one", "two", "three")
print(r.rpoplpush(src, dst)) # three
print(r.lrange(src, 0, -1)) # ['one', 'two']
print(r.lrange(dst, 0, -1)) # ['three']
r.delete(src)
print(r.rpoplpush(src, dst)) # None,source 不存在
finally:
r.delete(src, dst)
r.close()
Java(Jedis)示例,rpoplpush(src, dst) 返回 String,source 为空时返回 null:
java
try (Jedis jedis = new Jedis("localhost", 6379)) {
String src = "tutorial:{rpoplpush}:java_src";
String dst = "tutorial:{rpoplpush}:java_dst";
jedis.del(src, dst);
jedis.rpush(src, "one", "two", "three");
System.out.println(jedis.rpoplpush(src, dst)); // three
System.out.println(jedis.lrange(src, 0, -1)); // [one, two]
System.out.println(jedis.lrange(dst, 0, -1)); // [three]
jedis.del(src, dst);
}
十、原子性与集群注意事项
原子性:RPOPLPUSH 是单命令,"弹出加推入"不可分割,不存在元素已经离开 source 却还没进入 destination 的中间状态;多个消费者并发调用时,同一元素不会被两个消费者同时拿到,这正是可靠队列成立的基础。
集群语义 :在 Redis Cluster 中,source 与 destination 必须落在同一个哈希槽,否则报 CROSSSLOT。使用相同的哈希标签即可保证,例如 tutorial:{rpoplpush}:queue 与 tutorial:{rpoplpush}:processing 会落到同一槽。这是使用该命令时最容易忽略的限制。
其他:复杂度 O(1),与列表长度无关;在 MULTI/EXEC 事务、Lua 脚本与管道中正常工作;需要"没有元素就等待"时用 BLMOVE。
十一、典型业务场景
可靠任务队列:生产者 RPUSH 入队,消费者 RPOPLPUSH 把任务转移到 processing 列表,处理成功后 LREM 删除。异常宕机后,processing 列表里的残留任务可被人工或定时任务重新投放,避免任务丢失。
循环轮询调度 :把多个候选资源(如多个节点、多份配置)放入同一列表,用 RPOPLPUSH key key 每次取表尾并放回表头,实现无状态的轮流分配。
多队列串联搬运:把低优先级队列的元素批量搬运到高优先级队列,或在流量低谷时做队列整理。
消息多播:把一条消息依次推入多个消费者的私有列表,再结合 RPOPLPUSH 做各自的处理状态跟踪。
十二、练习、排错与总结
练习:用 tutorial:{rpoplpush}:ex_src 与 tutorial:{rpoplpush}:ex_dst 演练搬运流程:RPUSH 写入 a b c,执行一次 RPOPLPUSH 确认返回 "c" 且两列表内容符合预期;重复执行直到 source 弹空并返回空值,用 EXISTS 确认 source 已删除、destination 被创建;再对同一 Key 执行 RPOPLPUSH k k 观察旋转效果。
排错要点:返回空值时先用 EXISTS 确认 source 是否存在或已弹空;报 WRONGTYPE 时用 TYPE 检查两个 Key 的类型;集群中报 CROSSSLOT 时给两个 Key 加相同哈希标签;destination 意外为空说明搬运未发生,检查 source 是否真的有元素;发现元素丢失时排查消费者是否为 RPOP 直接弹出,建议迁移到处理中列表模式。清理使用 DEL tutorial:{rpoplpush}:mylist tutorial:{rpoplpush}:myotherlist tutorial:{rpoplpush}:rot tutorial:{rpoplpush}:queue tutorial:{rpoplpush}:processing tutorial:{rpoplpush}:empty tutorial:{rpoplpush}:dst tutorial:{rpoplpush}:str tutorial:{rpoplpush}:mylist2 tutorial:{rpoplpush}:py_src tutorial:{rpoplpush}:py_dst tutorial:{rpoplpush}:java_src tutorial:{rpoplpush}:java_dst tutorial:{rpoplpush}:ex_src tutorial:{rpoplpush}:ex_dst。速记:1.2 起支持、6.2 起弃用改用 LMOVE、表尾弹出推入目标表头、等价于 LMOVE RIGHT LEFT、返回被搬运的元素、source 空返回空值且不创建 source、destination 自动创建、同 Key 即旋转、可靠队列用处理中列表、集群必须同槽。