RocketMQ的认识
什么是MQ
MQ全称为Message Queue,即消息队列 ,是一种提供消息队列服务的中间件,也称为消息中间件,是一套提供了消息生 产、存储、消费全过程的软件系统,遵循FIFO原则。
MQ的使用场景
-
限流削峰
MQ可以将系统的超量请求暂存其中,以便系统后期可以慢慢进行处理,从而避免了请求的丢失或系统 被压垮。
-
异步&解耦
上游系统对下游系统的调用若为同步调用,则会大大降低系统的吞吐量与并发度,且系统耦合度太高。 而异步调用则会解决这些问题。所以两层之间若要实现由同步到异步的转化,一般性做法就是,在这两层间添加一个MQ层。 即使消费者挂掉也不影响生产者工作,只要把消息放入队列即可,消费者重启后自己消费即可。
-
数据收集
分布式系统会产生海量级数据流,如:业务日志、监控数据、用户行为等。针对这些数据流进行实时或 批量采集汇总,然后对这些数据流进行大数据分析,这是当前互联网平台的必备技术。通过MQ完成此 类数据收集是最好的选择。
-
大数据处理
比如我们的平台向“三方平台”获取数据,一次请求了大量数据回来要进行处理,由于数据较多处理不过来,那么就可以放入MQ,再创建一些消费者进行数据处理即可。
【注意
】如下情况不太适合MQ
-
小项目,体量不大,并发量低的使用MQ会太过笨重 - 你可以考虑使用Redis做一个消息队列。
-
对数据的一致性有要求(强一致性)的的场景不适合使用MQ,因为MQ是异步的。
使用MQ的好处
-
提高系统响应速度
任务异步处理。 将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
-
提高系统稳定性
一是并发被消峰后,系统不容易被高并发打垮,二是系统挂了也没关系,操作内容放到消息队列不丢失,后续重新消费者一样能消费做业务处理。
-
排序保证 FIFO
遵循队列先进先出的特点,能够保证消息按照添加的数据被消费。
RocketMQ介绍
简介
具体可参考:RocketMQ 简介_丁真的小马的博客-CSDN博客
可以概述为以下几点
- 每个消息必须投递一次:RocketMQ的消费者先将消息拉取到本地,消费完成后才向服务器返回ack。如果没有消费,就不会ack消息,因此RocketMQ可以很好地支持Exactly Only Once的特性。
- 消息路由注册与剔除机制:RocketMQ通过Topic的路由注册与剔除机制来实现消息的路由。这样可以保证消息能够被正确地发送到指定的消费者。
- 消息发送高可用设计:RocketMQ采用了主从同步(HA)的设计,确保消息发送的高可用性。
- 消息存储文件设计:RocketMQ使用了基于文件的存储方式,将消息持久化到磁盘上,以保证消息的可靠性和高性能。
- 并发消息拉取与消息消费流程:RocketMQ支持并发消息拉取和消息消费的流程,以提高消息的处理效率。
RocketMQ入门
导入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.8.0</version>
</dependency>
生产者
public class Producer {
public static void main(String[] args) throws Exception {
//创建生产者对象
DefaultMQProducer producer = new DefaultMQProducer("producer-hello");
//设置name-server地址
producer.setNamesrvAddr("localhost:9876");
//开启producer
producer.start();
//设置消息
Message message = new Message("topic-producer",
"tags-producer",
"生产者消息".getBytes(Charset.defaultCharset()));
//发送消息
SendResult result = producer.send(message);
System.out.println("发送成功"+result);
}
}
-
DefaultMQProducer : MQ生产者 , 可以指定组名 producerGroupName
-
producer.setNamesrvAddr : 指定Name Server地址,用作Brocker发现。注意IP和启动name server服务时指定的IP保持一致。
-
producer.start() : 启动 生产者
-
new Message("topic_log","tags_error",("我是消息"+i).getBytes()) :消息,参数为:topic,tags,内容
-
producer.send(message) : 发送消息
消费者
-
创建consumer组
-
设置Name Server地址
-
设置消费位置,从最开始销毁
-
设置消息回调处理监听 -> 处理消息
-
Start consumer
public class Consumer {
public static void main(String[] args) throws Exception {
//创建消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer-hello");
//设置name-server地址
consumer.setNamesrvAddr("localhost:9876");
//设置订阅
consumer.subscribe("topic-producer","tags-producer");
//注册监听器
consumer.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list
, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
list.forEach(messageExt -> {
byte[] msg = messageExt.getBody();
String string = new String(msg, CharsetUtil.UTF_8);
System.out.println(string);
});
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("消费成功");
}
}
-
DefaultMQPushConsumer :消费者 , 可以指定 consumerGroupName
-
consumer.setNamesrvAddr : 设置name server 地址
-
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET) :从什么位置开始消费
-
consumer.subscribe("topic_log", "tags_error") :订阅某个topic下的某个tags的消息
-
consumer.registerMessageListener :注册消息监听器,拿到消息后,进行消息处理。
-
ConsumeConcurrentlyStatus :消费者消费结果状态,ConsumeConcurrentlyStatus.CONSUME_SUCCESS代表成功,ConsumeConcurrentlyStatus.RECONSUME_LATER代表消费失败,稍后重试,会进行多次重试
RocketMQ的安装
下载RocketMQ
下载地址:下载 | RocketMQ (apache.org)
下载后解压:
配置ROCKETMQ_HOME
启动MQ
启动NameServer
Cmd命令框执行进入至MQ文件夹\bin
下,然后执行 start mqnamesrv.cmd
,启动NameServer。(注册中心)
启动Broker(MQ)
CMD执行start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
,启动Broker。
RocketMQ插件
为了方便管理,我们需要安装一个可视化插件
RocketMQ可视化管理插件下载地址:Releases · apache/rocketmq-externals · GitHub
修改配置
解压后,修改配置:src/main/resource/application.properties ,这里需要指向Name Server 的地址和端口
打包插件
回到安装目录(pom.xml所在目录),执行: mvn clean package -Dmaven.test.skip=true
,然后会在target目录生成打包后的jar文件
启动插件
进入 target 目录,执行 java -jar rocketmq-console-ng-1.0.0.jar
, 访问 http://localhost:8080
RocketMQ的使用
环境搭建
导入依赖
<parent>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<!-- <version>2.0.4</version> -->
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
启动类
@SpringBootApplication
public class ApplicationStart {
public static void main(String[] args) {
SpringApplication.run(ApplicationStart.class);
}
}
配置文件
要设置端口号,避免和RocketMQ控制台端口8080冲突
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: "service-producer"
max-message-size: 4194304
send-message-timeout: 3000
retry-times-when-send-failed: 2
retry-times-when-send-async-failed: 2
compress-message-body-threshold: 4096
consumer:
group: "service-consumer"
pull-batch-size: 10
message-model: CLUSTERING
selector-expression: "*"
server:
port: 8899
发送消息
我们使用的是SpringBoot和RocketMQ整合的方式进行消息发送,MQ提供了RocketMQTemplate来发送消息
首先注入RocketMQTemplate
@Autowired
private RocketMQTemplate rocketMqTemplate;
同步发送
无返回值:
/**
* 同步发送
* @param message
* @return
*/
@RequestMapping("/send/{message}")
public String sendMessage(@PathVariable("message") String message){
Message<String> data = MessageBuilder.withPayload(message).build();
rocketMqTemplate.send("topic-order:tags-order",data);
return "发送成功";
}
有返回值
/**
* 获取发送消息后的结果
* @param data
*/
@RequestMapping("/syncSend/{message}")
public String syncSendMessage(@PathVariable("message") String data){
Message<String> message = MessageBuilder.withPayload(data).build();
//发送同步消息,2s发送不成功就超时
SendResult sendResult = rocketMqTemplate.syncSend("topic-test:tags-test", message, 2000);
System.out.println(sendResult);
//判断发送结果状态
if(sendResult.getSendStatus() == SendStatus.SEND_OK){
System.out.println("发送成功");
return "发送成功";
}else{
System.out.println("发送失败");
return "发送失败";
}
}
-
SendStatus : 发送的状态OK
-
msgId: 发送者自动生成的ID
-
OffsetMsgId : 由Broker生成的消息ID
-
MessageQueue :队列信息
控制台结果:
RocketMQ控制台结果:
异步发送
/**
* 异步发送
* @param data
*/
@RequestMapping("/asyncSend/{message}")
public String delaySendMessage(@PathVariable("message") String data){
Message<String> message = MessageBuilder.withPayload(data).build();
//发送异步消息,2s发送不成功就超时,延时等级3--10s后发送
SendResult sendResult = rocketMqTemplate.syncSend("topic-test:tags-test",
message,
2000);
System.out.println(sendResult);
//判断发送结果状态
if(sendResult.getSendStatus() == SendStatus.SEND_OK){
System.out.println("发送成功");
return "发送成功";
}else{
System.out.println("发送失败");
return "发送失败";
}
}
消费者(拉取消息)
@Component
@RocketMQMessageListener(//消费者的名字
consumerGroup = "consumer-group-pay",
//主题
topic = "topic-test",
//tags
selectorExpression = "tags-test",
//消息消费模式:默认是CLUSTERING集群,还支持BROADCASTING广播
messageModel = MessageModel.CLUSTERING)
public class TestConsumer implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
byte[] body = messageExt.getBody();
System.out.println(new String(body, Charset.defaultCharset())+"+++++++++++++++++");
}
}
-
RocketMQListener<MessageExt> : MQ提供了的消费者监听器,MessageExt是消息对象Message的子类 。这里的泛型对应生产者的消息类型,可以直接是消息的对象类型。
-
@RocketMQMessageListener: 消息监听的注解,提供了常用的四个属性
-
consumerGroup :消费者的组名
-
topic : 主题,对应生产者发送消息指定的destination拼接的主题
-
selectorExpression :消息选择表达式,其实就是制定消费什么tags ; 可以是固定一个值,如果是 * 是消费该topic下的所有消息 ;或者可以使用: tag1 | tag2 的方式 消费多个消息,除此之外还支持使用SQL进行消息过滤,这种方式可以实现对消息的复杂过滤。SQL过滤表达式中支持多种常量类型与运算符。比如:and ; or ; not ; IS NULL 或者 IS NOT NULL 等等。
-
messageModel :消息的消费模式,默认是CLUSTERING集群,还支持BROADCASTING广播
延迟消息
延迟消息即:把消息写到Broker后需要延迟一定时间才能被消费 , 在RocketMQ中消息的延迟时间不能任意指定,而是由特定的等级(1 到 18)来指定,分别有:
messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
在SpringBoot中发送RocketMQ延迟消息只需要设置一个延迟等级即可
-
api : public SendResult syncSend(String destination, Message<?> message, long timeout, int delayLevel) : delayLevel就是延迟等级
这里设置的等级是 3,对应的是 10S,也就是10s之后,消费者就可以收到该消息了。
测试地址:http://localhost:8899/delaySend
浏览器结果:
控制台结果: