七、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。

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

第几次重试 与上次重试的间隔时间 第几次重试 与上次重试的间隔时间
1 10s 9 7min
2 30s 10 8min
3 1min 11 9min
4 2min 12 10min
5 3min 13 20min
6 4min 14 30min
7 5min 15 1h
8 6min 16 2h

顺序消费

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

二、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

消息的属性设置

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));

你可能感兴趣的:(RocketMQ,rocketmq,java,中间件)