七、RocketMQ的消费模式

一、Push模式消费消息

**优点:**及时性好

**缺点:**如果客户端没有做好流控,一旦服务端推送大量消息到客户端时,就会导致客户端消息堆积甚至崩溃。

主要关注点在订阅topic后,消费方式模式

  • 并发消费:多个线程同时进行消费,是乱序的
  • 顺序消费:多个线程在加锁的情况下进行消费,并行改串行

并发消费

@Test
public void pushDemo()throws Exception {
    DefaultMQPushConsumer pushConsumer = new DefaultMQPushConsumer("test_push_consumer");
    // 指定NameServer地址
    pushConsumer.setNamesrvAddr(RocketMQConfig.NAME_SERVER_ADDR);

    // 指定消费位置
    pushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);

    // 指定消费模式 默认为集群消费
    pushConsumer.setMessageModel(MessageModel.CLUSTERING);

    // 以topic的形式订阅消费
    pushConsumer.subscribe(RocketMQConfig.TEST_TOPIC, "TagA");

    // 主要关注对象—并发消费
    pushConsumer.registerMessageListener(new MessageListenerConcurrently() {
        @Override
        public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
            for (MessageExt msg : msgs) {
                String strA = msg.getProperties().get("a");

                System.out.println(String.format("body:%s,tag:%s,aValue:%s", new String(msg.getBody()), msg.getTags(), strA));
            }
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
    });

    pushConsumer.start();

    Thread.sleep(Integer.MAX_VALUE);
}

顺序消费

@Test
public void pushOrderDemo()throws Exception {
    DefaultMQPushConsumer pushConsumer = new DefaultMQPushConsumer("test_push_consumer");
    // 指定NameServer地址
    pushConsumer.setNamesrvAddr(RocketMQConfig.NAME_SERVER_ADDR);

    // 指定消费位置
    pushConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);

    // 指定消费模式 默认为集群消费
    pushConsumer.setMessageModel(MessageModel.CLUSTERING);

    // 以topic的形式订阅消费
    pushConsumer.subscribe(RocketMQConfig.TEST_TOPIC, "TagA");

    // 主要关注对象—顺序消费
    pushConsumer.registerMessageListener(new MessageListenerOrderly() {
        @Override
        public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
            for (MessageExt msg : msgs) {
                String strA = msg.getProperties().get("a");

                System.out.println(String.format("body:%s,tag:%s,aValue:%s", new String(msg.getBody()), msg.getTags(), strA));
            }
            return ConsumeOrderlyStatus.SUCCESS;
        }
    });

    pushConsumer.start();

    Thread.sleep(Integer.MAX_VALUE);
}

重试次数和重试间隔

当消费状态返回消费未成功的时候,就会进行重新消费,并发消费和顺序消费的重试方式是不一样的

如何配置

  • 最大重试次数:消息消费失败后,可被重复投递的最大次数。

    consumer.setMaxReconsumeTimes(10);
    
  • 重试间隔:消息消费失败后再次被投递给Consumer消费的间隔时间,只在顺序消费中起作用。

    consumer.setSuspendCurrentQueueTimeMillis(5000);
    

并发消费

并发消费消费失败后会将消费失败的消息重新投递回服务端,再等待服务端重新投递回来,在这期间会正常消费队列后面的消息

并发消费失败后并不是投递回原Topic,而是投递到一个特殊Topic,其命名为%RETRY%ConsumerGroupName,集群模式下并发消费每一个ConsumerGroup会对应一个特殊Topic,并会订阅该Topic。

并发消费的重试间隔不可以自定义配置,而是根据失败次数逐级递增

第几次重试与上次重试的间隔时间第几次重试与上次重试的间隔时间
110s97min
230s108min
31min119min
42min1210min
53min1320min
64min1430min
75min151h
86min162h

顺序消费

顺序消费会在本地重试直到最大重试次数,并不会将消息投递到服务端,这样做的目的是为了防止消息的乱序。消息的重试间隔也只在顺序消费中起作用

二、Pull模式消费消息

**优点:**客户端可以依据自己的消费能力进行消费

