找回密码
 立即注册
首页 业界区 业界 字节二面:Redis 能做消息队列吗?怎么实现? ...

字节二面:Redis 能做消息队列吗?怎么实现?

格恳绌 9 小时前
Java 面试 & 后端通用面试指南(Github 收获155+k Star,共有 600+ 位贡献者共同参与维护和完善):javaguide.cn。
先说结论:可以是可以,但要看具体场景。和专业的消息队列(如 Kafka、RabbitMQ)相比,还是有一些欠缺的地方。
正式开始介绍之前,我们先来看看:一个生产级 MQ 需要具备哪些核心能力?
能力维度定义关键指标/特征持久化消息写入后不因进程/节点故障丢失同步刷盘/多副本确认、RPO ≈ 0至少一次投递消息最终被消费,允许重复需配合消费者幂等性消费确认消费者显式告知处理成功ACK 机制、超时重试、死信队列消息重试消费失败可自动重新投递退避策略、最大重试次数、死信转移消费者组多消费者协作消费,故障自动转移组内负载均衡、分区分配、Rebalance消息堆积能力生产速率 > 消费速率时的缓冲能力磁盘存储、TTL、堆积告警顺序保证消息按发送顺序被消费分区有序/全局有序、乱序惩罚可扩展性水平扩展以提升吞吐或容灾分片机制、无状态 Broker、动态扩缩容Redis 提供了多种实现 MQ 的方式,从早期的 List 到 Pub/Sub,再到 Redis 5.0 新增的 Stream 数据结构(基于有序链表实现,支持消费者组和 ACK 机制,可用于构建轻量级消息队列)。
第一阶段:早期用 List 数据结构

Redis 2.0 之前,如果想要使用 Redis 来做消息队列的话,只能通过 List 来实现。
通过 RPUSH/LPOP 或者 LPUSH/RPOP 即可实现简易版消息队列:
  1. # 生产者生产消息
  2. > RPUSH myList msg1 msg2
  3. (integer) 2
  4. > RPUSH myList msg3
  5. (integer) 3
  6. # 消费者消费消息
  7. > LPOP myList
  8. "msg1"
复制代码
不过,通过 RPUSH/LPOP 或者 LPUSH/RPOP 这样的方式存在性能问题,我们需要不断轮询去调用 RPOP 或 LPOP 来消费消息。当 List 为空时,大部分的轮询的请求都是无效请求,这种方式大量浪费了系统资源。
因此,Redis 还提供了 BLPOP、BRPOP 这种阻塞式读取的命令(带 B-Blocking 的都是阻塞式),并且还支持一个超时参数。如果 List 为空,Redis 服务端不会立刻返回结果,它会等待 List 中有新数据后再返回或者是等待最多一个超时时间后返回空。如果将超时时间设置为 0 时,即可无限等待,直到弹出消息
  1. # 超时时间为 10s
  2. # 如果有数据立刻返回,否则最多等待10秒
  3. > BRPOP myList 10
  4. null
复制代码
List 实现消息队列功能太简单,像消息确认机制等功能还需要我们自己实现。最致命的是,它不支持一个消息被多个消费者消费(广播),而且消息一旦被取出,就没有了,如果消费者处理失败,消息就永久丢失了。
第二阶段:引入 Pub/Sub(发布/订阅)模式

Redis 2.0 引入了发布订阅 (Pub/Sub) 功能,解决了 List 实现消息队列没有广播机制的问题。
1.png

Pub/Sub 中引入了一个概念叫 Channel(频道),发布订阅机制的实现就是基于这个 Channel 来做的。
Pub/Sub 涉及发布者(Publisher)和订阅者(Subscriber,也叫消费者)两个角色:

  • 发布者通过 PUBLISH 投递消息给指定 Channel。
  • 订阅者通过SUBSCRIBE订阅它关心的 Channel。并且,订阅者可以订阅一个或者多个 Channel。
也就是说,多个消费者可以订阅同一个 Channel,生产者向这个 Channel 发布消息,所有订阅者都能收到。
我们这里启动 3 个 Redis 客户端来简单演示一下:
2.png

Pub/Sub 既能单播又能广播,还支持 Channel 的简单正则匹配。
Pub/Sub 有一个致命的缺陷:它发后即忘,完全没有持久化和可靠性保证。 如果消息发布时,某个消费者不在线,或者网络抖动了一下,那这条消息对它来说就永远丢失了。此外,它也没有 ACK 机制,无法知道消费者是否成功处理,更别提消息堆积的问题了。所以,Pub/Sub 只适合做一些对可靠性要求极低的实时通知,绝对不能用于任何严肃的业务消息队列。
第三阶段:Redis 5.0 新增 Stream

Redis 5.0 新增了 Stream 数据结构。这是一个基于 Radix Tree(基数树)实现的有序消息日志,天然支持消费者组和 ACK 机制,可用于构建轻量级消息队列。
为什么要用 Radix Tree? 很多人好奇,为什么不继续用 List/LinkedList?

  • 内存极度压缩:Stream 的消息 ID(如 1625000000000-0)是高度有序且前缀高度重合的。Radix Tree 是一种压缩前缀树,它会将具有相同前缀的节点合并。而 List/LinkedList 每个元素都要完整的链表节点开销,并且无法利用 ID 的前缀重复特性来节省空间。
  • 高效检索:在处理数百万级消息堆积时,Radix Tree 能保持极高的查询效率,这也是 Stream 能支持大数据量范围查询(XRANGE)的底层底气。相比之下,List/LinkedList只能从头尾操作,无法高效按 ID 范围查询,执行 XRANGE 需要遍历整个列表。
它借鉴了 Kafka 等专业 MQ 的核心概念:

  • 消费者组(Consumer Groups):实现消息在多个消费者间的负载均衡,支持故障自动转移。
  • 持久化:可以通过 RDB 和 AOF 保证消息在 Redis 重启后不丢失(取决于 appendfsync 配置,everysec 模式下通常最多丢失 1 秒数据)。
  • ACK 机制:消费者处理完消息后,需要手动 XACK 确认,否则消息会保留在 Pending List 中。这保证了消息至少被成功消费一次。
  • 消息回溯与转移:支持 XRANGE 按时间范围回溯消息,以及 XCLAIM 将挂起的消息转移到其他消费者处理。
<blockquote>

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册