26. Redis 消息队列 - 基于 List 实现学习文档

5 阅读3分钟

📚 实战篇 26. Redis 消息队列 - 基于 List 实现学习文档

一、 核心原理:如何用 List 模拟队列?

Redis 的 List 数据结构底层是一个双向链表。

既然要把它当成消息队列(Queue)来使用,我们就必须严格保证**“先进先出(FIFO - First In First Out)”**的特性。

  • 实现思路: 只要约束数据的出入口方向一致即可。
  • 具体做法: 生产者统一从队列的左侧插入消息,而消费者统一从队列的右侧取出消息。

二、 关键命令的演进:从 POP 到 BPOP

如果让你来手写消费者的监听代码,你可能会遇到一个关于 CPU 性能的“坑”。

  • 基础版(会疯狂空耗 CPU):

    生产者使用 LPUSH key value 发送消息,消费者使用 RPOP key 获取消息。

    痛点: 如果队列里暂时没有订单消息,RPOP 会立刻返回 null。为了不错过新消息,消费者只能写一个死循环(while(true))不断去查。这会导致极其严重的 CPU 空转和资源浪费。

  • 进阶版(优雅的阻塞等待):

    为了解决 CPU 空转问题,Redis 提供了一系列以 B (Blocking) 开头的阻塞命令。在这里我们使用 BRPOP key timeout

    优势: 当队列为空时,消费者再去执行 BRPOP,它不会立刻返回空,而是会让当前线程陷入阻塞(休眠)状态,直到队列中有新消息进入,或者达到了设置的最大超时时间,它才会被唤醒去拿数据。这完美契合了我们上一节用过的 JDK BlockingQueue.take() 的逻辑!


三、 致命缺陷与面试核心考点

虽然基于 List 做消息队列极其简单,甚至连初学者都能在一分钟内写出代码,但它在严苛的生产环境中依然是不合格的。面试官一定会问你它的缺点在哪:

  • 缺陷 1:不支持消息广播(一对多消费)。

    一条消息只能被消费一次。一旦某个消费者执行了 BRPOP 把消息拿走,这条消息就从 Redis 链表中彻底消失了。如果有其他的微服务(比如发短信的服务、发积分的服务)也想监听这个订单消息,它们是收不到的。

  • 缺陷 2:极易丢失消息(最致命的硬伤)。

    BRPOP 拿走消息后,Redis 默认你已经处理完了,没有任何的确认应答(ACK)机制

    试想一下:后台写库线程刚把订单信息从 Redis 里弹出来(Pop),放到了 JVM 内存里,还没来得及写进 MySQL 数据库,服务器突然停电宕机了!那么这条订单消息就永远地丢失了,客户付了钱却没有订单,直接引发 P0 级生产事故。


学习总结

基于 List 的消息队列,本质上就是利用 LPUSHBRPOP 实现了跨进程的生产者-消费者模型。它比 JVM 本地的 BlockingQueue 进步了一点(数据存在 Redis 里,不占用 Tomcat 内存,且有一定的持久化能力),但依然无法保证消息的绝对安全和可靠消费。