RocketMQ 的消息是推模式,还是拉模式?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

  1. 概念辨析能力:面试官不仅仅是想知道 "推还是拉" 这个结论,更是想知道你是否清楚 Push 和 Pull 两种模式的本质区别、各自的优缺点,而不是只停留在表面。

  2. 原理理解深度:考察你是否了解 RocketMQ 的 DefaultMQPushConsumer 内部其实是通过 "长轮询拉取" 实现的,能否透过 API 的 "推" 表象看到底层 "拉" 的本质。

  3. 架构设计思维:面试官想看你是否理解为什么 RocketMQ 选择这种 "推拉结合" 的设计,以及这种设计如何平衡实时性和服务端压力。

核心答案

RocketMQ 本质上是拉模式(Pull),但提供了 Push 模式的 API 封装。

准确地说:DefaultMQPushConsumer 给开发者 "推" 的体验,但底层依然是消费者主动向 Broker 拉取消息,采用的是 "长轮询"(Long Polling) 机制。

维度Pull 模式Push 模式RocketMQ 实际方案
主动方Consumer 主动拉取Broker 主动推送Consumer 主动拉取
实时性低(依赖拉取间隔)高(消息立即可达)高(长轮询,接近实时)
服务端压力大(需维护推送连接)
流量控制Consumer 自己掌控Broker 需要限流Consumer 自己掌控
代表实现DefaultMQPullConsumer典型 Push 模型DefaultMQPushConsumer(底层 Pull)

深度解析

一、推模式 vs 拉模式对比

上图对比了 Push 和 Pull 两种消息投递模式的工作方式:

  • Push 模式:Broker 有消息就主动推给 Consumer,实时性高,但 Broker 要管理所有 Consumer 的推送状态,压力较大;当 Consumer 消费慢时,还可能导致消息在 Consumer 端堆积甚至撑爆内存。
  • Pull 模式:Consumer 主动向 Broker 拉取消息,流量由自己掌控,但如果没有消息就会频繁空轮询,浪费网络和 CPU 资源,实时性也不够。

二、RocketMQ 的长轮询机制

RocketMQ 聪明就聪明在:用 Pull 的方式,达到了接近 Push 的实时性。核心就是 "长轮询"(Long Polling)。

上图展示了 RocketMQ 长轮询的完整交互流程,整体分为以下几个步骤:

  • 步骤 ①:Consumer 向 Broker 发送 Pull 请求,询问是否有新消息。
  • Broker 内部判断:检查 ConsumeQueue 中是否有新消息。如果有,立即返回;如果没有,不会立刻返回空响应,而是将请求挂起(Hold),等待新消息写入或超时(默认 15 秒)。
  • 步骤 ②:一旦有新消息到来或超时,Broker 才返回响应给 Consumer。
  • 步骤 ③:Consumer 收到响应后,立刻发起下一次 Pull 请求,形成持续循环。

关键设计点

  • 长轮询超时时间由参数 longPollingTimeout 控制,默认 15 秒(Broker 端 longPollingEnable=true 开启)
  • 这个设计巧妙地解决了 Pull 模式 "空轮询浪费资源" 的问题——没有消息时,请求被挂起,不浪费带宽
  • 同时也解决了 Push 模式 "Broker 压力大" 的问题——Consumer 主动拉取,Broker 无需维护推送状态

三、源码层面的验证

从 RocketMQ 源码可以清晰看到这个机制:

// DefaultMQPushConsumer 看名字是 "Push",实际内部用 Pull 实现
// 核心拉取逻辑在 PullAPIWrapper 中

// PullRequest 是拉取请求的封装
public class PullRequest {
    private String consumerGroup;        // 消费者组
    private MessageQueue messageQueue;   // 要拉取的队列
    private long nextOffset;             // 下一个拉取偏移量
    private ProcessQueue processQueue;   // 本地消息缓存队列
}

// PullMessageService 是拉取消息的核心线程
// 它从 pullRequestQueue 阻塞队列中取拉取任务,不断循环
public class PullMessageService extends ServiceThread {
    @Override
    public void run() {
        while (!this.isStopped()) {
            PullRequest pullRequest = this.pullRequestQueue.take();
            // 拉取消息 — 本质是发送 Pull 请求给 Broker
            this.pullMessage(pullRequest);
        }
    }
}

整个过程:

  • PullMessageService 是一个后台线程,不断从 pullRequestQueue 中取出拉取请求
  • 每次拉取完成后,不管有没有消息,都会把新的 PullRequest 放回队列
  • 这就形成了一个 "拉取 → 处理 → 再拉取" 的循环,永不停止

四、为什么 RocketMQ 选择 Pull + 长轮询?

设计考量纯 Push纯 PullPull + 长轮询
实时性极高低(取决于轮询间隔)高(接近实时)
Broker 压力大(维护推送连接 + 状态)
流量控制Consumer 被动,易被压垮Consumer 主动,可控Consumer 主动,可控
空闲时开销大(频繁空轮询)小(请求被挂起)
适合大规模集群

总结一句话:RocketMQ 用 Pull + 长轮询的方式,把 Push 的实时性和 Pull 的可控性结合到了一起,是分布式消息队列中最成熟的设计方案。

五、常见误区

  • 误区一:用了 DefaultMQPushConsumer 就是 Push 模式。错!它只是对 Pull 的封装,让你用起来像 Push,底层依然是拉。
  • 误区二DefaultMQPullConsumer 已经被废弃了。在 RocketMQ 5.x 中确实不推荐直接使用 PullConsumer,推荐使用 DefaultMQPushConsumer 或新的 SimpleConsumer
  • 误区三:长轮询会阻塞 Consumer 线程。不会。Broker 端挂起的是网络请求,Consumer 端的拉取是异步的,不会阻塞业务线程。

面试高频追问

  1. 追问一:长轮询的超时时间是多少?可以调整吗?

    Broker 端默认 15 秒(longPollingTimeout),可以通过 Broker 配置 longPollingEnable 和超时时间来调整。

  2. 追问二:Kafka 是推还是拉?

    Kafka 也是 Pull 模式,但 Kafka 没有长轮询机制(消费者通过 poll() 轮询),实时性不如 RocketMQ 的长轮询方案。

  3. 追问三:RocketMQ 5.x 的 SimpleConsumer 是推还是拉?

    SimpleConsumer 本质上也是拉模式,提供了更灵活的消息拉取和确认机制,是 RocketMQ 5.x 推荐的新消费者 API。

常见面试变体

  • 变体一:DefaultMQPushConsumerDefaultMQPullConsumer 有什么区别?
  • 变体二:RocketMQ 是怎么实现 "推" 的效果的?
  • 变体三:RocketMQ 的长轮询机制是怎么工作的?

记忆口诀

"名推实拉,长轮询补"——名字叫 Push,实际是 Pull,靠长轮询弥补实时性。三句话记住:Consumer 主动拉、没消息先挂着、来了再返回。

总结

RocketMQ 本质是拉模式DefaultMQPushConsumer 只是对 Pull 的封装。其核心设计是 长轮询机制:Consumer 发起 Pull 请求,Broker 没有消息时不立即返回,而是挂起请求等待新消息,兼顾了 Push 的实时性和 Pull 的流量可控性。