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

RocketMQ 消费消息是推还是拉?

RocketMQ 是一个分布式消息中间件,广泛应用于高性能、高可靠性的消息传输场景。对于很多开发者来说,理解 RocketMQ 消费消息的机制是至关重要的。一个常见的问题是:RocketMQ 的消息消费是推(Push)还是拉(Pull)?

消费消息的基本模式

在消息中间件的设计中,消息消费通常有两种模式:

  1. 推模式(Push):消息中间件主动将消息推送给消费者。
  2. 拉模式(Pull):消费者主动从消息中间件拉取消息。

这两种模式各有优缺点:

  • 推模式的优点是消息可以立即送达消费者,缺点是如果消费者处理能力不足,可能会导致消息堆积或丢失。
  • 拉模式的优点是消费者可以根据自身的处理能力主动控制消息的拉取速度,缺点是可能会增加延迟。

RocketMQ 的消费机制

RocketMQ 的消息消费机制是一种混合模式,表面上看是推模式,但实际上是基于拉模式实现的

diff 复制代码
+-------------------+
| 1. 消费者初始化   |
+-------------------+
        |
        v
+-------------------+
| 创建消费者实例   |
| 设置 NameServer  |
| 订阅主题         |
| 注册监听器       |
+-------------------+
        |
        v
+-----------------------+
| 2. 内部拉取线程       |
+-----------------------+
        |
        v
+-----------------------+
| 启动多个拉取线程     |
| 每个线程运行         |
| PullMessageService   |
+-----------------------+
        |
        v
+-----------------------+
| 3. 消息拉取           |
+-----------------------+
        |
        v
+--------------------------+
| 从队列中取出 PullRequest |
+--------------------------+
        |
        v
+--------------------------+
| 调用 consumerImpl.pull  |
| Message 拉取消息         |
+--------------------------+
        |
        v
+--------------------------+
| 如果找到消息,将消息     |
| 放入 ConsumeMessageService |
| 队列                     |
+--------------------------+
        |
        v
+-----------------------+
| 4. 消息推送给监听器   |
+-----------------------+
        |
        v
+-------------------------+
| ConsumeMessageService  |
| 从队列中处理消息       |
+-------------------------+
        |
        v
+-----------------------------+
| 调用注册的 MessageListener |
| Concurrently               |
+-----------------------------+
        |
        v
+-----------------------+
| 监听器处理消息       |
+-----------------------+

1. 消费者启动并注册监听器

当消费者启动时,用户会创建 DefaultMQPushConsumer 实例,并注册一个 MessageListenerConcurrently 监听器。代码示例如下:

java 复制代码
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("PushConsumerGroup");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.subscribe("TopicTest", "*");

consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
        for (MessageExt msg : msgs) {
            System.out.printf("Receive message: %s%n", new String(msg.getBody()));
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

consumer.start();
System.out.printf("Consumer started.%n");

2. 内部拉取线程

消费者内部启动多个拉取线程,这些线程不断从 Broker 拉取消息。这个过程由 PullMessageService 服务实现:

java 复制代码
public class PullMessageService extends ServiceThread {
    private final BlockingQueue<PullRequest> pullRequestQueue = new LinkedBlockingQueue<>();
    private final DefaultMQPushConsumerImpl consumerImpl;

    @Override
    public void run() {
        while (!this.isStopped()) {
            try {
                PullRequest pullRequest = this.pullRequestQueue.take();
                this.pullMessage(pullRequest);
            } catch (InterruptedException e) {
                log.error("PullMessageService interrupted", e);
            }
        }
    }

    private void pullMessage(PullRequest pullRequest) {
        // 调用拉取消息的方法
        this.consumerImpl.pullMessage(pullRequest);
    }
}

3. 消息推送给监听器

拉取到的消息会被放入 ConsumeMessageService 的阻塞队列中,然后由消费者的 ConsumeMessageService 服务处理。ConsumeMessageService 会从阻塞队列中取出消息,并调用用户注册的监听器进行处理:

java 复制代码
public void pullMessage(PullRequest pullRequest) {
    // 拉取消息
    PullResult pullResult = this.pullKernelImpl.pull(pullRequest);
    switch (pullResult.getPullStatus()) {
        case FOUND:
            // 将拉取到的消息放入阻塞队列
            this.consumeMessageService.submitConsumeRequest(
                pullResult.getMsgFoundList(),
                pullRequest.getProcessQueue(),
                pullRequest.getMessageQueue(),
                false
            );
            break;
        // 其他状态处理
    }
}

ConsumeMessageService 会从阻塞队列中取出消息,并调用注册的监听器:

java 复制代码
public class ConsumeMessageConcurrentlyService extends ConsumeMessageService {
    @Override
    public void run() {
        while (!this.isStopped()) {
            try {
                // 从阻塞队列中取出消息
                ConsumeRequest consumeRequest = this.consumeRequestQueue.take();
                // 调用注册的监听器进行处理
                this.consumeMessage(consumeRequest);
            } catch (InterruptedException e) {
                log.error("ConsumeMessageService interrupted", e);
            }
        }
    }

    private void consumeMessage(ConsumeRequest consumeRequest) {
        List<MessageExt> msgs = consumeRequest.getMsgs();
        ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(consumeRequest.getMessageQueue());

        ConsumeConcurrentlyStatus status = this.messageListener.consumeMessage(msgs, context);
        // 根据返回的状态进行相应的处理
    }
}

总结

RocketMQ 的消费机制实际上是一种混合模式,内部采用拉取模式实现消息的获取,然后再将消息推送给用户注册的监听器处理。这种设计不仅保证了消息的及时处理,还简化了用户的使用体验。

所以,RocketMQ 消费消息表面上是推模式(Push),但实际上是基于拉模式(Pull)实现的。这种混合模式结合了两种模式的优点,使得 RocketMQ 在消息消费方面具有很高的灵活性和性能。

相关推荐
devlei4 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
Accerlator5 小时前
2026 年 4 月 1 日电话面试
面试·职场和发展
努力的小郑5 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
Victor3566 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3566 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁6 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp7 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
宁瑶琴8 小时前
COBOL语言的云计算
开发语言·后端·golang
普通网友8 小时前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算
IT_陈寒9 小时前
Vue的这个响应式问题,坑了我整整两小时
前端·人工智能·后端