写在前面
从安装到测试成功,花了好几天的时间。在网上也查了很多的资料。但是很多时候都是徒劳的。第一次学习MQ,想把这些天学到的东西总结一下。也很欢迎各位大神、大牛来评论。
首先,我们花点时间介绍一下RocketMQ的性能及一些角色。个人觉得这是很有必要的。其实,不管是什么MQ,他们都是MQ,只要是MQ,他都具有一下特点:
- 削峰填谷(主要解决瞬时写压力大于应用服务能力导致消息丢失、系统奔溃等问题)
- 系统解耦(解决不同重要程度、不同能力级别系统之间依赖导致一死全死)
- 提升性能(当存在一对多调用时,可以发一条消息给消息系统,让消息系统通知相关系统)
- 蓄流压测(线上有些链路不好压测,可以通过堆积一定量消息再放开来压测)
而RocketMQ独有的特性有: - 支持结合rocketmq的多个系统之间数据最终一致性(多方事务,二方事务是前提)
- 支持18个级别的延迟消息(rabbitmq和kafka不支持)
- 支持指定次数和时间间隔的失败消息重发(kafka不支持,rabbitmq需要手动确认)
- 支持consumer端tag过滤,减少不必要的网络传输(rabbitmq和kafka不支持)
- 支持重复消费(rabbitmq不支持,kafka支持)
RocketMQ整体结构及特色
如上图所示,整体可以分成4个角色,分别是:Producer,Consumer,Broker以及NameServer;
角色如下:
NameServer
可以理解为是消息队列的协调者,Broker向它注册路由信息,同时Client向其获取路由信息,如果使用过Zookeeper,就比较容易理解了,但是功能比Zookeeper弱;
NameServer本身是没有状态的,并且多个NameServer直接并没有通信,可以横向扩展多台,Broker会和每一台NameServer建立长连接;
Broker
Broker是RocketMQ的核心,提供了消息的接收,存储,拉取等功能,一般都需要保证Broker的高可用,所以会配置Broker Slave,当Master挂掉之后,Consumer然后可以消费Slave;
Broker分为Master和Slave,一个Master可以对应多个Slave,Master与Slave的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0表示Master,非0表示Slave;
Producer
息队列的生产者,需要与NameServer建立连接,从NameServer获取Topic路由信息,并向提供Topic服务的Broker Master建立连接;Producer无状态.
Consumer
消息队列的消费者,同样与NameServer建立连接,从NameServer获取Topic路由信息,并向提供Topic服务的Broker Master,Slave建立连接;
Topic 和 Message Queue
在介绍完以上4个角色以后,还需要重点介绍一下上面提到的Topic和Message Queue;字面意思就是主题,用来区分不同类型的消息,发送和接收消息前都需要先创建Topic,针对Topic来发送和接收消息,为了提高性能和吞吐量,引入了Message Queue,一个Topic可以设置一个或多个Message Queue,有点类似kafka的分区(Partition),这样消息就可以并行往各个Message Queue发送消息,消费者也可以并行的从多个Message Queue读取消息;
跟其他的MQ相比,可能角色多了点,但是没有关系。这里我说明一点。前面两种角色不需要我们自己创建,开发者只需要自己创建后三者角色。
在申明一点:我们一定要提前搭建好RocketMQ的基本环境,如果你还不会搭建的,请点击这里;如果你搭建好了,先启动NameServer服务器,在启动Broker服务器,在运行相应的jar包。详细步骤上一篇文章都有。
整合RocketMQ基本上算是有两种方法
第一种 springboot提供了依赖
我们先看怎么整合的问题,至于问题,后面再说
Pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tff</groupId>
<artifactId>springboot_rocketmq</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<!-- springboot 整合RocketMQ依赖 -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
生产者角:
package com.tff.springbootrocketmq;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Producer{
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void sendMessage(){
//参数1:topic 参数2:要发送的消息
rocketMQTemplate.convertAndSend("hoppy","i love code");
System.out.println("消息发送完成");
}
}
消费者角色:
package com.tff.springbootrocketmq;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
#这个注解相当重要 参数1 topic 参数2 消息的组
@RocketMQMessageListener(topic = "hoppy",consumerGroup = "${rocketmq.producer.group}")
@Component
public class Consumer implements RocketMQListener<String> {
@Override
public void onMessage(String s) {
System.out.println("接收的消息:"+s);
}
}
controller层
package com.tff.web;
import com.tff.springbootrocketmq.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.UnsupportedEncodingException;
@RestController
public class RocketMQController {
@Autowired
private Producer producer; //采用springboot模板集成方式来整合
@RequestMapping("/push")
public String pushMsg() throws UnsupportedEncodingException {
producer.sendMessage();
return "ERROR";
}
}
大家可以不写这一层,直接利用springboot的测试单元也可以
配置文件
# 安装RocketMQ的主机IP地址和端口
rocketmq.name-server=127.0.0.1:9876
#发送消息的组
rocketmq.producer.group=group1
测试:
浏览器地址栏输入:http://localhost:8080/push
我们可以看到,消息消费端已经接受到消息了。
打开RocketMQ的图形用户界面
我们看到,已经可以看到我们的生产者了。
我们在来看一下“消息”菜单
这些都是我们刚才发送的消息
这种方式有一定的局限性,Springboot在这方面做的不是很好。所以,我们在开发过程中经常使用第二种方式,开发者自己进行封装。
原始的API开发方式
Pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tff</groupId>
<artifactId>rocketmq_test_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.3.1</version>
</dependency>
</dependencies>
</project>
生产者:
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.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
public class Producer1 {
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException {
//创建默认 DefaultMQProducer
DefaultMQProducer producer = new DefaultMQProducer("demo1");
//设置nameServer地址
producer.setNamesrvAddr("127.0.0.1:9876");
//开启 DefaultMQProducer
producer.start();
//创建消息
Message message = new Message("Hello_World","jsj","二叉树、平衡二叉树、图、有向图、无向图、线性结构等".getBytes(RemotingHelper.DEFAULT_CHARSET));
//发送消息
SendResult result = producer.send(message);
System.out.println(result);
//关闭
producer.shutdown();
}
}
消息消费者
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
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.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import java.io.UnsupportedEncodingException;
import java.util.List;
public class Consumer1 {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("demo1");
consumer.setNamesrvAddr("127.0.0.1:9876");
//从那个主题中获取 第二个参数是怎么过滤,*代表所有,也可以是tag
consumer.subscribe("Hello_World","*");
//创建消息监听器
consumer.registerMessageListener(new MessageListenerConcurrently() {
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//迭代消息
for(MessageExt msg : list){
try {
//获取主题
String topic = msg.getTopic();
//获取tag
String tag = msg.getTags();
//获取消息
byte[] mes = msg.getBody();
String message = new String(mes,RemotingHelper.DEFAULT_CHARSET);
System.out.println("接收到消息==: 消息主题:"+topic+", tag: "+tag+", 内容:"+message);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
//消息接收失败,重试机制
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
//消息接收成功,返回状态
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//开启consumer
consumer.start();
}
}
这里就不具体解释代码的含义了,都有注释
这里只需要补充一点;也是特别重要的一点。
就是我们下载的RocketMQ的版本要是我们导入的依赖版本一致,否则,会出现bug
例如,我这个是4.3.1版本
那么,我的maven依赖也因该是这个版本
测试:
先启动生产者:
已经发送成功
再启动消费者:
基本上就是这两种方式了,一般开发过程中,选用后者的比较多。大家在开发过程中,可以自己封住,这里只是为了测试,我没有进行封装。
另外,附一下RocketMQ的官网地址;很有用的,因为是阿里开发的,所以中国人看起来还是挺可以的。
官网地址:http://rocketmq.apache.org/docs/simple-example/
欢迎大家批评指正!如果你喜欢的话,请留下您宝贵的建议……