Bootstrap

Spring Boot集成RocketMQ全部种类消息实现+生产者和消费者配置信息介绍(内含5.x新增可自定义时间的定时/延时消息)

前言

这里不对RocketMQ做介绍是完全的功能实现没有废话,这里使用@RocketMQMessageListener注解+RocketMQListener接口实现,会对@RocketMQMessageListener注解中的参数做详细介绍。

需要RocketMQ安装教程可以参考:
Docker部署RocketMQ5.x
docker-compose部署RocketMQ5.x

添加POM依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--要使用RocketMQ5.x的自定义时间延时消息必须要使用2.2.3及以上的版本-->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.74</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.2.5</version>
        </dependency>
    </dependencies>

添加application.yml配置信息

server:
  port: 8888

rocketmq:
  # name-server服务地址多个用;隔开 例如127.0.0.1:9876;127.0.0.1:9877
  name-server: 192.168.10.220:9876
  producer:  # 生产者配置
    group: group1  # 生产者分组
    send-message-timeout: 3000 # 消费者发送消息超时时间单位毫秒

创建公共示例对象(只看demo可忽略)

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDTO {
    private String id;
    private String nickname;
    private Integer age;
    private LocalDateTime sendTime;
}

消费者相关介绍

因为这里用的rocketmq-spring-boot-starter包,那么消费者的核心就在于@RocketMQMessageListener注解和RocketMQListener接口,这里会对这这两个部分做一些使用上的说明。

ACK机制介绍

  • RocketMQ提供了ack机制(默认是手动ack),以保证消息能够被正常消费。为了保证消息肯定消费成功,只有使用方明确表示消费成功,RocketMQ才会认为消息消费成功,然后删除消息。中途断电,抛出异常等都不会认为成功。
  • 这里使用的rocketmq-spring-boot-starter包算是自动ACK了吧,使用@RocketMQMessageListener注解标记一个实现RocketMQListener接口的消费者类并且实现onMessage方法,只要这个onMessage方法执行不抛出异常则由代理类自动进行ACK处理,如果抛出异常则确认失败消息会继续重试消费。
    在这里插入图片描述

@RocketMQMessageListener介绍

参数介绍

RocketMQListener接口介绍

泛型问题
使用MessageExt(可获取完整消息对象:消息体、消息ID、topic、queueId等)

如果需要获取获取除了消息体以外的信息可以使用MessageExt作为RocketMQListener的泛型,在做消息幂等时可能会需要使用到消息ID。

@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "message-ext-group-01", topic = "message-ext-topic-01")
public class MessageExtComsumer implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt messageExt) {
        String body = new String(messageExt.getBody(), StandardCharsets.UTF_8);
        UserDTO userDTO = JSON.parseObject(body, UserDTO.class); // 这里使用的是fastjson也可以使用别的方式
        log.info("收到消息: msgId={} topic={} queueId={} body={}", messageExt.getMsgId(), messageExt.getTopic(), messageExt.getQueueId(), userDTO);
        // 业务执行完毕没有抛出异常则由代理类自动提交ACK
    }
}
使用UserDTO(不需要完整消息对象直接使用消息体类型)

直接使用发送消息时的body传入类型对象会自动序列化成对应类型,不需要完整消息对象直接使用消息体类型是一个很不错的选择,下面例子中大部分都是使用这种方式。

发送单向消息

发送方只负责发送消息,不等待服务端返回响应且没有回调函数触发,即只发送请求不等待应答。此方式发送消息的过程耗时非常短,一般在微秒级别。适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。

生产者

@Slf4j
@Component
public class OnewayMessageProducer {
    @Autowired
    RocketMQTemplate rocketMQTemplate;
    /** 发送单向消息 */
    public void send() {
        String id = UUID.randomUUID().toString();
        int age = RandomUtil.randomInt(100);
        UserDTO userVo = new UserDTO(id , "kerwin"+age, age, LocalDateTime.now());
        log.info("开始发送消息 userVo = {}", userVo);
        rocketMQTemplate.sendOneWay("simple-sync-topic-01", MessageBuilder.withPayload(userVo).build());
        log.info("发送消息成功 userVo = {}",userVo);
    }
}

消费者

@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "one-way-group-01", topic = "one-way-topic-01")
public class OnewayMessageConsumer implements RocketMQListener<UserDTO> {
    @Override
    public void onMessage(UserDTO message) {
        log.info("收到消息: {}", message);
        // 业务执行完毕没有抛出异常则由代理类自动提交ACK
    }
}

