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

相关推荐
布朗克1688 分钟前
38 Spring Boot入门——自动配置、核心注解与Starter机制
java·spring boot·后端
程序员老申12 分钟前
外呼突然全挂了,追查 24 分钟后我发现了 etcd 最阴的一颗雷
后端·程序员
何以解忧,唯有..13 分钟前
Go语言变量的声明方式详解
开发语言·后端·golang
长栎13 分钟前
MyBatis 缓存为啥总是失效?装饰器模式套娃的代价
后端
触底反弹14 分钟前
一文彻底搞懂 JavaScript 栈和队列(建议收藏)
javascript·算法·面试
bright_ye15 分钟前
setjmp & longjmp 深度详解 + 代码示例
后端
To_OC15 分钟前
我一直以为 Ajax 是个黑盒,直到我写了这 50 行代码
前端·后端·全栈
她的男孩17 分钟前
AI 自动化编写 SQL 脚本,更要守住 Flyway 版本管理的防线
人工智能·后端
卷无止境19 分钟前
Python的ABC库探索:能不能在系统设计之初就定义好所有抽象类?
后端
卷无止境21 分钟前
Python collections 库深度解析:那些被低估的数据结构利器
后端