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";
}