发送同步消息(响应值为void)

简单同步消息响应参数为void,如果发送成功则会打印发送消息成功日志,如果发送失败会抛出特定异常

生产者

@Slf4j
@Component
public class SimpleSyncMessageProducer {
    @Autowired
    RocketMQTemplate rocketMQTemplate;
    /** 发送简单同步消息 */
    public void send() {
        String id = UUID.randomUUID().toString();
        int age = RandomUtil.randomInt(100);
        UserDTO userVo = new UserDTO(id , "kerwin"+ges, age, LocalDateTime.now());
        log.info("开始发送消息 userVo = {}",userVo);
        rocketMQTemplate.send("simple-sync-topic-01", MessageBuilder.withPayload(userVo).build());
        log.info("发送消息成功 userVo = {}",userVo);
    }
}

消费者

@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "simple-sync-group-01", topic = "simple-sync-topic-01")
public class SimpleSyncMessageConsumer implements RocketMQListener<UserDTO> {
    @Override
    public void onMessage(UserDTO message) {
        log.info("收到消息: {}", message);
        // 业务执行完毕没有抛出异常则由代理类自动提交ACK
    }
}

发送同步消息(响应值为SendResult可以获取消息ID等信息)

生产者

@Slf4j
@Component
public class SimpleSyncMessageProducer {
    @Autowired
    RocketMQTemplate rocketMQTemplate;
    /** 发送简单同步消息 */
    public void send() {
        String id = UUID.randomUUID().toString();
        int age = RandomUtil.randomInt(100);
        UserDTO userVo = new UserDTO(id , "kerwin"+age, age, LocalDateTime.now());
        log.info("开始发送消息 userVo = {}",userVo);
        SendResult sendResult = rocketMQTemplate.syncSend("simple-sync-topic-01", MessageBuilder.withPayload(userVo).build());
        log.info("发送消息成功 userVo = {}",userVo);
    }
}

消费者

@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "simple-sync-group-01", topic = "simple-sync-topic-01")
public class SimpleSyncMessageConsumer implements RocketMQListener<UserDTO> {
    @Override
    public void onMessage(UserDTO message) {
        log.info("收到消息: {}", message);
        // 业务执行完毕没有抛出异常则由代理类自动提交ACK
    }
}

发送异步消息

生产者

@Slf4j
@Component
public class SimpleSyncMessageProducer {
    @Autowired
    RocketMQTemplate rocketMQTemplate;
    /** 发送异步消息 */
    public void send() {
        String id = UUID.randomUUID().toString();
        int age = RandomUtil.randomInt(100);
        UserDTO userVo = new UserDTO(id , "kerwin"+age, age, LocalDateTime.now());
        log.info("开始发送消息 userVo = {}",userVo);
        rocketMQTemplate.asyncSend("simple-async-topic-01", MessageBuilder.withPayload(userVo).build(),new SendCallback(){
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("发送消息成功 sendResult = {}",sendResult);
            }
            @Override
            public void onException(Throwable throwable) {
                log.info("发送消息失败 errorMessage = {}",throwable.getMessage());
                throwable.printStackTrace();
                // 发布消息失败可以进行重试发送,或者自己记录失败数据进行人工补偿。
            }
        });
    }
}

消费者

@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "simple-async-group-01", topic = "simple-async-topic-01")
public class SimpleAsyncMessageConsumer implements RocketMQListener<UserDTO> {
    @Override
    public void onMessage(UserDTO message) {
        log.info("收到消息: {}", message);
        // 业务执行完毕没有抛出异常则由代理类自动提交ACK
    }
}

发送使用延时等级的延时消息(这种是4.x的延时消息5.x也能使用)

该延时消息使用延时等级来控制延时消息发送时间,默认18个等级使用是等级从1开始,可以在borker修改延时等级配置messageDelayLevel ,在前言的安装文档中有borker配置参数介绍,不过不建议修改因为消息重试机制也是使用的messageDelayLevel作为重试间隔时间。

生产者

@Slf4j
@Component
public class DelayLevelMessageProducer {
    @Autowired
    RocketMQTemplate rocketMQTemplate;
    /** 发送使用延时等级的延时消息 */
    public void send() {
        String id = UUID.randomUUID().toString();
        int age = RandomUtil.randomInt(100);
        UserDTO userVo = new UserDTO(id , "kerwin"+age, age, LocalDateTime.now());
        log.info("开始发送消息 userVo = {}", userVo);
        rocketMQTemplate.syncSend( "delay-level-topic-01",
                MessageBuilder.withPayload(userVo).build(),
                //发送消息超时时间
                3000,
                //设置延时等级3,这个消息将在10s之后发送(现在只支持固定的几个时间,详看delayTimeLevel)
                //messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
                3 );
        log.info("发送消息成功 userVo = {}",userVo);
    }
}

消费者

@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "delay-level-group-01", topic = "delay-level-topic-01")
public class DelayLevelMessageConsumer implements RocketMQListener<UserDTO> {
    @Override
    public void onMessage(UserDTO message) {
        log.info("收到消息: {}", message);
        // 业务执行完毕没有抛出异常则由代理类自动提交ACK
    }
}

发送定时消息可指定时间(5.x新增)

延时消息存在着一些不足:

  • 1.延时级别只有 18 个,并不能满足所有场景;
  • 2.如果通过修改 messageDelayLevel 配置来自定义延时级别,并不灵活,比如一个在大规模的平台上,延时级别成百上千,而且随时可能增加新的延时时间;
  • 3.延时时间不准确,后台的定时线程可能会因为处理消息量大导致延时误差大。

为了弥补延时消息的不足,RocketMQ 5.0 引入了定时消息,可指定消息消费时间。

生产者

@Slf4j
@Component
public class DeliverTimeMillsMessageProducer {
    @Autowired
    RocketMQTemplate rocketMQTemplate;
    /** 发送定时消息 */
    public void send() {
        String id = UUID.randomUUID().toString();
        int age = RandomUtil.randomInt(100);
        UserDTO userVo = new UserDTO(id , "kerwin"+age, age, LocalDateTime.now());
        log.info("开始发送消息 userVo = {}", userVo);
        rocketMQTemplate.syncSendDeliverTimeMills( "deliver-time-mills-topic-01",
                MessageBuilder.withPayload(userVo).build(),
                // 定时时间 当前时间戳 + 3000毫秒 ,3秒之后消息会被消费,这个定时时间默认不能超过 1 天,可以配置
                System.currentTimeMillis() + 3000);
        log.info("发送消息成功 userVo = {}",userVo);
    }
}

消费者

@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "deliver-time-mills-group-01", topic = "deliver-time-mills-topic-01")
public class DeliverTimeMillsMessageConsumer implements RocketMQListener<UserDTO> {
    @Override
    public void onMessage(UserDTO message) {
        log.info("收到消息: {}", message);
        // 业务执行完毕没有抛出异常则由代理类自动提交ACK
    }
}

发送顺序消息

  • 概念:RocketMQ的Topic在创建时默认会生成4个队列,只能保证在一个队列中保证顺序消息,通过消息的id hash取模获取这条消息应该储存在那个队列中,并且必须使用同步消息,消费者采用ConsumeMode.ORDERLY(同一个队列单线程消费)模式消费,对于指定的一个Topic,每个队列的消息按照严格的先入先出(FIFO)的顺序来发布和消费。
  • 适用场景:适用于性能要求不高,所有的消息严格按照FIFO原则来发布和消费的场景。

使用RocketMQTemplate直接发送

RocketMQTemplate提供的syncSendOrderly方法第三个参数会作为计算存储在那一个队列的条件,会根据第三个参数hash取模获取对应队列下标,自己测试时可以将id值固定查看是否都投递到同一个队列中。

@Slf4j
@Component
public class OrderlyMessageProducer {
    @Autowired
    RocketMQTemplate rocketMQTemplate;
    /** 发送顺序消息 */
    public void send() {
        String id = UUID.randomUUID().toString();
        int age = RandomUtil.randomInt(100);
        UserDTO userVo = new UserDTO(id , "kerwin"+age, age, LocalDateTime.now());
        log.info("开始发送消息 userVo = {}", userVo);
        rocketMQTemplate.syncSendOrderly("orderly-topic-01",userVo,id);
        log.info("发送消息成功 userVo = {}",userVo);
    }
}

使用原生DefaultMQProducer发送

  • 使用该方法投递消息可以自定义投递算法,也可以使用这种方式发送普通消息自定义消息投递算法。
