LPUSH+BRPOP构成FIFO阻塞队列,兼容Redis 2.0+;但消费失败会导致消息丢失,适合允许少量丢失的场景,强可靠性需求应改用Stream。

直接用 LPUSH 入队、BRPOP 出队,就能构成一个可用的阻塞式消息队列。它不依赖额外模块,兼容所有 Redis 2.0+ 版本,但必须注意消费失败时的消息丢失风险。
为什么选 LPUSH + BRPOP 而不是 RPUSH + RPOP
组合方式决定队列语义:LPUSH 把新消息插到列表头,RPOP 从尾部取,天然形成 FIFO;若用 RPUSH + RPOP,新消息总在尾部追加、又从尾部弹出,实际变成 LIFO(栈),不符合队列预期。
-
LPUSH queue "msg1"→ 列表变为["msg1"] -
LPUSH queue "msg2"→ 列表变为["msg2", "msg1"] -
BRPOP queue 0→ 返回"msg1",下一次返回"msg2"
反过来,RPUSH + LPOP 也能实现 FIFO,但 LPOP 是非阻塞的,无法避免轮询;而 BRPOP 天然支持阻塞等待,更省资源。
BRPOP 的超时参数怎么设:0 还是正整数
BRPOP key timeout 中的 timeout 控制消费者空等多久。设为 0 表示无限等待,适合后台常驻消费者线程;设为正整数(如 30)则超时后返回 nil,适合需定期检查状态、做健康上报或优雅退出的场景。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 生产环境建议用
0:避免因短暂网络抖动或瞬时无消息导致消费者退出,引发消息积压 - 测试或调试时可用
5~10:方便观察日志、手动干预 - 不要设太小(如
1):频繁超时 + 重试 = 伪轮询,失去阻塞意义
消费失败时消息丢了怎么办
BRPOP 是原子性弹出,一旦返回消息,该消息就从列表中消失。如果业务处理中途崩溃(如 JVM Crash、OOM、网络中断),这条消息彻底丢失 —— 这是 List 队列最典型的可靠性短板。
- 临时补救:改用
BRPOPLPUSH source dest timeout,把消息先转移到备份列表dest,处理成功后再LREM dest 0 msg或LPOP dest清理 - 长期方案:不要强求 List 支持 ACK,该换
Stream类型(Redis 5.0+),它原生支持 consumer group 和 pending entries - 警惕
BRPOPLPUSH的副作用:若消费者重复拉取同一条消息(如处理幂等没做好),可能造成dest列表堆积
Spring Data Redis 中的典型误用点
很多人直接调用 redisTemplate.opsForList().rightPop(key),这实际发的是 RPOP 命令,非阻塞,必须自己加 Thread.sleep(),容易写成 busy-wait。
- 正确做法是用
redisTemplate.opsForList().rightPop(key, timeout, timeUnit),底层对应BRPOP - 注意 timeout 单位是
TimeUnit,不是秒;传0表示无限等待,但某些旧版客户端(如 Lettuce 4.x)对0解析异常,建议显式传Long.MAX_VALUE替代 - 别在 Web 请求线程里调
BRPOP:会卡住整个 HTTP 线程,应交由独立线程池或@Async执行
真正难的不是命令怎么写,而是想清楚「这条消息丢了能不能接受」—— List 队列只适合允许少量丢失、追求简单和低延迟的场景。一旦业务要求至少一次投递(at-least-once)或顺序+分组消费,就得跳出 List,直奔 Stream。

















