Redis LMOVE 命令详细教程
LMOVE 从一个列表的左端或右端弹出一个元素,原子地推入另一个列表的左端或右端,并返回被搬运的元素。它自 Redis 6.2.0 起提供,取代已弃用的 RPOPLPUSH(后者自 6.2.0 起标记弃用,新代码应全部迁移到 LMOVE)。四个方向组合让它既能做先进先出搬运,也能做列表旋转,是"处理中列表"可靠队列模式的核心命令。
资料合集:https://pan.quark.cn/s/10e98d308913、https://pan.quark.cn/s/f56bc69c5338
一、概览与语法
redis
LMOVE source destination LEFT|RIGHT LEFT|RIGHT
| 项目 | 说明 |
|---|---|
| 数据类型 | List |
| 支持版本 | Redis 6.2.0 起 |
| source | 被弹出元素的源列表 Key |
| destination | 接收元素的目标列表 Key |
| 方向参数 | 第一个 LEFT|RIGHT 决定从 source 的哪端弹出,第二个决定推入 destination 的哪端 |
| 返回值 | Bulk 字符串:被搬运的元素;source 不存在或为空时返回空值,不创建 source |
| 时间复杂度 | O(1) |
| ACL | @write、@list、@slow |
| 命令标记 | write、denyoom |
官方说明:原子地返回并移除存储在 source 的列表的第一个或最后一个元素(由第一个方向参数决定),并将该元素插入到存储在 destination 的列表的第一个或最后一个元素之前或之后(由第二个方向参数决定),例如 LMOVE a b LEFT RIGHT 从 a 的表头取出元素并推入 b 的表尾。$TRAE_REF
二、基础示例
以下命令在测试实例的 redis-cli 中执行。文中结果是预期说明,未实际连接 Redis 运行。示例沿用官方文档的数据与结果。
redis
DEL tutorial:{lmove}:src tutorial:{lmove}:dst
RPUSH tutorial:{lmove}:src "one" "two" "three"
LMOVE tutorial:{lmove}:src tutorial:{lmove}:dst RIGHT LEFT
LMOVE tutorial:{lmove}:src tutorial:{lmove}:dst LEFT RIGHT
LRANGE tutorial:{lmove}:src 0 -1
LRANGE tutorial:{lmove}:dst 0 -1
预期结果:RPUSH 返回 3;第一条 LMOVE 从 src 表尾弹出 "three" 并推入 dst 表头,返回 "three";第二条 LMOVE 从 src 表头弹出 "one" 并推入 dst 表尾,返回 "one";此时 src 只剩 "two",dst 依次为 "three"、"one"。两次搬运方向不同,目标列表中的元素顺序也随之不同,这正是四方向设计的意义。
三、四个方向组合详解
| source 方向 | destination 方向 | 语义 | 典型用途 |
|---|---|---|---|
| LEFT | LEFT | 从 source 表头取出,放到 destination 表头 | 整体搬运时元素逆序 |
| LEFT | RIGHT | 从 source 表头取出,放到 destination 表尾 | 按原顺序整体搬运 |
| RIGHT | LEFT | 从 source 表尾取出,放到 destination 表头 | RPOPLPUSH 等价语义,FIFO 可靠队列首选 |
| RIGHT | RIGHT | 从 source 表尾取出,放到 destination 表尾 | 后进先出的逆序搬运 |
重点理解 RIGHT LEFT 组合:生产者用 RPUSH 入队,消费者用 LMOVE queue backup RIGHT LEFT 出队到备份列表,元素从 queue 表尾离开、进入 backup 表头,backup 中已消费元素的顺序与消费顺序一致,整体表现为先进先出;其余三个方向的元素次序可对照上表类推。
四、同 Key 旋转(循环队列)
当 source 与 destination 是同一个 Key 时,LMOVE key key RIGHT LEFT 把表尾元素搬到表头,实现列表循环旋转,可用于轮询调度:
redis
DEL tutorial:{lmove}:dst
RPUSH tutorial:{lmove}:dst "a" "b" "c"
LMOVE tutorial:{lmove}:dst tutorial:{lmove}:dst RIGHT LEFT
LRANGE tutorial:{lmove}:dst 0 -1
预期结果:LMOVE 返回 "c",列表变为 "c"、"a"、"b",原表尾元素转到表头。每执行一次轮转一个元素,表头永远是"下一个该轮到的对象"。
五、可靠队列模式(处理中列表)
消费者直接 LPOP 弹出任务后如果崩溃,任务就丢了。可靠队列用两条列表解决:queue 存待处理任务,processing 存"已领取、处理中"的任务。消费者用 LMOVE 领取任务(弹出并转移到 processing),处理成功后用 LREM 从 processing 删除;消费者崩溃时任务仍留在 processing,可以被重新投递。
redis
DEL tutorial:{lmove}:queue tutorial:{lmove}:processing
RPUSH tutorial:{lmove}:queue "t1" "t2"
LMOVE tutorial:{lmove}:queue tutorial:{lmove}:processing LEFT RIGHT
LRANGE tutorial:{lmove}:processing 0 -1
LREM tutorial:{lmove}:processing 1 "t1"
LRANGE tutorial:{lmove}:processing 0 -1
预期结果:RPUSH 返回 2;LMOVE 返回 "t1",任务从 queue 进入 processing,queue 剩 "t2",processing 为 "t1";处理完成后 LREM 返回 1,processing 清空。若消费者在 LREM 之前崩溃,t1 仍在 processing 中,监控进程可扫描该列表并重新投递。
六、Key 不存在与类型语义
source 与 destination 的存在性语义不对称,是使用时的关键点:
- source 不存在:返回空值,且不会创建 source,可用 EXISTS 验证。
- destination 不存在:成功搬运时自动创建 destination 并推入元素。
- source 只剩一个元素:弹出后 source 被删除(不存在空列表 Key)。
redis
DEL tutorial:{lmove}:src tutorial:{lmove}:dst
LMOVE tutorial:{lmove}:src tutorial:{lmove}:dst LEFT RIGHT
EXISTS tutorial:{lmove}:src
RPUSH tutorial:{lmove}:src "one"
LMOVE tutorial:{lmove}:src tutorial:{lmove}:dst LEFT RIGHT
EXISTS tutorial:{lmove}:dst
预期结果:source 不存在时第一条 LMOVE 返回空值,EXISTS 返回 0;写入一个元素后,LMOVE 返回 "one",第二次 EXISTS 对 dst 返回 1,说明 destination 被自动创建。
七、边界情况与错误处理
| 场景 | 行为 |
|---|---|
| source 不存在或为空 | 返回空值,不创建 source |
| destination 不存在 | 自动创建并推入元素 |
| source 或 destination 是非列表类型 | 报 WRONGTYPE |
| 方向参数不是 LEFT/RIGHT | 报语法错误 |
| source 弹出最后一个元素 | source Key 被删除 |
| source 与 destination 相同 | 正常执行,表现为旋转 |
redis
SET tutorial:{lmove}:str text
LMOVE tutorial:{lmove}:str tutorial:{lmove}:dst LEFT RIGHT
LMOVE tutorial:{lmove}:src tutorial:{lmove}:dst UP LEFT
预期结果:SET 返回 OK;对 String 类型执行报 WRONGTYPE;方向参数写成 UP 报语法错误,只接受 LEFT 或 RIGHT。
八、与相近命令的区别
| 命令 | 与 LMOVE 的关系 |
|---|---|
| RPOPLPUSH | 固定 RIGHT 到 LEFT 的特例,自 6.2.0 起已弃用,建议迁移到 LMOVE |
| BLMOVE | 阻塞版 LMOVE,6.2.0 起,source 为空时阻塞等待 timeout 秒 |
| LMPOP | 可从多个 Key 中按序弹出,但只弹出返回,不转移到另一列表 |
| LPOP/RPOP | 只弹出并返回元素,不推入其他列表 |
| LREM | 按值删除元素,常与 LMOVE 搭配完成可靠队列的"处理完成"确认 |
迁移 RPOPLPUSH 时注意参数:RPOPLPUSH src dst 等价于 LMOVE src dst RIGHT LEFT。
九、客户端示例
前提为已安装 redis-py 并准备好本地测试实例。redis-py 的 lmove(first_list, second_list, src, dest) 返回被搬运的元素,source 为空时返回 None,方向参数通过 src 与 dest 关键字传入。
python
import redis
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
src = "tutorial:{lmove}:py"
dst = "tutorial:{lmove}:dst"
try:
r.delete(src, dst)
r.rpush(src, "one", "two", "three")
print(r.lmove(src, dst, src="RIGHT", dest="LEFT")) # three
print(r.lmove(src, dst, src="LEFT", dest="RIGHT")) # one
print(r.lrange(src, 0, -1)) # ['two']
print(r.lrange(dst, 0, -1)) # ['three', 'one']
print(r.lmove("no-such", dst, src="LEFT", dest="RIGHT")) # None
finally:
r.delete(src, dst)
r.close()
Java(Jedis)示例,需导入 redis.clients.jedis.args.ListDirection,lmove 返回 String,source 为空时返回 null:
java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.args.ListDirection;
try (Jedis jedis = new Jedis("localhost", 6379)) {
String src = "tutorial:{lmove}:java";
String dst = "tutorial:{lmove}:src";
jedis.del(src, dst);
jedis.rpush(src, "one", "two", "three");
System.out.println(jedis.lmove(src, dst, ListDirection.RIGHT, ListDirection.LEFT));
// three
System.out.println(jedis.lmove(src, dst, ListDirection.LEFT, ListDirection.RIGHT));
// one
System.out.println(jedis.lmove("no-such", dst, ListDirection.LEFT, ListDirection.RIGHT));
// null
jedis.del(src, dst);
}
十、原子性与集群注意事项
原子性:LMOVE 是单命令,弹出与推入不可分割,不存在元素"既不在 source 也不在 destination"或"同时存在于两边"的中间态;并发消费者各自拿到不同元素,这正是它适合做任务领取的原因。
集群与事务 :Redis Cluster 中 source 与 destination 必须落在同一哈希槽,否则报 CROSSSLOT,给两个 Key 使用相同哈希标签(如 tutorial:{lmove}:src 与 tutorial:{lmove}:dst)即可同槽;命令 O(1) 完成一次搬运,在 MULTI/EXEC 事务、Lua 脚本与管道中正常工作。
十一、典型业务场景
可靠任务队列:RPUSH 写入 queue,消费者 LMOVE 到 processing,处理成功后 LREM 确认,崩溃后可从 processing 重新投递,实现至少一次投递。
循环轮询调度:把服务实例、下游节点放进一个列表,用同 Key 旋转依次取出"下一个节点",实现简单的轮询负载均衡。
多队列之间搬运:把高优先级队列的任务 LMOVE 进统一工作队列,或在重试队列与主队列之间转移任务。
十二、练习、排错与总结
练习:用 tutorial:{lmove}:queue 与 tutorial:{lmove}:processing 完整走一遍可靠队列:RPUSH 写入 a b c;循环用 LMOVE ... LEFT RIGHT 领取并观察返回值;每"处理"完一个就用 LREM 从 processing 删除;故意对最后一个元素不做 LREM,模拟消费者崩溃,再用 LRANGE 查看 processing 中残留的待重投任务;最后对 tutorial:{lmove}:dst 连续执行三次同 Key 旋转,观察表头变化。
排错要点:报 WRONGTYPE 时用 TYPE 检查两个 Key 的类型;集群下报 CROSSSLOT 时确认 source 与 destination 是否带相同哈希标签;返回空值时区分 source 不存在与已弹空,用 EXISTS 判断;方向参数只能写 LEFT 或 RIGHT,写错报语法错误;从 RPOPLPUSH 迁移时核对参数顺序是否改为"源、目标、源方向、目标方向"。清理使用 DEL tutorial:{lmove}:src tutorial:{lmove}:dst tutorial:{lmove}:queue tutorial:{lmove}:processing tutorial:{lmove}:str tutorial:{lmove}:py tutorial:{lmove}:java。速记:6.2 起支持、替代 RPOPLPUSH、双端四方向、返回被搬运的元素、source 空返回空值、destination 自动创建、同 Key 即旋转、可靠队列用处理中列表、集群需同槽。