@Slf4j
@Component
public class OrderlyMessageProducer {
    @Autowired
    RocketMQTemplate rocketMQTemplate;
    /** 发送顺序消息 */
    @SneakyThrows
    public void send() {
        DefaultMQProducer producer = rocketMQTemplate.getProducer();
        String id = UUID.randomUUID().toString();
        int age = RandomUtil.randomInt(100);
        UserDTO userVo = new UserDTO(id, "kerwin" + age, age, LocalDateTime.now());
        Message message = new Message("orderly-topic-01", JSON.toJSONBytes(userVo));
        producer.send(message, new MessageQueueSelector() {
            /**
             * @param list topic下的所有队列
             * @param message 消息
             * @param o    外部传入的消息id
             */
            @Override
            public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
                // 这里的投递算法可以自己定义,我这里使用传入的id进行hash取模得到对应的队列下标,需要取绝对值因为hash之后的值可能存在负数
                int queueIndex = Math.abs(o.hashCode() % list.size());
                // 需要发送到那个队列,直接通过下标从list取出对应队列return即可
                return list.get(queueIndex);
            }
        }, id);
    }
}

公共消费者

参数介绍:

  • consumeMode:消费模型 默认是consumeMode.CONCURRENTLY每个队列都是多线程消费,这里设置为consumeMode = ConsumeMode.ORDERLY:topic中每个队列单线程消费 但是topic中每一个队列可以有一个线程同时消费,需要当前消息被消费完成ACK后才会消费下一条消息。
  • consumeThreadNumber(5.x新参数):消费线程数 默认值为20,如果是单机消费建议配置成和队列数相同数量一个topic默认是4个队列这里配置成4个线程,如果是两个消费者建议配置成2,每台消费者消费两个队列。
  • consumeThreadMax(4.x参数):最大线程数 默认值为64,配置消费线程数量和consumeThreadNumber配置一致,在5.x官方已经不推荐使用,该消费线程池使用LinkedBlockingQueue作为任务队列默认容量有限为(Integer.MAX_VALUE)。
@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "orderly-group-01", topic = "orderly-topic-01", consumeThreadNumber = 4, consumeMode = ConsumeMode.ORDERLY)
public class OrderlyMessageConsumer implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt messageExt) {
        String body = new String(messageExt.getBody(), StandardCharsets.UTF_8);
        UserDTO userDTO = JSON.parseObject(body, UserDTO.class); // 这里使用的是fastjson也可以使用别的方式
        log.info("收到消息: threadName={} msgId={} topic={} queueId={} body={}", Thread.currentThread().getName(), messageExt.getMsgId(), messageExt.getTopic(), messageExt.getQueueId(), userDTO);
        // 业务执行完毕没有抛出异常则由代理类自动提交ACK
    }
}

发送带有Tag的消息

  • Tag,即消息标签,用于对某个Topic下的消息进行分类。消息队列RocketMQ版的生产者在发送消息时,已经指定消息的Tag,消费者需根据已经指定的Tag来进行订阅。

生产者

@Slf4j
@Component
public class TagMessageProducer {
    @Autowired
    RocketMQTemplate rocketMQTemplate;
    /** 发送Tag消息 */
    public void send() {
        String id = UUID.randomUUID().toString();
        int age = RandomUtil.randomInt(100);
        UserDTO userVo = new UserDTO(id , "kerwin"+age, age, LocalDateTime.now());
        log.info("开始发送消息 userVo = {}",userVo);
        // 都是destination参数都是topic +":"+tags 低层封装会使用:自动拆分topic 和 tags
        rocketMQTemplate.send("tag-topic-01"+":"+"tag-01", MessageBuilder.withPayload(userVo).build());
        rocketMQTemplate.send("tag-topic-01"+":"+"tag-02", MessageBuilder.withPayload(userVo).build());
        rocketMQTemplate.send("tag-topic-01"+":"+"tag-03", MessageBuilder.withPayload(userVo).build());
        log.info("发送消息成功 userVo = {}",userVo);
    }
}

消费者

selectorType:控制如何选择消息 默认SelectorType.TAG,使用标记选择
selectorExpression :控制可以选择的消息 默认是*,消费全部的消息,也可以指定多个tag以||隔开

