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

相关推荐
skyshandianxia15 分钟前
Java面试八股之MySQL存储货币数据,用什么类型合适
mysql·面试·职场和发展
孤寒者28 分钟前
(三十一)Flask之wtforms库【剖析源码下篇】
后端·python·flask·源码剖析·wtforms
milong52132 分钟前
Flask自定义命令
后端·python·flask
xiongxinyu1035 分钟前
让一个元素水平垂直居中的方式
前端·javascript·css·面试
knighthood200135 分钟前
flask中解决图片不显示的问题(很细微的点)
后端·python·flask
小子爱吃鱼1 小时前
ERP的模块说明
后端·工厂方法模式
普通程序员A1 小时前
代码技巧专题 -- 使用策略模式编写HandleService
设计模式·面试·策略模式·代码优化·handle
Neituijunsir2 小时前
2024.06.28 校招 实习 内推 面经
c++·python·算法·面试·自动驾驶·汽车·求职招聘
不要飞升2 小时前
百日筑基第十一天-看看SpringBoot
java·spring boot·后端·实习
ytgytg282 小时前
SpringBoot返回应答为String类型时,默认带双引号(““),取消双引号的方法
java·spring boot·后端