RocketMQ 消费消息是推还是拉?
RocketMQ 是一个分布式消息中间件,广泛应用于高性能、高可靠性的消息传输场景。对于很多开发者来说,理解 RocketMQ 消费消息的机制是至关重要的。一个常见的问题是:RocketMQ 的消息消费是推(Push)还是拉(Pull)?
消费消息的基本模式
在消息中间件的设计中,消息消费通常有两种模式:
- 推模式(Push):消息中间件主动将消息推送给消费者。
- 拉模式(Pull):消费者主动从消息中间件拉取消息。
整理了一份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
这两种模式各有优缺点:
- 推模式的优点是消息可以立即送达消费者,缺点是如果消费者处理能力不足,可能会导致消息堆积或丢失。
- 拉模式的优点是消费者可以根据自身的处理能力主动控制消息的拉取速度,缺点是可能会增加延迟。
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 在消息消费方面具有很高的灵活性和性能。