@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "tag-group-01", topic = "tag-topic-01",selectorType = SelectorType.TAG,selectorExpression = "tag-01||tag-03")
public class TagMessageConsumer implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt messageExt) {
        String tags = messageExt.getTags();
        switch (tags) {
            case "tag-01":
                log.info("tag-01 天下无敌");
                break;
            case "tag-02":
                log.info("tag-02 一人之下");
                break;
            case "tag-03":
                log.info("tag-03 三楼C");
                break;
            default:
                log.info("tags不存在溜了溜了");
                break;
        }
        String body = new String(messageExt.getBody(), StandardCharsets.UTF_8);
        UserDTO userDTO = JSON.parseObject(body, UserDTO.class); // 这里使用的是fastjson也可以使用别的方式
        log.info("收到消息: tags={} msgId={} topic={} queueId={} body={}", tags, messageExt.getMsgId(), messageExt.getTopic(), messageExt.getQueueId(), userDTO);
        // 业务执行完毕没有抛出异常则由代理类自动提交ACK
    }
}

发送事务消息

这里的事务消息只做简单示例不做过多介绍,实际结合业务场景使用和这个演示有本质的区别,需要查看实际业务场景代码实现可跳转下面博客:
https://blog.csdn.net/weixin_44606481/article/details/129903032

生产者

@Slf4j
@Component
public class TransationMessageProducer {
    @Autowired
    RocketMQTemplate rocketMQTemplate;
    /** 发送事务消息 */
    public void send() {
        String msg = "发送事务消息"+RandomUtil.randomInt(100);
        // 发送事务消息:采用的是sendMessageInTransaction方法,返回结果为TransactionSendResult对象,该对象中包含了事务发送的状态、本地事务执行的状态等
        TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction("transation-topic-01", MessageBuilder.withPayload(userVo).build(), null);
        String transactionId = result.getTransactionId();
        String status = result.getSendStatus().name();
        log.info("发送消息成功 userVo = {} transactionId={} status={} ",userVo,transactionId,status);
    }
}

