📚 实战篇 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,它不会立刻返回空,而是会让当前线程陷入阻塞(休眠)状态,直到队列中有新消息进入,或者达到了设置的最大超时时间,它才会被唤醒去拿数据。这完美契合了我们上一节用过的 JDKBlockingQueue.take()的逻辑!
三、 致命缺陷与面试核心考点
虽然基于 List 做消息队列极其简单,甚至连初学者都能在一分钟内写出代码,但它在严苛的生产环境中依然是不合格的。面试官一定会问你它的缺点在哪:
-
缺陷 1:不支持消息广播(一对多消费)。
一条消息只能被消费一次。一旦某个消费者执行了
BRPOP把消息拿走,这条消息就从 Redis 链表中彻底消失了。如果有其他的微服务(比如发短信的服务、发积分的服务)也想监听这个订单消息,它们是收不到的。 -
缺陷 2:极易丢失消息(最致命的硬伤)。
BRPOP拿走消息后,Redis 默认你已经处理完了,没有任何的确认应答(ACK)机制。试想一下:后台写库线程刚把订单信息从 Redis 里弹出来(Pop),放到了 JVM 内存里,还没来得及写进 MySQL 数据库,服务器突然停电宕机了!那么这条订单消息就永远地丢失了,客户付了钱却没有订单,直接引发 P0 级生产事故。
学习总结
基于 List 的消息队列,本质上就是利用 LPUSH 和 BRPOP 实现了跨进程的生产者-消费者模型。它比 JVM 本地的 BlockingQueue 进步了一点(数据存在 Redis 里,不占用 Tomcat 内存,且有一定的持久化能力),但依然无法保证消息的绝对安全和可靠消费。