前三篇,主要记录了,RabbitMQ的安装,并使用SpringBoot整合RabbitMQ,后续简称MQ。本篇就对之前学习MQ的内容进行整理和总结。篇幅较长,请耐心观看。若有错漏,请指正~
如何安装RabbitMQ的内容本篇就不说了,可以去看别人的文章哦,安装教程很详细。
安装好MQ后,访问MQ服务端页面,可以看到有多个标签。Exchange就是交换机、queues就是队列,现在主要使用的就是这两个东西。
既然只使用这两块,那么这两块有什么关系呢?那来看一下MQ生产与消费消息的流程图。
如果我们生产者需要发送消息到MQ中,那么就必须要有对应的交换机和队列(因为他们两个是绑定关系)。否则就会提示错误信息(NOT FOUNT - exchange 或者 not route),生产消息到MQ时是有四种的发送状态,点击链接会直接跳转到底下的代码哦。
1.有匹配到交换机,但是没有匹配到绑定的队列。(交换机没有绑定队列)- not route
2.没有匹配到交换机。(交换机名称错误,not found - exchange)
3.交换机和队列都没有匹配(和第二种状态一样,没有匹配到交换机,直接返回。)
4.成功发送消息。
后面再来说明这四种的重现。
在MQ中已存在了7种交换机,分为四种类型。分别是默认交换机("(AMQP default)"),直连交换机(amq.direct),扇形交换机(fanout.direct),头交换机(amq.headers,amq.match),主题交换机(amq.rabbitmq.trace,amq.topic)。
本篇主要讲的是直连交换机(amq.direct)、扇形交换机(amq.fanout)、主题交换机(amq.topic)。
MQ中交换机和队列是绑定的关系,其中这三个交换机之间的差别就是,绑定路由键值的不同,看以下代码和解释:
1.BindingBuilder.bind(queue()).to(exchange()).with(routingkey);
2.BindingBuilder.bind(queue()).to(exchange());
第一种是直连交换机和主题交换机的用法。
第二种是扇形交换机的用法,不需要绑定路由键。
queue()方法:自定义的队列,也可以使用已存在的队列
new Queue("已存在的队列名或自定义队列名",true,true,false)。
1.durable,是否持久化,默认为false。若为true,则将队列放入到磁盘中。否则只存再内存中,宕机后,所有数据都会消失。
2.exclusive(排他性队列),是否仅当前连接可见,默认为false。若为true,则该队列在当前连接断开后,就会删除该队列,其优先级高于durable,也就是说无论是否设置为持久化,该操作都生效。
3.autoDelete,自动删除队列,默认为false。若为true,则当消费者宕机后,会自动删除该队列。此时若生产者持续发送消息,这期间的发送内容都会丢失。
这些自己测试一下就知道了。
exchange()方法:自定义的交换机,也可以使用已存在的交换机。
(直连交换机) new DirectExchange("已存在交换机名或自定义交换机名",true);
1.durable
routinekey:交换机与队列之间绑定的路由键。发送消息时,routinekey错误,则无法获取队列,则无法进行消息发送。
直连交换机和主题交换机是必须加路由键,扇形交换机则不是必须加的。
1.直连交换机
直连交换机与队列进行绑定时,需要绑定一个路由键X。此时若有消息载体中携带了X路由键,则会找到对应的队列,并将消息存放到对应的队列中。
2.扇形交换机(广播)
扇形交换机与队列进行绑定,不需要绑定路由键,就算绑定了也没有作用。若扇形交换机中绑定了N个队列,此时有消息载体到了扇形交换机,则会被存放到扇形交换机绑定的所有队列中,消费者消费时,就会消费其所有队列的消息。
3.主题交换机
主题交换机和队列进行绑定时,可以通过通配符来进行路由键的模糊匹配。路由键可以使用topic.* 或者topic.#表示。其中*号代表了,句点后必须有1个或多个值。#号代表了,句点后必须有0个或多个值。
主题交换机是很强大的,它可以代替直连交换机和扇形交换机使用。若路由键中设置#号(说明路由键可以为空),则可以代替扇形交换机。若路由键不包含 *号或者#号(说明不存在模糊匹配,需要全匹配。)则可以代替直连交换机使用。
看一下以下的例子:
若有交换机绑定了队列1(topic.thing)、队列2(topic.*)
此时若有消息载体携带topic.thing路由键,则消息就会进入队列1和队列2中。
若有消息载体携带topic.other,则消息只会进入到队列2中,因为*号匹配了other。这就说明句点后可以输入任意字符。
以上说明了一下MQ的流程以及交换机与队列之间的关系。接下来开始正式的编码吧
POM的依赖文件,基于SpringBoot 2.4.3版本,生产者项目和消费者项目都可以使用这样的依赖。
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.mq.producter</groupId>
<artifactId>mqproducter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mqproducter</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties配置如下,生产者和消费者都可用一样的。
spring.application.name=MqProducer
##配置rabbitmq
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
创建一个生产者项目MQProducer,首先进行Config的配置。
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
/**
* 直连交换机配置
*/
public class DirectConfig {
@Bean
Queue directQueue(){
return new Queue("directQueue",true);
}
@Bean
DirectExchange createExchange(){
//原本存在的交换机,都是持久性的。
return new DirectExchange("amq.direct");
}
@Bean
Binding bindingExchange(){
//可以绑定自定义交换机或者绑定已存在的交换机
return BindingBuilder.bind(directQueue()).to(createExchange()).with("directRouting");
}
}
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
/**
* 直连交换机配置
*/
public class FanoutConfig {
@Bean
Queue firstFanoutQueue(){
return new Queue("firstFanoutQueue",true);
}
@Bean
Queue secondFanoutQueue(){
return new Queue("secondFanoutQueue",true);
}
@Bean
FanoutExchange createFanoutExchange(){
return new FanoutExchange("amq.fanout");
}
@Bean
Binding bindingFirstQueue(){
//可以绑定自定义交换机或者绑定已存在的交换机
//需要注意的是,扇形交换机with就不需要了
return BindingBuilder.bind(firstFanoutQueue()).to(createFanoutExchange());
}
@Bean
Binding bindingSeccondQueue(){
//可以绑定自定义交换机或者绑定已存在的交换机
return BindingBuilder.bind(secondFanoutQueue()).to(createFanoutExchange());
}
}
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
/**
* 直连交换机配置
*/
public class TopicConfig {
@Bean
Queue firstTopicQueue(){
return new Queue("firstTopicQueue",true);
}
@Bean
Queue secondTopicQueue(){
return new Queue("secondTopicQueue",true);
}
@Bean
TopicExchange createTopicExchange(){
//选择已存在的交换机
return new TopicExchange("amq.topic");
}
@Bean
Binding bindingFirstTopicQueue(){
//可以绑定自定义交换机或者绑定已存在的交换机
//需要注意的是,扇形交换机with就不需要了
return BindingBuilder.bind(firstTopicQueue()).to(createTopicExchange()).with("topic.thing");
}
@Bean
Binding bindingSecondTopicQueue(){
//可以绑定自定义交换机或者绑定已存在的交换机
return BindingBuilder.bind(secondTopicQueue()).to(createTopicExchange()).with("topic.#");
}
}
主题交换机的代码有所不同,在路由键中使用了通配符#
交换机对应的简单的配置代码都已经完成了,接下来就写一下发送消息的接口。
package com.mq.producter.mqproducter.action;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/mq")
public class ProducerAction {
@Autowired
RabbitTemplate rabbitTemplate;
@RequestMapping("/sending")
public void sending(){
try{
String message = "This is a first message!";
Map<String,Object> m = commonMap(message);
rabbitTemplate.convertAndSend("amq.direct","directRouting",m);
}catch(Exception e){
e.printStackTrace();
}
}
@RequestMapping("/sendtofanout")
public void sendToFanout(){
try{
String message = "This is a second message to Fanout!";
Map<String,Object> m = commonMap(message);
rabbitTemplate.convertAndSend("amq.fanout",null,m);
}catch(Exception e){
e.printStackTrace();
}
}
@RequestMapping("/sendtotopic1")
public void sendToTopicMan(){
try{
String message = "This is a first message to topic : Thing!";
Map<String,Object> m = commonMap(message);
rabbitTemplate.convertAndSend("amq.topic","topic.thing",m);
}catch(Exception e){
e.printStackTrace();
}
}
@RequestMapping("/sendtotopic2")
public void sendToTopicWoman(){
try{
String message = "This is a first message to topic : other!";
Map<String,Object> m = commonMap(message);
rabbitTemplate.convertAndSend("amq.topic","topic.asdasd",m);
}catch(Exception e){
e.printStackTrace();
}
}
public Map<String,Object> commonMap(String message){
String uuid = String.valueOf(UUID.randomUUID());
String date = new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date());
Map<String,Object> m = new HashMap<>();
m.put("messageId", uuid);
m.put("createTime",date);
m.put("message",message);
return m;
}
}
启动生产者项目后会发现,创建了N个队列,但是没有创建一个新交换机。
然后再创建一个消费者者项目MQCustomer,首先进行RabbitConfig配置(消费者的这一步可以不配置,若要配置直接拿生产者的就可以了,不过需要针对需求进行配置。消费者配置了Config也可以当做生产者使用,可以生产消息。
)。
消费者中,直接配置简单的监听就可以。复杂一点的的本篇最后会进行介绍。
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@RabbitListener(queues = {"directQueue"})
public class DirectListener {
@RabbitHandler
public void process(Map m){
System.out.println("DirectListener接受到的消息为:"+m.toString());
}
}
该代码监听的是directQueue队列的数据,若有数据就会实时消费,其他队列的数据不会进行消费。
因为有N个队列,此时需要创建N个监听器,或者在一个监听器中,监听N个队列,这样子简单的监听就没办法对不同的队列进行逻辑处理了。
FirstFanoutListener.java
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@RabbitListener(queues = {"firstFanoutQueue"})
public class FirstFanoutListener {
@RabbitHandler
public void process(Map m){
System.out.println("FirstFanoutListener接受到的消息为:"+m.toString());
}
}
SecondFanoutListener.java
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@RabbitListener(queues = {"secondFanoutQueue"})
public class SecondFanoutListener {
@RabbitHandler
public void process(Map m){
System.out.println("SecondFanoutListener接受到的消息为:"+m.toString());
}
}
这两个类监听的是扇形交换机绑定的两个队列,若有消息载体进入扇形交换机,这两个监听类都会实时消费数据。
因为有N个队列,此时需要创建N个监听器,或者在一个监听器中,监听N个队列,这样子简单的监听就没办法对不同的队列进行逻辑处理了。
FirstTopicListener.java
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@RabbitListener(queues = {"firstTopicQueue"})
public class FirstTopicListener {
@RabbitHandler
public void process(Map m){
System.out.println("FirstTopicListener接受到的消息为:"+m.toString());
}
}
SecondTopicListener.java
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@RabbitListener(queues = {"secondTopicQueue"})
public class SecondTopicListener {
@RabbitHandler
public void process(Map m){
System.out.println("SecondTopicListener接受到的消息为:"+m.toString());
}
}
这两个类监听的是主题交换机绑定的两个队列,若有消息载体进入扇形交换机,携带绑定的路由键,就会进入到指定队列,不同的监听器就会实时消费。
生产者和消费者都开发完成了,将两个项目都启动后,调用接口,往MQ中发送数据。
1.调用发送到直连交换机的接口http://localhost:8961/mq/sending
消费者端实时消费数据:
DirectListener接受到的消息为:{createTime=2021-02-25 13:55, messageId=cf0b8188-6fb4-43cb-86f2-ad0286889ceb, message=This is a first message!}
2.调用发送到扇形交换机的接口http://localhost:8961/mq/sendtofanout
FirstFanoutListener接受到的消息为:{createTime=2021-02-25 13:56, messageId=afe4206e-46e8-4e9c-9a99-d38d130b54e1, message=This is a second message to Fanout!}
SecondFanoutListener接受到的消息为:{createTime=2021-02-25 13:56, messageId=afe4206e-46e8-4e9c-9a99-d38d130b54e1, message=This is a second message to Fanout!}
两个监听器都实时消费了数据。
3.调用发送到主题交换机的两个接口
调用发送消息载体中携带topic.thing的接口:
SecondTopicListener:{createTime=2021-02-25 14:02, messageId=7f412bbd-4032-476b-a578-cd7a0aa04e1d, message=This is a first message to topic : Man!}
FirstTopicListener接受到的消息为:{createTime=2021-02-25 14:02, messageId=7f412bbd-4032-476b-a578-cd7a0aa04e1d, message=This is a first message to topic : Man!}
明明topic.thing路由绑定的是队列firstTopicQueue,但是secondTopicQueue却也收到了,为什么呢?这是因为队列secondTopicQueue的路由键配置的是topic.*,那么句点后无论是什么都会和匹配成功。
若调用发送消息载体中携带topic.other的接口:
SecondTopicListener:{createTime=2021-02-25 14:05, messageId=ad408aef-d678-4d06-be5c-7982403b3b48, message=This is a first message to topic : Woman!}
那么此刻只有队列2是匹配的。
以上就是简单的生产者与消费者的代码。接下来,来看一下回调函数以及针对不同的队列使用不同的逻辑进行数据的消费。
若想要进行函数的回调,那么就需要在application.properties文件中,加入这两句代码
###配置回调函数确认
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.publisher-confirm-type=correlated
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
@Bean
public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
//设置强制调用回调函数
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback((correlationData, b, s) -> {
System.out.println("相关数据:"+correlationData);
System.out.println("确认情况:"+b);
System.out.println("原因:"+s);
System.out.println("===============================");
});
rabbitTemplate.setReturnsCallback(returnedMessage -> {
System.out.println("交换机为:"+returnedMessage.getExchange());
System.out.println("返回消息为:"+returnedMessage.getMessage());
System.out.println("路由键为:"+returnedMessage.getRoutingKey());
System.out.println("回应消息为:"+returnedMessage.getReplyText());
System.out.println("回应代码为:"+returnedMessage.getReplyCode());
System.out.println("===============================");
});
return rabbitTemplate;
}
}
那么发送数据时,什么时候会进入不同的回调函数呢?
上面已经介绍了四种状态,点击链接直接抵达~
然后添加以下三个方法,匹配了1、2、3中状态。
@RequestMapping("/noExchange")
public void sendToNoExchange(){
//交换机不存在
Map<String,Object> m = commonMap("Judge the exchange is not exist!");
rabbitTemplate.convertAndSend("amq.topic1","topic.asdasd",m);
}
@RequestMapping("/noRoute")
public void sendToNoRoute(){
//交换机存在但是队列不存在
Map<String,Object> m = commonMap("Judge the route is not exist!");
rabbitTemplate.convertAndSend("amq.topic","asdasdsad",m);
}
@RequestMapping("/allNo")
public void sendToAllNo(){
//交换机与队列都不存在的情况
Map<String,Object> m = commonMap("Judge the exchange or route is not exist!");
rabbitTemplate.convertAndSend("amq.topic1","asdasdsad1",m);
}
若调用/noExchange接口,会调用ConfirmCallback回调函数
:
相关数据:null
确认情况:false
原因:channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'amq.topic1' in vhost '/', class-id=60, method-id=40)
若调用/noRoute接口,会调用ConfirmCallback、ReturnsCallback回调函数
:
交换机为:amq.topic
相关数据:null
确认情况:true
原因:null
===============================
返回消息为:(Body:'{createTime=2021-02-25 14:46, messageId=54986dfb-c7e3-4f4b-94a3-ff3882bdef3d, message=Judge the route is not exist!}' MessageProperties [headers={}, contentType=application/x-java-serialized-object, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0])
路由键为:asdasdsad
回应消息为:NO_ROUTE
回应代码为:312
===============================
若调用/allNo的接口的话,会发现和调用/noExchange的结果是一致的,这边就不说了。
但是若正常发送数据,则会调用ConfirmCallback回调函数
:
相关数据:null
确认情况:true
原因:null
这时候,突然来了个需求,需要针对主题交换机的队列进行不同的逻辑处理?其他交换机的数据都不进行消费。若是直连交换机则拒绝消费并将数据再放入到队列中。若是扇形交换机直接拒绝消费,并删除数据。
这一块有两个内容,1.手动确认消费数据。2.监听队列,判断是否是主题交换机绑定的队列
首先,需要在消费者项目中进行配置,代码中有注释了:
import com.mq.mqcustomer.ack.MyAckListener;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MessageListenerConfig {
@Autowired
private CachingConnectionFactory cachingConnectionFactory;
@Autowired
private MyAckListener myAckListener;
@Bean
SimpleMessageListenerContainer simpleMessageListenerContainer(){
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(cachingConnectionFactory);
//设置通同时存在的用户数为1个
listenerContainer.setConcurrentConsumers(1);
//设置通同时存在的最大用户数为1个
listenerContainer.setMaxConcurrentConsumers(1);
//设置确认消息的模式,将自动确认改为手动确认
listenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
//设置监听的队列
listenerContainer.addQueueNames("directQueue",
"firstFanoutQueue","firstTopicQueue");
//绑定消息自定义消息确认监听器myAckListener
listenerContainer.setMessageListener(myAckListener);
return listenerContainer;
}
}
编写消息确认监听器代码myAckListener:
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class MyAckListener implements ChannelAwareMessageListener {
enum Action{
ack,resend,delete
}
int count = 0;
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long key = message.getMessageProperties().getDeliveryTag();
Enum s = null;
try{
String msg = message.toString();
//看Message.toString()的源码,可以看到用'可以将消息主题和其他的文本区分开来
Map<String,Object> m = stringToMap(msg.split("'")[1]);
Object messageId = m.get("messageId");
Object createTime = m.get("createTime");
Object messageData = m.get("message");
String queue = message.getMessageProperties().getConsumerQueue();
if(queue.equals("directQueue")){
System.out.println("接收直连交换机中的数据!");
System.out.println("接收到队列:"+queue+"的消息!");
System.out.println("接收到的消息为:"+m.toString());
System.out.println("接收到的消息MessageId:"+messageId+",消息主体为:"+messageData+",接收到的时间为:"+createTime);
s = Action.resend;
if(count%2!=0){
s = Action.ack;
System.out.println(queue+"的数据我要消费了!");
}
++count;
}
if(queue.contains("topic")){
System.out.println("接收主题交换机中的数据!");
System.out.println("接收到队列:"+queue+"的消息!");
System.out.println("接收到的消息为:"+m.toString());
System.out.println("接收到的消息MessageId:"+messageId+",消息主体为:"+messageData+",接收到的时间为:"+createTime);
s = Action.ack;
}
if(queue.contains("fanout")){
System.out.println("接收扇形交换机中的数据!");
System.out.println("接收到队列:"+queue+"的消息!");
System.out.println("接收到的消息为:"+m.toString());
System.out.println("接收到的消息MessageId:"+messageId+",消息主体为:"+messageData+",接收到的时间为:"+createTime);
s = Action.delete;
}
}catch(Exception e){
e.printStackTrace();
s = Action.resend;
}finally{
if(s == Action.ack){//消息处理正常,就正常消费消息
channel.basicAck(key,true);
}else if(s == Action.resend){//若有处理异常或者是直连交换机中的数据,则拒绝消费,并将该消息再放入到队列中。
channel.basicReject(key,true);
}else{//若是扇形交换机,则拒绝消费,并且删除队列中的消息
channel.basicNack(key,false,false);
}
}
}
public Map<String,Object> stringToMap(String str){
str = str.substring(1,str.length()-1);
String[] messages = str.split(",");
Map<String,Object> m = new HashMap<>();
for (String message : messages) {
m.put(message.split("=")[0].trim(),message.split("=")[1]);
}
return m;
}
}
接口调用
调用发送数据到直连交换机的接口,MQ中就有一条数据未确认:
然后会执行到直连交换机的逻辑代码,并设置为拒绝消费,然后将数据放回队列中,count+1(否则会一直处于消费-拒绝-消费-拒绝的死循环中,导致了队列中的消息积压
)。
接收直连交换机中的数据!
接收到队列:directQueue的消息!
接收到的消息为:{createTime=2021-02-25 15:15, messageId=61cc56c7-fd66-444e-84ae-fb0557d42499, message=This is a first message!}
接收到的消息MessageId:61cc56c7-fd66-444e-84ae-fb0557d42499,消息主体为:This is a first message!,接收到的时间为:2021-02-25 15:15
第一次拒绝消费后,将count+1,第二次就要进行消费了
接收直连交换机中的数据!
接收到队列:directQueue的消息!
接收到的消息为:{createTime=2021-02-25 15:15, messageId=61cc56c7-fd66-444e-84ae-fb0557d42499, message=This is a first message!}
接收到的消息MessageId:61cc56c7-fd66-444e-84ae-fb0557d42499,消息主体为:This is a first message!,接收到的时间为:2021-02-25 15:15
directQueue的数据我要消费了!
查看MQ服务端,消费成功!
这段代码使用的是basic.reject(拒绝消费),其中有两个参数:
tagId:是消息通道的名称。
flag : 若为true,则会将消息再放入到队列中。若为false,则是告诉MQ,我不要这条消息了,删了吧。
看上面的代码,我加了count变量,用于将放回队列的消息再次进行消费,否则就会陷入死循环中了。始终都在消费这条数据,并且始终都消费不掉。
若调用扇形交换机的接口
会将消息发送到扇形交换机中所有的队列里,但是我只配置监听了firstFanoutQueue,并且把secondFanoutListener监听器注释掉了。
接收扇形交换机中的数据!
接收到队列:firstFanoutQueue的消息!
接收到的消息为:{createTime=2021-02-25 15:37, messageId=ad097199-b8ac-41cd-8725-eaac690946ea, message=This is a second message to Fanout!}
接收到的消息MessageId:ad097199-b8ac-41cd-8725-eaac690946ea,消息主体为:This is a second message to Fanout!,接收到的时间为:2021-02-25 15:37
CustomerFanoutThirdListener接受到的消息为:{createTime=2021-02-25 15:37, messageId=ad097199-b8ac-41cd-8725-eaac690946ea, message=This is a second message to Fanout!}
扇形交换机的消息我不要了!删了吧
看MQ服务端,可以看到firstFanoutQueue的数据被我拒绝消费了,此时没有数据。而secondFanoutQueue的数据,我并没有消费它,所以还存在。
若调用调用主题交换机的接口,就会直接确认消费,不会有其他的逻辑。
接收主题交换机中的数据!
接收到队列:firstTopicQueue的消息!
接收到的消息为:{createTime=2021-02-25 15:43, messageId=3992dadc-f7cd-446e-b7c4-46daea7e260f, message=This is a first message to topic : Man!}
接收到的消息MessageId:3992dadc-f7cd-446e-b7c4-46daea7e260f,消息主体为:This is a first message to topic : Man!,接收到的时间为:2021-02-25 15:43
以上代码,可以看出有三种确认消息的方式:
basic.ack、basic.reject、basic.nack
basic.ack :
basic.reject : 拒绝消费消息,可以选择拒绝消费后是否要将消息放回到队列中,还是直接删除。若第二个参数为true,则放回队列中。但是若选择这个模式的话,就需要注意放回队列的时机以及消费数据获取删除数据的时机。否则会该消息会一直处于消费-拒绝-消费-拒绝的道路。
basic.nack : 拒绝消费消息,有三个参数:
①deliveryTag,对应的消息通道的名字。
②flag,批量拒绝,若为true,则会将比当前tagId小的消息全部拒绝。
消息通道的名字,会在原来的tag上+1,也就是每个通道的deliveryTag都比上一个大。
③flag1,若为true,就会拒绝消费该消息,并将该消息再放回到队列中。
以上就是关于RabbitMQ的整理与总结了。