生产者消息监听器

   // 事务消息共有三种状态,提交状态、回滚状态、中间状态:
    // RocketMQLocalTransactionState.COMMIT: 提交事务,它允许消费者消费此消息。
    // RocketMQLocalTransactionState.ROLLBACK: 回滚事务,它代表该消息将被删除,不允许被消费。
    // RocketMQLocalTransactionState.UNKNOWN: 中间状态,它代表需要检查消息队列来确定状态。
    /** 执行本地事务(在发送消息成功时执行)  */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        //TODO 开启本地事务(实际就是我们的jdbc操作)
        //TODO 执行业务代码(例如插入订单数据库表等)
        //TODO 提交或回滚本地事务
        //模拟一个处理结果
        int index=2;
        /**
         * 模拟返回事务状态
         */
        switch (index){
            case 1:
                //处理业务
                String jsonStr = new String((byte[]) message.getPayload(), StandardCharsets.UTF_8);
                log.info("本地事务回滚,回滚消息,"+jsonStr);
                //返回ROLLBACK状态的消息会被丢弃
                return RocketMQLocalTransactionState.ROLLBACK;
            case 2:
                //返回UNKNOW状态的消息会等待Broker进行事务状态回查
                log.info("需要等待Broker进行事务状态回查");
                return RocketMQLocalTransactionState.UNKNOWN;
            default:
                log.info("事务提交,消息正常处理");
                //返回COMMIT状态的消息会立即被消费者消费到
                return RocketMQLocalTransactionState.COMMIT;
        }
    }
    /**
     * 检查本地事务的状态
     * 回查间隔时间:系统默认每隔60秒发起一次定时任务,对未提交的半事务消息进行回查,共持续12小时。
     * 第一次消息回查最快
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        String transactionId = message.getHeaders().get("__transactionId__").toString();
        log.info("检查本地事务状态,transactionId:{}", transactionId);
        return RocketMQLocalTransactionState.COMMIT;
    }

消费者

@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "transation-group-01", topic = "transation-topic-01")
public class TransationMessageConsumer implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt messageExt) {
        String body = new String(messageExt.getBody(), StandardCharsets.UTF_8);
        UserDTO userDTO = JSON.parseObject(body, UserDTO.class); // 这里使用的是fastjson也可以使用别的方式
        log.info("收到消息: msgId={} topic={} queueId={} body={}", messageExt.getMsgId(), messageExt.getTopic(), messageExt.getQueueId(), userDTO);
        // 业务执行完毕没有抛出异常则由代理类自动提交ACK

    }
}

消息消费重试问题

  • 消费重试前提条件
    • 只有在消息模式为MessageModel.CLUSTERING集群模式时,Broker才会自动进行重试,广播消息是不会重试的
    • 消费失败/消费超时(默认15分钟)或者直接响应ConsumeConcurrentlyStatus.RECONSUME_LATER
  • 重试机制和限制
    • 如果消费者在消费时抛出异常或者返回ConsumeConcurrentlyStatus.RECONSUME_LATER,会将这个消息放入一个重试队列%RETRY%topic,重试间隔时间会使用RocketMQ Broker中的messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h参数,默认重试16次如果没有成功会将这个消息放入死信队列%DLQ%topic中。

消费者代码演示

messageModel:默认值也是MessageModel.CLUSTERING可以不用额外配置
maxReconsumeTimes:重试次数默认-1,在MessageModel.CLUSTERING模式中,-1表示16,消费失败后会重试16次,在MessageModel.BROADCASTING模式中,-1表示整数Integer.MAX_VALUE

@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "reconsume-group-01", topic = "reconsume-topic-01",messageModel = MessageModel.CLUSTERING,maxReconsumeTimes = 2)
public class ReconsumeMessageConsumer implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt messageExt) {
        // 当前重试次数
        int reconsumeTimes = messageExt.getReconsumeTimes();
        String body = new String(messageExt.getBody(), StandardCharsets.UTF_8);
        UserDTO userDTO = JSON.parseObject(body, UserDTO.class); // 这里使用的是fastjson也可以使用别的方式
        log.info("收到消息: reconsumeTimes={} msgId={} topic={} queueId={} body={}",reconsumeTimes, messageExt.getMsgId(), messageExt.getTopic(), messageExt.getQueueId(), userDTO);
        if(1==1){
            throw new RuntimeException();
        }
        // 业务执行完毕没有抛出异常则由代理类自动提交ACK
    }
}

消费重试次数用完会投递到死信队列中,topic名称规则为%DLQ%+consumerGroup名称
在这里插入图片描述

死信队列消费代码演示

  • 死信特性
  • 死信消息具有以下特性:
    • 不会再被消费者正常消费。
    • 有效期与正常消息相同,均为 3 天,3 天后会被自动删除。因此,请在死信消息产生后的 3 天内及时处理。
    • 一个死信队列对应一个 Group ID, 而不是对应单个消费者实例。
      如果一个 Group ID 未产生死信消息,消息队列 RocketMQ 不会为其创建相应的死信队列,一个死信队列包含了对应 Group ID 产生的所有死信消息,不论该消息属于哪个 Topic。
@Slf4j
@Component
@RocketMQMessageListener(consumerGroup = "DLQ-reconsume-group-01", topic = "%DLQ%reconsume-group-01")
public class DLQMessageConsumer implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt messageExt) {
        String body = new String(messageExt.getBody(), StandardCharsets.UTF_8);
        UserDTO userDTO = JSON.parseObject(body, UserDTO.class); // 这里使用的是fastjson也可以使用别的方式
        log.info("收到消息: msgId={} topic={} queueId={} body={}", messageExt.getMsgId(), messageExt.getTopic(), messageExt.getQueueId(), userDTO);
        // 业务执行完毕没有抛出异常则由代理类自动提交ACK
    }
}

核心参数介绍

生产者

rocketmq:
  # name-server服务地址多个用;隔开
  name-server: 127.0.0.1:9876;127.0.0.1:9877
  producer:
    #生产者组名,规定在一个应用里面必须唯一
    group: group1
    #消息发送的超时时间 默认3000ms
    send-message-timeout: 3000
    #消息达到4096字节的时候,消息就会被压缩。默认 4096
    compress-message-body-threshold: 4096
    #最大的消息限制,默认为4M
    max-message-size: 4194304
    #同步消息发送失败重试次数默认为3
    retry-times-when-send-failed: 3
    #在内部发送失败时是否重试其他代理,这个参数在有多个broker时才生效
    retry-next-server: true
    #异步消息发送失败重试的次数
    retry-times-when-send-async-failed: 3

消费者

因为这里用的rocketmq-spring-boot-starter 2.2.3包,那么消费者的核心就在于@RocketMQMessageListener注解,所有可配置参数都在这个注解中,这里对@RocketMQMessageListener注解中配置信息做详细介绍

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RocketMQMessageListener {
	// nameServer服务地址,可以直接在注解中指定也可以读取配置文件
    String NAME_SERVER_PLACEHOLDER = "${rocketmq.name-server:}";
    // ACL验证key,服务端开启了ACL时使用,可以直接在注解中指定也可以读取配置文件
    String ACCESS_KEY_PLACEHOLDER = "${rocketmq.consumer.access-key:}";
    // ACL验证密钥,服务端开启了ACL时使用,可以直接在注解中指定也可以读取配置文件
    String SECRET_KEY_PLACEHOLDER = "${rocketmq.consumer.secret-key:}";
    // 自定义的消息轨迹主题
    String TRACE_TOPIC_PLACEHOLDER = "${rocketmq.consumer.customized-trace-topic:}";
    String ACCESS_CHANNEL_PLACEHOLDER = "${rocketmq.access-channel:}";

    // 消费者分组,不同消费者分组名称不能重复
    String consumerGroup();

    // topic名称
    String topic();

    // selectorType 消息选择器类型
    // 默认值 SelectorType.TAG 根据TAG选择
	// 仅支持表达式格式如:“tag1 || tag2 || tag3”,如果表达式为null或者“*”标识订阅所有消息
	// SelectorType.SQL92 根据SQL92表达式选择
    SelectorType selectorType() default SelectorType.TAG;

    /**
     * Control which message can be select. Grammar please see {@link SelectorType#TAG} and {@link SelectorType#SQL92}
     */
    String selectorExpression() default "*";

    // 消费模式,可以选择并发或有序接收消息,默认并发消费 ConsumeMode.CONCURRENTLY
    ConsumeMode consumeMode() default ConsumeMode.CONCURRENTLY;

    // 控制消息模式,可以选择集群和广播,默认集群 MessageModel.CLUSTERING
    // 集群: 消息只会被一个消费者消费 广播:消息被所有消费者都消费一次
    MessageModel messageModel() default MessageModel.CLUSTERING;

    // 消费者最大线程数,在5.x版本该参数已经不推荐使用,因为该实现方式底层线程使用LinkedBlockingQueue作为阻塞队列,队列长度使用Integer.MAX_VALUE。
    @Deprecated
    int consumeThreadMax() default 64;

    // 消费线程数,属于rocketmq-spring-boot-starter 2.2.3新参数,推荐使用该版本
    int consumeThreadNumber() default 20;

    // 消费失败重试次数,在MessageModel.CLUSTERING模式中,-1表示16,消费失败后会重试16次
    int maxReconsumeTimes() default -1;

    // 最大消费时间 默认15分钟
    long consumeTimeout() default 15L;

    // 发送回复消息超时 默认3000毫秒
    int replyTimeout() default 3000;

    // ACL验证key,服务端开启了ACL时使用,可以直接在注解中指定也可以读取配置文件
    String accessKey() default ACCESS_KEY_PLACEHOLDER;

    // ACL验证密钥,服务端开启了ACL时使用,可以直接在注解中指定也可以读取配置文件
    String secretKey() default SECRET_KEY_PLACEHOLDER;

    // 切换消息跟踪的标志实例
    boolean enableMsgTrace() default false;

    // 自定义跟踪主题
    String customizedTraceTopic() default TRACE_TOPIC_PLACEHOLDER;

    // nameServer服务地址,可以直接在注解中指定也可以读取配置文件
    String nameServer() default NAME_SERVER_PLACEHOLDER;

    // The property of "access-channel".
    String accessChannel() default ACCESS_CHANNEL_PLACEHOLDER;
 
    // The property of "tlsEnable" default false.
    String tlsEnable() default "false";

    // 使用者的命名空间
    String namespace() default "";

    // 并发模式下的消息消耗重试策略,下次消费时的延迟级别
    int delayLevelWhenNextConsume() default 0;

    // 以有序模式暂停拉入的间隔,以毫秒为单位。
	// 最小值为10,最大值为30000 默认1000毫秒
    int suspendCurrentQueueTimeMillis() default 1000;

    // 关闭使用者时等待消息消耗的最长时间,以毫秒为单位。
	// 最小值为0 默认1000毫秒
    int awaitTerminationMillisWhenShutdown() default 1000;

    // 实例名称
    String instanceName() default "DEFAULT";
}

;