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 在消息消费方面具有很高的灵活性和性能。

相关推荐
盖世英雄酱581367 小时前
Java 组长年终总结:靠 AI 提效 50%,25 年搞副业只赚 4k?
后端·程序员·trae
+VX:Fegn08958 小时前
计算机毕业设计|基于springboot + vue在线音乐播放系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
code bean8 小时前
Flask图片服务在不同网络接口下的路径解析问题及解决方案
后端·python·flask
+VX:Fegn08959 小时前
计算机毕业设计|基于springboot + vue律师咨询系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
努力的小郑9 小时前
2025年度总结:当我在 Cursor 里敲下 Tab 的那一刻,我知道时代变了
前端·后端·ai编程
颜淡慕潇10 小时前
深度解析官方 Spring Boot 稳定版本及 JDK 配套策略
java·后端·架构
Victor35610 小时前
Hibernate(28)Hibernate的级联操作是什么?
后端
Victor35610 小时前
Hibernate(27)Hibernate的查询策略是什么?
后端
飞鸟真人11 小时前
Redis面试常见问题详解
数据库·redis·面试
superman超哥11 小时前
Rust 内部可变性模式:突破借用规则的受控机制
开发语言·后端·rust·rust内部可变性·借用规则·受控机制