必须用BRPOP/BLPOP替代RPOP/LPOP,因其阻塞特性避免空轮询;LPUSH+BRPOP构成FIFO队列;无ACK机制致崩溃丢消息;延时任务需ZSET而非List;超简单场景适用,复杂需求应选Stream或专业MQ。

能用,但必须配 BRPOP 或 BLPOP,否则就是轮询空转、浪费CPU又延迟高。
为什么不能直接用 RPOP/LPOP 做消费者?
因为 RPOP 和 LPOP 是“即查即返”型命令:队列空时立刻返回 nil,不会等。实际代码里如果写成 while(true) { $msg = $redis->rPop($queue); if ($msg) { handle($msg); } },就会变成高频空请求,Redis QPS 暴涨,而消息延迟反而不可控。
- 真实错误现象:
redis-cli中反复执行RPOP myq,看到大量(nil)返回,INFO commandstats里cmdstat_rpop调用量飙升 - 正确做法:改用阻塞式命令,让 Redis 主动挂起连接,直到有数据或超时
- 超时参数很关键:
BRPOP myq 0表示无限等待;BRPOP myq 30表示最多等 30 秒,超时返回null,便于做健康检查或优雅退出
LPUSH + BRPOP 是最稳的 FIFO 组合
生产者用 LPUSH 往列表左端推入,消费者用 BRPOP 从右端阻塞弹出,天然构成先进先出(FIFO)队列。注意别混用 RPUSH + BRPOP —— 虽然也能工作,但语义上是“尾进尾出”,容易和业务逻辑对不上。
- 命令顺序要匹配:
LPUSH queue "task1"→LPUSH queue "task2"→BRPOP queue 0返回"task1"(不是"task2") - 多消费者安全:多个客户端同时执行
BRPOP queue 0,Redis 内部保证同一时刻只有一个能拿到消息,无需额外加锁 - 持久化依赖 Redis 配置:只有开启
RDB或AOF,重启后队列内容才不丢;默认关闭时,消息纯内存存储
消费者进程崩溃了,消息会丢吗?
会丢。List 队列本身没有 ack 机制:BRPOP 一返回,消息就从列表里删掉了。如果消费者取到消息后还没处理完就崩了,这条消息彻底消失。
- 常见补救方式:消费前先
LRANGE queue -1 -1看一眼最新消息(不删),处理成功再RPOP;但并发下仍可能冲突 - 更可靠方案:改用
Stream结构(Redis 5.0+),支持XREADGROUP+XAck,但 List 就是轻量级场景,别强求它扛住金融级可靠性 - 底线建议:至少给消费者加上
try/catch,并在 catch 块里记录日志或发告警,别让失败静默发生
延时任务不能只靠 List,得换 ZSET
List 本身不带时间维度,没法原生支持“1 小时后执行”。硬要在 List 里塞时间戳然后轮询判断,等于自己实现调度器,性能差还容易漏。
- 正确姿势:用
ZADD delay_queue <unix_timestamp> "task_json"</unix_timestamp>把到期时间设为 score,再用ZRANGEBYSCORE delay_queue 0 <now> LIMIT 1</now>扫描到期任务 - 原子性陷阱:扫描 + 删除(
ZREM)两步操作在多进程下会重复消费,必须用 Lua 脚本封装成单次原子调用 - 别把延时队列和普通队列混用同一个 key,语义混乱且难以监控
List 队列的边界很清晰:它适合中小流量、允许少量丢失、追求简单上线的异步场景。一旦需要重试、死信、分组消费或精确延时,就该考虑 Stream 或专有 MQ 了——不是 List 不好,而是它压根没设计去干那些事。


















