Spring整合RocketMQ
首先创建两个maven工程,分别是rocketmq-study-consumer
,rocketmq-study-producer
,都添加以下依赖
<dependencies>
<!--rocketmq包-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.8.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
</dependencies>
生产者
主启动类
package com.zhima;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RocketProducerApplication {
public static void main(String[] args) {
SpringApplication.run(RocketProducerApplication.class,args);
}
}
yml文件
rocketmq:
producer:
# 所在生产组的组名
group-name: rocketmq-producer-group1
# 是否启动生产者
is-on-off: on
# mq的namesrv的地址,多个地址用;分割
namesrv-addr: localhost:9876
# 消息的最大长度,单位字节,默认4M
max-message-size: 4096
# 失败重试次数
retry-times-when-send-failed: 2
# 发送消息超时时间
send-msg-timeout: 3000
生产者属性类
package com.zhima.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@ConfigurationProperties(prefix = "rocketmq.producer")
public class RocketMqProducerProperties {
private String isOnOff;
private String groupName;
private String namesrvAddr;
private Integer maxMessageSize;
private Integer sendMsgTimeout;
private Integer retryTimesWhenSendFailed;
}
生产者配置类
package com.zhima.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
@Slf4j
@Configuration
@EnableConfigurationProperties(RocketMqProducerProperties.class)
public class RocketMqProducerConfiguration {
@Resource
private RocketMqProducerProperties rocketMqProducerProperties;
@Bean
public DefaultMQProducer getRocketMQProducer() {
if (StringUtils.isEmpty(rocketMqProducerProperties.getGroupName())) {
throw new RuntimeException("rocketMq producer groupName is blank");
}
if (StringUtils.isEmpty(rocketMqProducerProperties.getNamesrvAddr())) {
throw new RuntimeException("rocketMq producer nameServerAddr is blank");
}
DefaultMQProducer producer = new DefaultMQProducer(rocketMqProducerProperties.getGroupName());
producer.setNamesrvAddr(rocketMqProducerProperties.getNamesrvAddr());
// 如果需要同一个jvm中不同的producer往不同的mq集群发送消息,需要设置不同的instanceName
// producer.setInstanceName("producer1");
producer.setMaxMessageSize(rocketMqProducerProperties.getMaxMessageSize());
producer.setSendMsgTimeout(rocketMqProducerProperties.getSendMsgTimeout());
producer.setRetryTimesWhenSendFailed(rocketMqProducerProperties.getRetryTimesWhenSendFailed());
try {
producer.start();
log.info("producer starts successfully !, groupName: {} ,namesrvAddr:{}", rocketMqProducerProperties.getGroupName(), rocketMqProducerProperties.getNamesrvAddr());
} catch (MQClientException e) {
log.error("producer starts failure, reason: {}", e.getMessage());
throw new RuntimeException(e);
}
return producer;
}
}
生产者控制器
package com.zhima.controller;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/producer")
public class RocketMqProducerController {
@Autowired
private DefaultMQProducer defaultMQProducer;
@PostMapping("/send/{topic}/{tags}/{message}")
public SendResult send(
@PathVariable("message") String message,
@PathVariable("topic") String topic,
@PathVariable("tags") String tags) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
System.out.printf("开始发送消息: %s%n", message);
return defaultMQProducer.send(new Message(topic, tags, message.getBytes()));
}
}
测试
消费者
Consumer 组名,多个Consumer如果属于一个应用,订阅同样的消息,且消费逻辑一致,则应该将它们归为同一组
如果一个Consumer Group内的两个Consumer订阅不相同的Topic,可能会导致各Consumer只消费到一部分Topic
相关问题的博客:https://blog.csdn.net/qq_41377914/article/details/85092816
不同的consumerGroup名称属于不同的消费者集群,不同的消费者集群订阅了同一个topic,那么消息会广播给两个消费者集群
消费者集群中的消费者是以广播模式收到消息,还是通过负载均衡轮训收到消息是由消费端订阅方式messageModel决定的
默认消费模式时负载均衡,并不是广播模式,如果需要开启广播模式需要设置如下代码即可
consumer.setMessageModel(MessageModel.BROADCASTING);
主启动类
package com.zhima;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RocketConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RocketConsumerApplication.class, args);
}
}
yml配置文件
server:
port: 8081
rocketmq:
consumer:
# 所在消费组的组名
group-name: rocketmq-consumer-group1
# 是否启动消费者
is-on-off: on
# mq的namesrv的地址,多个地址用;分割
namesrv-addr: localhost:9876
# 最小消费线程数
consume-thread-min: 20
# 最大消费线程数
consume-thread-max: 64
# 订阅的主题和tags
# 格式:topicName~tag1||tag2||tag3;topicName2~*
# 订阅topicName下的tag1、tag2、tag3主题和topicName2下的所有tags
topics: test_topic~*
# 一次消费的最大消息条数,默认1条
consume-message-batch-max-size: 1
消费者属性类
package com.zhima.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "rocketmq.consumer")
public class RocketMqConsumerProperties {
private String isOnOff;
private String groupName;
private String namesrvAddr;
private String topics;
private Integer consumeThreadMin = 16;
private Integer consumeThreadMax = 64;
private Integer consumeMessageBatchMaxSize = 1;
}
消费者本尊
package com.zhima.consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.nio.charset.StandardCharsets;
import java.util.List;
@Component
@Slf4j
public class RocketMqMessageListener implements MessageListenerConcurrently {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> messageExts, ConsumeConcurrentlyContext context) {
if (CollectionUtils.isEmpty(messageExts)) {
log.info("消息为空");
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
for (MessageExt messageExt : messageExts) {
log.info("接受到的消息为:{}", messageExt.toString());
// 消费此条消息
String message = new String(messageExt.getBody(), StandardCharsets.UTF_8);
log.info("消费者收到消息:{}", message);
}
// 如果没有return success ,broker会重复下发该消息,并且记录重试次数
// consumer会重复消费该消息,直到return success
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
}
消费者配置类
package com.zhima.config;
import com.zhima.processor.RocketMqConsumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
@Slf4j
@Configuration
@EnableConfigurationProperties(RocketMqConsumerProperties.class)
public class RocketMqConsumerConfiguration {
@Resource
private RocketMqConsumerProperties RocketMqConsumerProperties;
@Resource
private RocketMqConsumer rocketMqConsumer;
@Bean
public DefaultMQPushConsumer getRocketMQConsumer() {
if (StringUtils.isEmpty(RocketMqConsumerProperties.getGroupName())) {
throw new RuntimeException("groupName is null !");
}
if (StringUtils.isEmpty(RocketMqConsumerProperties.getNamesrvAddr())) {
throw new RuntimeException("namesrvAddr is null !");
}
if (StringUtils.isEmpty(RocketMqConsumerProperties.getTopics())) {
throw new RuntimeException("topics is null !");
}
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(RocketMqConsumerProperties.getGroupName());
consumer.setNamesrvAddr(RocketMqConsumerProperties.getNamesrvAddr());
consumer.setConsumeThreadMin(RocketMqConsumerProperties.getConsumeThreadMin());
consumer.setConsumeThreadMax(RocketMqConsumerProperties.getConsumeThreadMax());
// 注册消费消息的类
consumer.registerMessageListener(rocketMqConsumer);
//设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
//如果非第一次启动,那么按照上次消费的位置继续消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
// 设置消费模型,集群还是广播,默认为集群
consumer.setMessageModel(MessageModel.BROADCASTING);
// 设置一次消费消息的条数,默认为1条
consumer.setConsumeMessageBatchMaxSize(RocketMqConsumerProperties.getConsumeMessageBatchMaxSize());
try {
// 设置该消费者订阅的主题和tag
String[] topicTagsArr = RocketMqConsumerProperties.getTopics().split(";");
for (String topicTags : topicTagsArr) {
String[] topicTag = topicTags.split("~");
consumer.subscribe(topicTag[0], topicTag[1]);
}
consumer.start();
log.info("consumer start successfully, groupName:{},topics:{},namesrvAddr:{}", RocketMqConsumerProperties.getGroupName(),
RocketMqConsumerProperties.getTopics(), RocketMqConsumerProperties.getNamesrvAddr());
} catch (MQClientException e) {
log.error("consumer starts failure, groupName:{},topics:{},namesrvAddr:{}", RocketMqConsumerProperties.getGroupName(),
RocketMqConsumerProperties.getTopics(), RocketMqConsumerProperties.getNamesrvAddr(), e);
throw new RuntimeException(e);
}
return consumer;
}
}
测试
在RocketMqMessageListener中的consumeMessage方法的第一行打上断点
然后发送一条消息
查看控制台
初始化参数介绍
生产者
参数名 | 参数说明 |
---|---|
producerGroup | 消息生产组组名,一个应用的消息生产者应当归为一个生产者组 |
nameserAddr | 生产者地址 |
sendMsgTimeOut | 消息发送的超时时间 |
maxMessageSize | 消息最大大小,默认4M |
retryTimesWhenSendFailed | 失败重试次数,默认2次 |
executorService | 事务消息处理线程池 |
transactionListener | 事务消息监听器 |
消费者
参数名 | 含义 |
---|---|
consumberGroup | 消费组名,一个应用的消费者都在一个消费组里面 |
nameserAddr | 消费者地址 |
consumeThreadMin | 消费者最小线程数 |
consumeThreadMax | 消费者最大线程数 |
consumeMessageBatchMaxSize | 并发消费数量,默认是1 |
subscribe | 消费者订阅主题信息 |