RocketMQ 消息订阅Subscribe—— Push & Pull 模式

RocketMQ提供Push和Pull两种订阅模式。Push模式下,MQServer看似主动推送,实际是consumer轮询消费;Pull模式则由consumer主动拉取。Push模式适合快速消费,Pull模式允许按需消费,但存在消息延迟问题。长轮询Pull是两者的一种折衷方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RocketMQ 消息订阅Subscribe—— Push & Pull 模式

 

RocketMQ消息订阅的两种模式

RocketMQ消息订阅有两种模式,一种是Push模式(MQPushConsumer),即MQServer主动向消费端推送;另外一种是Pull模式(MQPullConsumer),即消费端在需要时,主动到MQServer拉取。

但在具体实现时,Push和Pull模式都是采用消费端主动拉取的方式,即consumer轮询从broker拉取消息。

区别是:

Push方式里,consumer把轮询过程封装了,并注册MessageListener监听器,取到消息后,唤醒MessageListener的consumeMessage()来消费,对用户而言,感觉消息是被推送过来的。

Pull方式里,取消息的过程需要用户自己写,首先通过打算消费的Topic拿到MessageQueue的集合,遍历MessageQueue集合,然后针对每个MessageQueue批量取消息,一次取完后,记录该队列下一次要取的开始offset,直到取完了,再换另一个MessageQueue。

 

Push && Pull 的使用场景

慢消费

慢消费无疑是Push模式最大的致命伤,如果消费者的速度比发送者的速度慢很多,势必造成消息在broker的堆积。假设这些消息都是有用的无法丢弃的,消息就要一直在broker端保存。当然这还不是最致命的,最致命的是broker给consumer推送一堆consumer无法处理的消息,consumer不是reject就是error,然后来回踢皮球。

反观Pull模式,consumer可以按需消费,不用担心自己处理不了的消息来骚扰自己,而broker堆积消息也会相对简单,无需记录每一个要发送消息的状态,只需要维护所有消息的队列和偏移量就可以了。所以对于慢消费,消息量有限且到来的速度不均匀的情况,pull模式比较合适。

 

消息延迟与忙等

这是Pull模式最大的短板。由于主动权在消费方,消费方无法准确地决定何时去拉取最新的消息。如果一次Pull取到消息了还可以继续去Pull,如果没有Pull取到则需要等待一段时间重新Pull。但等待多久就很难判定了。你可能会说,我可以有xx动态Pull拉取时间调整算法,但问题的本质在于,有没有消息到来这件事情决定权不在消费方。也许1分钟内连续来了1000条消息,然后半个小时没有新消息产生,可能你的算法算出下次最有可能到来的时间点是31分钟之后,或者60分钟之后,结果下条消息10分钟后到了,是不是很让人沮丧?

当然也不是说延迟就没有解决方案了,业界较成熟的做法是从短时间开始(不会对broker有太大负担),然后指数级增长等待。比如开始等5ms,然后10ms,然后20ms,然后40ms……直到有消息到来,然后再回到5ms。

即使这样,依然存在延迟问题:假设40ms到80ms之间的50ms消息到来,消息就延迟了30ms,而且对于半个小时来一次的消息,这些开销就是白白浪费的。

在RocketMQ里,有一种优化的做法——长轮询 Pull ,来平衡推拉模型各自的缺点。基本思路是:消费者如果尝试拉取失败,不是直接return,而是把连接挂在那里wait,服务端如果有新的消息到来,把连接notify起来,这也是不错的思路。但海量的长连接block对系统的开销还是不容小觑的,还是要合理的评估时间间隔,给wait加一个时间上限比较好。

长轮询 Pull 的原理类似于 :http://www.ibm.com/developerworks/cn/web/wa-lo-comet/

 

MQPullConsumer 的使用

PullConsumer.java

package com.rocketmq.demo.pull;

import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Created by xinxingegeya on 2017/6/14.
 */
public class PullConsumer {

    private static final Map<MessageQueue, Long> offsetTable = new HashMap<MessageQueue, Long>();

