最近接触到的一个项目有用到RocketMQ需要整合SpringBoot,因为之前没有用过(只是用RabbitMQ),所有特地学习记录一下。本文主要是对RocketMQ做简单介绍(一些基本概念),以及对SpringBoot的整合还有整合过程中遇到的一些问题做一个记录。
RocketMQ 是阿里巴巴开源的队列模型的消息中间件,具有高性能、高可靠、高实时、分布式特点。我们需要先对他的一些基本概念有所了解,即Producer、Consumer、Broker、NameServer、Group、Topic等。
RocketMQ主要由 Producer、Broker、Consumer 三部分组成,其中Producer 负责生产消息,Consumer 负责消费消息,Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器,每个 Broker 可以存储多个Topic的消息,每个Topic的消息也可以分片存储于不同的 Broker。Message Queue 用于存储消息的物理地址,每个Topic中的消息地址存储于多个 Message Queue 中。ConsumerGroup 由多个Consumer 实例构成。
1.Name Server:名称服务充当路由消息的提供者。是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。在消息队列 RocketMQ 中提供命名服务,更新和发现 Broker 服务。
2.Broker:消息中转角色,负责存储消息,转发消息。可以理解为消息队列服务器,提供了消息的接收、存储、拉取和转发服务。broker是RocketMQ的核心,它不不能挂的,所以需要保证broker的高可用。
3.Producer:Producer表示消息队列的生产者。消息队列的本质就是实现了publish-subscribe模式,生产者生产消息,消费者消费消息。所以这里的Producer就是用来生产和发送消息的,一般指业务系统。
4.Consumer:消息消费者,一般由业务后台系统异步的消费消息。
5.Group:又有Produser Group和Consumer Group可以看做是一类生产者和消费者的集合。
6.Topic:代表普通的消息队列,而Queue是组成Topic的更小单元(可以看成是Queue的集合)。
以上是一些RocketMQ的基本概念,详细的可以参考:https://www.cnblogs.com/weifeng1463/p/12889300.html
在整合之前我们需要先下载RocketMQ,本文直接是在本地使用(学习及调试更方便),下载地址(https://github.com/apache/rocketmq/tags),为了方便我们也下载RocketMQ图形界面
下载解压完启动RocketMQ
#cmd启动,先启动mqnamesrv后启动mqbroker
start mqnamesrv.cmd
start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
如果在启动Broker过程中报找不到主加载类的错,可以如下编辑runbroker.cmd添加引号(RocketMQ需要依赖java环境)
另外我们可以将下载的图形工具打包运行(需要修改配置文件指定RocketMQ地址)可以让我们学习的时候更直观。
mvn clean package -Dmaven.test.skip=true #打包
java -jar rocketmq-console-ng-1.0.0.jar #运行
以上完成准备操作。
分别创建producer服务和consumer服务
1.分别引入RocketMQ的依赖
<dependency>
<groupId>com.alibaba.rocketmqgroupId>
<artifactId>rocketmq-clientartifactId>
<version>3.2.6version>
dependency>
2.producer服务编写生产者
@RequestMapping("/hi/{id}")
public Response sayHi(@PathVariable Integer id){
DefaultMQProducer defaultMQProducer = new DefaultMQProducer();
try {
defaultMQProducer.setProducerGroup("my-producer-group");
defaultMQProducer.setNamesrvAddr("127.0.0.1:9876");
defaultMQProducer.setInstanceName("producer01");
defaultMQProducer.start();
String msg="这个是使用单独的rocketmq发送的消息-"+id;
Message message = new Message("first-topic",msg.getBytes());
SendResult send = defaultMQProducer.send(message);
log.info("----------------发送消息返回的结果:{}",send);
defaultMQProducer.shutdown();
return Response.ok();
}catch (Exception e){
log.error(e.getMessage());
log.info("发送失败");
}
return Response.fail();
}
3.consumer服务编写消费者
@Slf4j
@Configuration
public class RocketMQConfig {
@Bean("consumer001")
public DefaultMQPushConsumer consumer01() throws Exception{
DefaultMQPushConsumer defaultMQPushConsumer = new DefaultMQPushConsumer();
defaultMQPushConsumer.setInstanceName("first-consumer");
defaultMQPushConsumer.setNamesrvAddr("127.0.0.1:9876");
defaultMQPushConsumer.setConsumerGroup("my-producer-group");
defaultMQPushConsumer.subscribe("first-topic","*");
defaultMQPushConsumer.registerMessageListener(new Consumer01());
defaultMQPushConsumer.start();
return defaultMQPushConsumer;
}
@Bean("consumer002")
public DefaultMQPushConsumer consumer02() throws Exception{
DefaultMQPushConsumer defaultMQPushConsumer = new DefaultMQPushConsumer();
defaultMQPushConsumer.setInstanceName("second-consumer");
defaultMQPushConsumer.setNamesrvAddr("127.0.0.1:9876");
defaultMQPushConsumer.setConsumerGroup("my-producer-group");
defaultMQPushConsumer.subscribe("first-topic","*");
defaultMQPushConsumer.registerMessageListener(new Consumer02());
defaultMQPushConsumer.start();
return defaultMQPushConsumer;
}
}
@Slf4j
@Component
public class Consumer01 implements MessageListenerConcurrently {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
for (MessageExt msg:list){
String result="";
try {
result = new String(msg.getBody(), "utf-8");
log.info("=====consumer01 消费结果:{}",result);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}catch (Exception e){
log.error(e.getMessage());
log.info("消费失败");
}
}
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
启动服务进行测试,刚开始没有为每个消费者分别设置实例名称,结果导致报错(The consumer group[my-producer-group] has been created before, specify another name please),后来调用setInstanceName为每个消费者分别设置实例名称就好了。
创建producer服务和consumer服务
1.分别引入RocketMQ依赖
<dependency>
<groupId>org.apache.rocketmqgroupId>
<artifactId>rocketmq-spring-boot-starterartifactId>
<version>2.1.0version>
dependency>
@Slf4j
@RestController
public class HelloRocketMQ {
@Autowired
private RocketMQTemplate rocketMQTemplate;
//使用mq-starter的形式发送消息
@RequestMapping("/hello/{i}")
public Response hello(@PathVariable Integer i){
try {
String message="这是第一条消息"+i;
rocketMQTemplate.convertAndSend("first-topic",message);
System.out.println("生产:"+message);
return Response.ok();
}catch (Exception e){
log.info("信息发送失败了!!!!",e);
}
return Response.fail();
}
}
@Component
/**
* @RocketMQMessageListener注解指定消费组和topic
*/
@RocketMQMessageListener(topic = "first-topic",consumerGroup = "my-producer-group")
public class HelloConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String s) {
System.out.println("消费者1:"+s);
}
}
//@RocketMQMessageListener注解指定消费组和topic
@Component
@RocketMQMessageListener(topic = "first-topic",consumerGroup = "my-producer-group")
public class HelloConsumer2 implements RocketMQListener<String>, RocketMQPushConsumerLifecycleListener {
@Override
public void onMessage(String s) {
System.out.println("消费者2:"+s);
}
//解决多个消费者消费同一消费组同一个topic报错问题
@Override
public void prepareStart(DefaultMQPushConsumer defaultMQPushConsumer) {
defaultMQPushConsumer.setInstanceName("consumer2");
}
}
启动producer服务和consumer服务测试,上面的(The consumer group[my-producer-group] has been created before, specify another name please)问题我们可以通过实现RocketMQPushConsumerLifecycleListener 来为每个消费者设置实例名称。