RocketMQ学习笔记(三)

一、前文回顾

在上一篇文章中使用Springboot整合了RocketMQ,并实现了最简单的消费者和生产者,今天继续学习RocketMQ中其他类型的消息。

二、延时消息

顾名思义延时消息就是生产者生产消息后,消费者过一段时间才消费对应的消息。使用场景例如,定时任务、订单超时取消等等。
注意:在RocketMQ 4.X的版本是不支持自定义精度的延时,只支持18个级别,如下图:
image.png
但是到了RocketMQ5.0版本是可以支持自定义精度的延时。

1、创建一个类型为Delay的Topic

message.type可以使用 TRANSACTION, FIFO, DELAY 或NORMAL,如未指定该属性,默认是NORMAL。
./mqadmin updateTopic -a +message.type=DELAY -b 192.168.111.140:10911 -n 192.168.111.140:9876 -t TestDelay
(这里吐槽一下官方文档,并没有说明这一情况!!!!,最后还是百度到的https://blog.youkuaiyun.com/wwwwfw/article/details/127445987image.png

2、生产者

/**
* 发送延迟消息
*/
@GetMapping("/delay")
    public void sendDelayMessage() throws ClientException, IOException {
    //这里不直接使用rocketMQTemplate 是因为最新的rocketmq-spring-boot-starter 是2.2版本,其中Rocket MQ client版本为4.9.3
    //而本demo则使用的是Rocket 5.0
    String topic = "TestDelay";
    //获取客户端服务提供者(不是消息生产者)
    ClientServiceProvider provider = ClientServiceProvider.loadService();
    //定义延时时间(延时10s)
    long deliverTimeStamp = System.currentTimeMillis() + 10 * 1000;
    Producer producer = MyRocketMQUtil.getProducer(topic, provider);
    Message message = provider.newMessageBuilder().setTopic(topic)
        //设置消息索引键,可根据关键字精确查找某条消息。
        .setKeys("delayKey")
        //设置消息Tag,用于消费端根据指定Tag过滤消息。
        .setTag("delayTag")
        .setDeliveryTimestamp(deliverTimeStamp)
        .setBody("你好我发送的是延时消息".getBytes())
        .build();
    try {
        SendReceipt result = producer.send(message);
        log.info("消费发送时间:{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        log.info("消息ID:{}", result.getMessageId());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

结果:生产者生产消息的时间为 2022-11-28 13:33:40
image.png

3、消费者

    public static void main(String[] args) throws ClientException {
        final ClientServiceProvider provider = ClientServiceProvider.loadService();
        // 接入点地址,需要设置成Proxy的地址和端口列表,一般是xxx:8081;xxx:8081。
        String endpoints = "192.168.16.128:8081";
        ClientConfiguration clientConfiguration = ClientConfiguration.newBuilder().setEndpoints(endpoints).build();
        // 订阅消息的过滤规则,表示订阅所有Tag的消息。
        String tag = "*";
        FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG);
        // 为消费者指定所属的消费者分组,Group需要提前创建。
        String consumerGroup = "MyConsumerGroup";
        // 指定需要订阅哪个目标Topic,Topic需要提前创建。
        String topic = "TestDelay";
        // 初始化PushConsumer,需要绑定消费者分组ConsumerGroup、通信参数以及订阅关系。
        PushConsumer pushConsumer = provider.newPushConsumerBuilder().setClientConfiguration(clientConfiguration)
            // 设置消费者分组。
            .setConsumerGroup(consumerGroup)
            // 设置预绑定的订阅关系。
            .setSubscriptionExpressions(Collections.singletonMap(topic, filterExpression))
            // 设置消费监听器。
            .setMessageListener(messageView -> {
                // 处理消息并返回消费结果。
                // LOGGER.info("Consume message={}", messageView);
                System.out.println("Consume message!!,Time:"
                    + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                return ConsumeResult.SUCCESS;
            }).build();
        // 如果不需要再使用PushConsumer,可关闭该进程。
        // pushConsumer.close();
    }

结果:消费者收到消息的时间则是:2022-11-28 13:33:50,刚好延迟了10s
image.png

三、顺序消息

所谓顺序消息就和字面意思一样,消息是有顺序的,发送的时候是什么顺序,收到的时候也是什么顺序。
Apache RocketMQ 顺序消息的顺序关系通过消息组(MessageGroup)判定和识别,发送顺序消息时需要为每条消息设置归属的消息组,相同消息组的多条消息之间遵循先进先出的顺序关系,不同消息组、无消息组的消息之间不涉及顺序性。
基于消息组的顺序判定逻辑,支持按照业务逻辑做细粒度拆分,可以在满足业务局部顺序的前提下提高系统的并行度和吞吐能力。
那么如何保证消息的顺序行呢?这就要分为两个方面,首先是生产者端:

  • 单一生产者:消息生产的顺序性仅支持单一生产者,不同生产者分布在不同的系统,即使设置相同的消息组,不同生产者之间产生的消息也无法判定其先后顺序。
  • 串行发送:Apache RocketMQ 生产者客户端支持多线程安全访问,但如果生产者使用多线程并行发送,则不同线程间产生的消息将无法判定其先后顺序。

这也很好理解,如果多个生产者同时生产消息,无法保证那个生产者先生产了消息;其次如果使用多线程并行的生产消息,也无法保证那个线程先完成。
结论:(1)单一生产者 (2)生产者串行发送 (3)消息的顺序性只体现在同一个消息组内,不同组不存在顺序性。
说完生产者在看消费者端:
1、顺序投递:RocketMQ 通过客户端SDK和服务端通信协议保障消息按照服务端存储顺序投递,但业务方消费消息时需要严格按照接收—处理—应答的语义处理消息,避免因异步处理导致消息乱序。
这里要注意一下:在RocketMQ中消费者分为两类,一类是PushConsumer(被动的接收消息),另一类的是SimpleConsumer(主动拉取消息),如果使用SimpleConsumer拉取消息,一次拉取多条消息则需要在业务方保证顺序性。
2、有限的重试:RocketMQ 顺序消息投递仅在重试次数限定范围内,即一条消息如果一直重试失败,超过最大重试次数后将不再重试,跳过这条消息消费,不会一直阻塞后续消息处理。、
总结:实现顺序消息需要生产者和消费者共同配合
image.png

1、创建FIFO主题

顺序消息仅支持使用MessageType为FIFO的主题,即顺序消息只能发送至类型为顺序消息的主题中,发送的消息的类型必须和主题的类型一致。所以我们要创建一个类型为FIFO的TOPIC
命令如下:./mqadmin updateTopic -a +message.type=FIFO -b 192.168.111.140:10911 -n 192.168.111.140:9876 -t TestFIFO
image.png

2、生产者

  ClientServiceProvider provider = ClientServiceProvider.loadService();
        String topic = "TestFIFO";
        Producer producer = ProducerUtil.getProvider(topic, provider);
        // 延时消息
        Message message1 = provider.newMessageBuilder().setTopic(topic)
            // 设置消息索引键,可根据关键字精确查找某条消息。
            .setKeys("messageKey1")
            // 设置消息Tag,用于消费端根据指定Tag过滤消息。
            .setTag("fifoTag")
            // 设置消息分组
            .setMessageGroup("fifoGroup")
            // 消息体。
            .setBody("FIFO MESSAGE 1".getBytes()).build();
        Message message2 = provider.newMessageBuilder().setTopic(topic)
            // 设置消息索引键,可根据关键字精确查找某条消息。
            .setKeys("messageKey2")
            // 设置消息Tag,用于消费端根据指定Tag过滤消息。
            .setTag("fifoTag")
            // 设置消息分组
            .setMessageGroup("fifoGroup")
            // 消息体。
            .setBody("FIFO MESSAGE 2".getBytes()).build();

        try {
            producer.send(message1);
            producer.send(message2);
            System.out.println("发送完成");
        } catch (Exception e) {
            e.printStackTrace();
        }

3、消费者

public static void main(String[] args) throws ClientException {
    final ClientServiceProvider provider = ClientServiceProvider.loadService();
    // 接入点地址,需要设置成Proxy的地址和端口列表,一般是xxx:8081;xxx:8081。
    String endpoints = "192.168.16.128:8081";
    ClientConfiguration clientConfiguration = ClientConfiguration.newBuilder().setEndpoints(endpoints).build();
    // 订阅消息的过滤规则,表示订阅所有Tag的消息。
    String tag = "*";
    FilterExpression filterExpression = new FilterExpression(tag, FilterExpressionType.TAG);
    // 为消费者指定所属的消费者分组,Group需要提前创建。
    String consumerGroup = "MyConsumerGroup";
    // 指定需要订阅哪个目标Topic,Topic需要提前创建。
    String topic = "TestFIFO";
    // 初始化PushConsumer,需要绑定消费者分组ConsumerGroup、通信参数以及订阅关系。
    PushConsumer pushConsumer = provider.newPushConsumerBuilder().setClientConfiguration(clientConfiguration)
        // 设置消费者分组。
        .setConsumerGroup(consumerGroup)
        // 设置预绑定的订阅关系。
        .setSubscriptionExpressions(Collections.singletonMap(topic, filterExpression))
        // 设置消费监听器。
        .setMessageListener(messageView -> {
            // 处理消息并返回消费结果。
            // LOGGER.info("Consume message={}", messageView);
            System.out.println("Consume message!! " + getString(messageView.getBody()));
            return ConsumeResult.SUCCESS;
        }).build();
    // 如果不需要再使用PushConsumer,可关闭该进程。
    // pushConsumer.close();
}

结果:
image.png

四、小结

本文简单的学习了RocketMQ中的延时消息和顺序消息,目前还差一种事务消息还没有学习。由于事务消息相对来说复杂,所以单独放在下一篇文章中。希望对你有所帮助!

未完待续

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值