    public static void main(String[] args) throws MQClientException {
        DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("please_rename_unique_consumer_group_name_5");
        consumer.setNamesrvAddr("127.0.0.1:9876");
        consumer.start();
        Set<MessageQueue> mqs = consumer.fetchSubscribeMessageQueues("PullConsumerTopicTest");
        for (MessageQueue mq : mqs) {
            System.err.println("Consume from the queue: " + mq);
            SINGLE_MQ:
            while (true) {
                try {
                    PullResult pullResult = consumer.pullBlockIfNotFound(mq, null, getMessageQueueOffset(mq), 32);
                    System.out.println(pullResult);
                    putMessageQueueOffset(mq, pullResult.getNextBeginOffset());

                    switch (pullResult.getPullStatus()) {
                        case FOUND:
                            List<MessageExt> messageExtList = pullResult.getMsgFoundList();
                            for (MessageExt m : messageExtList) {
                                System.out.println(new String(m.getBody()));
                            }
                            break;
                        case NO_MATCHED_MSG:
                            break;
                        case NO_NEW_MSG:
                            break SINGLE_MQ;
                        case OFFSET_ILLEGAL:
                            break;
                        default:
                            break;
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        consumer.shutdown();
    }

    private static void putMessageQueueOffset(MessageQueue mq, long offset) {
        offsetTable.put(mq, offset);
    }

    private static long getMessageQueueOffset(MessageQueue mq) {
        Long offset = offsetTable.get(mq);
        if (offset != null)
            return offset;
        return 0;
    }
}

Producer.java

package com.rocketmq.demo.pull;

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;

/**
 * Created by xinxingegeya on 2017/6/14.
 */
public class Producer {

    public static void main(String[] args) throws MQClientException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_producer_group_name_5");

        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();
        for (int i = 0; i < 1000; i++) {
            try {
                // Create a message instance, specifying topic, tag and message body.
                Message msg = new Message(
                        "PullConsumerTopicTest" /* Topic */,
                        "TagA" /* Tag */,
                        ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
                );
                //Call send message to deliver message to one of brokers.
                SendResult sendResult = producer.send(msg);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
        // Shut down once the producer instance is not longer in use.
        producer.shutdown();
    }
}

参考:http://www.jianshu.com/p/453c6e7ff81c

https://yq.aliyun.com/articles/55627

http://www.cnblogs.com/maypattis/p/5689187.html

============END============

转载于:https://my.oschina.net/xinxingegeya/blog/956370

### RocketMQ PushPull 消息传递机制 #### Push 模式 Push 模式RocketMQ 提供的一种消费消息方式,在这种模式下,Broker 负责主动将消息推送到消费者(Consumer),这种方式具有较强的实时性[^1]。然而,由于 Broker 需要持续监控并及时推送新到达的消息给 Consumer,这可能会造成一定的系统资源消耗。 当采用 Push 方式时,只要消费者关联的消息队列中有新的消息到来,Broker 就会立即将这些消息推送给对应的 Consumer 实例。这样的设计使得应用程序可以更快地接收到最新的数据更新通知,从而提高了系统的响应速度和用户体验质量[^4]。 ```python from rocketmq.client import PushConsumer, MessageListenerConcurrently def consume_msg(msgs): for msg in msgs: print(f&quot;Received message: {msg.body.decode(&#39;utf-8&#39;)}&quot;) return True consumer = PushConsumer(&quot;example_group&quot;) consumer.set_name_server_address(&quot;localhost:9876&quot;) consumer.subscribe(&quot;TopicTest&quot;, &quot;*&quot;, MessageListenerConcurrently(consume_msg)) consumer.start() ``` #### Pull 模式 相比之下,Pull 模式的实时性较差一些,但它提供了更好的灵活性以及更低的资源占用率。在这种模式里, Consumers 可以按照自己的节奏定期查询服务器上的消息队列来获取未读取过的消息。这意味着开发者可以根据实际需求调整拉取频率,进而更好地控制整个系统的性能开销。 对于那些对延迟不太敏感的应用场景来说,使用 Pull 方法可能更加合适;而对于追求极致低延时的服务,则可以选择 Push 来获得更快速的通知服务。 ```python from rocketmq.client import PullConsumer consumer = PullConsumer(&quot;example_group&quot;) consumer.set_name_server_address(&quot;localhost:9876&quot;) consumer.subscribe(&quot;TopicTest&quot;) for i in range(5): # 假设只尝试拉取五次作为例子 result = consumer.pull_block_if_not_match_2pc(None, 32, 0) if not result.offset_msgs: continue for msg in result.offset_msgs: print(f&quot;Pulled message: {msg.body.decode(&#39;utf-8&#39;)}&quot;) consumer.shutdown() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值