**缺点:**拉取的频率需要用户自己控制,拉取频繁容易造成服务端和客户端的压力,拉取间隔长又容易造成消费不及时

订阅消费

与push一样,可以通过指定tag标签,或者使用sql92来消费消息

@Test
public void pullDemo() throws Exception{
    DefaultLitePullConsumer pullConsumer = new DefaultLitePullConsumer("test_pull_consumer");

    pullConsumer.setNamesrvAddr(RocketMQConfig.NAME_SERVER_ADDR);

    pullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);

    pullConsumer.subscribe(RocketMQConfig.TEST_TOPIC,"*");

    // 指定每次拉取的条数
    pullConsumer.setPullBatchSize(20);

    pullConsumer.start();

    while (true) {
        // 拉取消息
        List<MessageExt> messageExtList = pullConsumer.poll();

        for (MessageExt messageExt : messageExtList) {
            String strA = messageExt.getProperties().get("a");
            System.out.println(String.format("body:%s,tag:%s,aValue:%s", new String(messageExt.getBody()), messageExt.getTags(), strA));
        }
    }
}

分配队列消费

  1. 拉取想要消费topic的队列
  2. 筛选想要消费的队列
  3. 分配队列
  4. 拉取消费
  5. 消费消息
  6. 提交消息,分配消息不会自动提交,订阅消息会自动提交
@Test
public void pullAssign() throws Exception{
    DefaultLitePullConsumer pullConsumer = new DefaultLitePullConsumer("test_pull_consumer");

    pullConsumer.setNamesrvAddr(RocketMQConfig.NAME_SERVER_ADDR);

    pullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
    pullConsumer.setPullBatchSize(20);
    pullConsumer.start();

    // 获取所有的队列
    Collection<MessageQueue> messageQueues = pullConsumer.fetchMessageQueues(RocketMQConfig.TEST_TOPIC);

    // 这里可以筛选出要消费的队列...

    // 消费所有队列
    pullConsumer.assign(messageQueues);

    while (true) {
        // 拉取消息
        List<MessageExt> messageExtList = pullConsumer.poll();

        for (MessageExt messageExt : messageExtList) {
            String strA = messageExt.getProperties().get("a");
            System.out.println(String.format("body:%s,tag:%s,aValue:%s", new String(messageExt.getBody()), messageExt.getTags(), strA));
        }
        pullConsumer.commitSync();
    }
}

订阅消息—Tags过滤和SQL92过滤

Tag过滤

  • 订阅所有标签

    consumer.subscribe("test", "*");
    
  • 订阅单个标签

    consumer.subscribe("test", "TagA");
    
  • 订阅多个标签

    consumer.subscribe("test", "TagA||TagB");
    

SQL92过滤

SQL92可以通过消息的属性和消息的标签进行过滤,消息的标签的key为TAGS

使用SQL92需要在服务端的配置文件中进行以下配置

enablePropertyFilter=true

消息的属性设置

Message message = new Message("test", "TagA", "hello world".getBytes());
message.putUserProperty("a", "1");

订阅标签为TagA并且属性a的值为1的消息

String bySQL = "(TAGS is not null and TAGS = 'TagA') and (a is not null and a = 1)";
pushConsumer.subscribe(RocketMQConfig.TEST_TOPIC, MessageSelector.bySql(bySQL));

消费端的一些公共配置

消费模式配置

  • MessageModel.CLUSTERING:集群模式
  • MessageModel.BROADCASTING:广播模式
pushConsumer.setMessageModel(MessageModel.CLUSTERING);

消费位置配置

  • ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET:从上次的位置开始消费
  • ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET:从起点的位置开始消费
  • ConsumeFromWhere.CONSUME_FROM_TIMESTAMP:从指定时间点开始消费
// 配置消费位置
pullConsumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);

// 如果是指定时间点开始消费,就需要设置时间点  ConsumeFromWhere.CONSUME_FROM_TIMESTAMP
// 时间点的模式 20231015161121   年月日时分秒
// pullConsumer.setConsumeTimestamp(UtilAll.timeMillisToHumanString3(System.currentTimeMillis() - 1800000L));
  • 15
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默默行路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值