如果一个消费者必须等到一批元素凑齐后再转移,Redis 8.10 的 BLMOVEM 可以用 EXACTLY 表达这个约束:源列表少于指定数量时继续阻塞,达到数量后一次性移动;如果只是“有数据就尽量取”,则使用 COUNT。关键不是把 timeout 写得很大,而是先选对批量条件。
官方地址:https://redis.io/docs/latest/commands/blmovem/
本文只讨论源列表到目标列表的阻塞式批量转移,不把普通单元素 BLMOVE 队列模式混进来。
EXACTLY 和 COUNT 解决的是两种不同的批处理语义
BLMOVEM source destination source-side destination-side timeout 的前半部分决定从哪里取、放到哪里;可选的数量块决定“凑多少才算满足”。
COUNT n:源列表只要不是空的就可以解除阻塞,最多移动n个,实际不足时允许少于n。EXACTLY n:源列表必须至少有n个,数量不够时继续等待;满足后恰好移动n个。
因此,订单批处理、固定大小的向量拼接、需要保持一批输入完整性的消费者适合 EXACTLY;日志汇聚、尽快清空队列或允许小批量降级时,COUNT 更合适。
先用最小命令看懂 EXACTLY 的等待条件
下面的例子从源列表右端取出,按批次顺序从目标列表左端放入。命令中的 0 表示无限等待;实际服务中通常应换成有限秒数。
# 准备两个元素;此时还不满足 EXACTLY 3 redis-cli DEL queue:ready queue:processing redis-cli RPUSH queue:ready job-1 job-2 # 源列表不足 3 个时继续阻塞,直到补齐或 timeout 到期 redis-cli BLMOVEM queue:ready queue:processing RIGHT LEFT 10 EXACTLY 3 BULK # 另一个连接补入第 3 个元素后,上面的阻塞调用才具备返回条件 redis-cli RPUSH queue:ready job-3
这里的 EXACTLY 3 是“解除阻塞的最低数量”,不是“最多读三条”。如果只想在有一条数据时立即拿走当前最多三条,应改成 COUNT 3。
COUNT 和 EXACTLY 怎么选
| 需求 | 参数 | 源列表不足目标数量时 | 适合场景 |
|---|---|---|---|
| 有数据就处理 | COUNT n | 拿到当前可用元素,最多 n 个 | 弹性消费、低延迟处理 |
| 必须凑满一批 | EXACTLY n | 继续阻塞,不提前返回 | 固定批量、批处理窗口 |
| 没有数据也不想无限挂起 | 任一参数 + 有限 timeout | 到期返回空结果 | 可取消的工作循环 |
四个方向参数决定数据最终的顺序
BLMOVEM 有两个方向:第一个是源列表从左端还是右端弹出,第二个是目标列表从左端还是右端推入。以 FIFO 队列为例,常见组合是 RIGHT LEFT:从源队尾取最早入队元素,再放到目标队头。
# FIFO:右端出、左端入;EXACTLY 4 要求一次凑够 4 个 redis-cli BLMOVEM queue:ready queue:processing RIGHT LEFT 5 EXACTLY 4 BULK # 栈式转移:左端出、右端入;元素相对顺序由 OBO/BULK 决定 redis-cli BLMOVEM stack:in stack:out LEFT RIGHT 5 COUNT 2 OBO
BULK 把一批元素作为整体转移并保持文档定义的相对顺序;OBO 按元素逐个移动。若目标列表的消费顺序很重要,方向和顺序选项必须一起写进接口契约,不能只凭“看起来像队列”来猜。
timeout 到期时不要把空结果当成成功批次
timeout 是最大阻塞秒数,写成 0 会无限等待。使用有限 timeout 时,调用可能因为源列表始终凑不够而返回空值;这表示本次没有完成转移,不应当提交一个“已处理 n 条”的业务批次。
# 最多等待 3 秒,凑不够 8 个就让调用返回 # 业务层应把空返回记为超时或稍后重试,而不是确认消费成功 redis-cli BLMOVEM queue:ready queue:processing RIGHT LEFT 3 EXACTLY 8 BULK
生产消费者通常还要为 timeout 配合取消、重试和幂等键。EXACTLY 只保证这次 Redis 列表转移的数量条件,不会替你完成业务数据库提交、重复消费保护或跨系统事务。
事务、脚本和集群环境要单独留意
BLMOVEM 的阻塞行为适合独立连接上的消费者。放进 MULTI/EXEC 或 Lua 脚本时,阻塞命令不会真正等待,而是按非阻塞的 LMOVEM 语义处理;如果你的方案依赖“等到足量再执行”,不要把它包进事务或脚本后再期待同样的等待效果。
它涉及源、目标两个 key,在 Redis Cluster 中还需要遵守多 key 操作的槽位约束。跨槽时不要只修改 timeout 或数量参数,应该先规划 hash tag 或改用能够落在同一槽位的 key 设计。
另外,BLMOVEM 是 Redis Open Source 8.10.0 起提供的能力。老版本服务器不认识该命令时,客户端会收到未知命令错误;上线前应以服务器版本和客户端命令支持情况为准,不要只看本地工具版本。
一张决策表记住最小用法
| 你要保证什么 | 推荐写法 | 需要解释的边界 |
|---|---|---|
| 凑够 n 个再移动 | EXACTLY n | 不足 n 个继续阻塞,超时返回空 |
| 有数据就最多移动 n 个 | COUNT n | 可能返回少于 n 个 |
| 固定 FIFO 方向 | RIGHT LEFT | 源端和目标端要与消费者约定一致 |
| 防止无限占用连接 | 有限 timeout | 超时后的重试与幂等由业务处理 |
常见问题
EXACTLY 需要源列表一开始就有足够元素吗?
不需要。只要调用仍在阻塞窗口内,其他客户端把元素追加到源列表后,数量达到要求就可以满足条件。
COUNT 3 会永远等到三条吗?
不会。COUNT 的含义是最多移动三条,源列表只要有可用元素就可能返回;必须凑满时要用 EXACTLY。
EXACTLY 超时后源列表会被部分删掉吗?
没有满足数量条件而超时时,本次转移不会因为“差一点”而完成部分移动。业务层仍应把空返回当作未完成处理,并设计下一次拉取策略。
总结一下:把 EXACTLY 当成“批次完整性”开关,把 COUNT 当成“最多取多少”的上限;再根据两端方向、BULK/OBO 和有限 timeout 写出完整契约,BLMOVEM 才适合放进稳定的批量消费流程。