1.安装erlang
1.下载地址
https://www.erlang.org/downloads
2.环境变量
自定义安装路径,不要有中文路径
打开系统环境变量
1.新建变量
环境变量ERLANG_HOME
安装路径(到bin上一层)
2.变量配置
Path中新建
内容%ERLANG_HOME%\bin
2.安装rabbitMq
1.下载地址
https://github.com/rabbitmq/rabbitmq-server/releases
注意:rabbitMq的版本要和erlang版本对应 https://www.rabbitmq.com/which-erlang.html
2.安装
解压后安装,自定义安装路径,不要有中文路径
3.启动插件
到sbin目录下执行
.\rabbitmq-plugins enable rabbitmq_management
4.启动
访问 http://localhost:15672
如果访问不了
在sbin目录下执行
.\rabbitmqctl start_app
报错如下
去C盘用户目录下将 .erlang.cookie 文件复制到 C:\Windows\System32\config\systemprofile 目录下
然后再次打开http://localhost:15672
账户密码guest
guest
1.直连交换机
1.生产者
1.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">
<parent>
<artifactId>rabbitmqTest</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>producerService</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.5</version>
</dependency>
</dependencies>
</project>
2.配置类
package com.poplar.config;
import org.springframework.amqp.core.*;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 直连型交换机配置类
*
* @author yangyongjie
* @date 2023/11/3
*/
@Configuration
public class DirectRabbitExchangeConfig {
//创建队列
@Bean
public Queue TestDirectQueue() {
//共有五个参数 string boolean boolean boolean Map
//第一个是队列名称
//第二个是 durable: 是否持久化,默认是false;持久化的队列:会被储存在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
//第三个是 exclusive: 默认false,只能被当前连接使用,当连接关闭后队列即被删除,优先级高于durable
//第四个是 autoDelete: 是否自动删除,当没有生产者和消费者使用此队列,此队列会自动删除
//第五个是参数,可选
//参数个数: 1 2 4 5
//一般设置持久化即可,其他默认
return new Queue("directQueueTest", true);
}
//创建交换机
@Bean
DirectExchange TestDirectExchange() {
//共有四个参数 string boolean boolean Map
//一二三与Queue相同
//四为参数
//参数个数: 1 3 4
return new DirectExchange("directExchangeTest", true, false);
}
//将队列和交换机绑定
@Bean
Binding bindDirect() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("directRoutTest");
}
@Bean
DirectExchange lonelyDirectExchange() {
return new DirectExchange("lonelyDirectExchange");
}
}
3.controller
package com.poplar.controller;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 生产信息控制器
*
* @author yangyongjie
* @date 2023/11/3
*/
@RestController
public class ProductMessageController {
@Autowired
RabbitTemplate rabbitTemplate;
private final String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
private final Map<String, Object> map = new HashMap<>();
private final String messageId = String.valueOf(UUID.randomUUID());
private final Integer loopCounts = 10;
@GetMapping("/sendMessage")
public String sendDirectMessage() {
System.out.println("直连交换机开始工作");
String messageData = "test message, hello!";
for (int i = 0; i < loopCounts; i++) {
map.put("messageId", messageId);
map.put("messageData", messageData + "=>" + i);
map.put("createTime", createTime);
rabbitTemplate.convertAndSend("directExchangeTest", "directRoutTest", map);
Thread.sleep(1000);
}
System.out.println("直连交换机工作完成");
return "ok";
}
}
4.启动类
package com.poplar;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author yangyongjie
* @date 2023/11/3
*/
@SpringBootApplication
public class ProducterMain {
public static void main(String[] args) {
SpringApplication.run(producterMain.class);
}
}
5.postman测试
getlocalhost:9315/sendMessage
6.rabbitmq查看
可以看到自己在配置类中设置的队列
2.消费者
1.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">
<parent>
<artifactId>rabbitmqTest</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumerService</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.5</version>
</dependency>
</dependencies>
</project>
2.监听类
package com.poplar.listener;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 直连型交换机监听器
*
* @author yangyongjie
* @date 2023/11/6
*/
@Component
@RabbitListener(queues = "directQueueTest")//要监听的队列的名称
public class DirectReceiver {
@RabbitHandler
public void process(Map testMessage) {
System.out.println("直连交换机消费者接收到的消息:" + testMessage.get("messageData"));
}
}
3.启动类
package com.poplar;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author yangyongjie
* @date 2023/11/6
*/
@SpringBootApplication
public class ConsumerMain {
public static void main(String[] args) {
SpringApplication.run(ConsumerMain.class);
}
}
4.测试
启动消费者工程
到postman调用接口
可以看见每次有新的消息产生都会被监听到
5.多个消费者绑定同一个直连型交换机
1.新的监听器类
package com.poplar.listener;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 直连型交换机监听器2
*
* @author yangyongjie
* @date 2023/11/6
*/
@Component
@RabbitListener(queues = "directQueueTest")//要监听的队列的名称
public class DirectReceiver2 {
@RabbitHandler
public void process(Map testMessage) {
System.out.println("直连交换机消费者-2接收到的消息:" + testMessage.get("messageData"));
}
}
2.重新启动消费者服务
可以看到可以同时接收到同一个消息队列的消息且没有重复
2.主题交换机(Topic)
1.生产者
1.配置类
package com.poplar.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;
/**
* 主题交换机配置类
*
* @author yangyongjie
* @date 2023/11/6
*/
@Configuration
public class TopicRabbitExchangeConfig {
private final static String man_topic = "topic.man";
private final static String woman_topic = "topic.woman";
@Bean
public Queue firstManTopicQueue() {
return new Queue(TopicRabbitExchangeConfig.man_topic);
}
@Bean
public Queue secondWomanTopicQueue() {
return new Queue(TopicRabbitExchangeConfig.woman_topic);
}
@Bean
public Queue totalTopicQueue() {
return new Queue("topic.#");
}
@Bean
TopicExchange topicExchange() {
return new TopicExchange("topicExchangeTest");
}
//将firstManTopicQueue和topicExchange绑定,而且绑定的键值为topic.man
//这样只要是消息携带的路由键是topic.man,才会分发到该队列
@Bean
Binding bindingExchangeMessageMan() {
return BindingBuilder.bind(firstManTopicQueue()).to(topicExchange()).with(man_topic);
}
//将secondWomanTopicQueue和topicExchange绑定,而且绑定的键值为topic.woman
//这样只要是消息携带的路由键是topic.woman,才会分发到该队列
@Bean
Binding bindingExchangeMessageWoman() {
return BindingBuilder.bind(secondWomanTopicQueue()).to(topicExchange()).with(woman_topic);
}
//将totalTopicQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#
// 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列
@Bean
Binding bindingExchangeMessageSum() {
return BindingBuilder.bind(totalTopicQueue()).to(topicExchange()).with("topic.#");
}
}
2.controller
新建两个接口
@GetMapping("/topicMan")
public String sendTopicMessage1() throws InterruptedException {
System.out.println("主题交换机-man开始工作");
String messageData = "message: man";
for (int i = 0; i < loopCounts; i++) {
map.put("messageId", messageId);
map.put("messageData", messageData + "=>" + i);
map.put("createTime", createTime);
rabbitTemplate.convertAndSend("topicExchangeTest", "topic.man", map);
Thread.sleep(1000);
}
System.out.println("主题交换机-man工作完成");
return "ok";
}
@GetMapping("/topicWoman")
public String sendTopicMessage2() throws InterruptedException {
System.out.println("主题交换机-woman开始工作");
String messageData = "message: woman";
for (int i = 0; i < loopCounts; i++) {
map.put("messageId", messageId);
map.put("messageData", messageData + "=>" + i);
map.put("createTime", createTime);
rabbitTemplate.convertAndSend("topicExchangeTest", "topic.woman", map);
Thread.sleep(1000);
}
System.out.println("主题交换机-woman工作完成");
return "ok";
}
2.消费者
1.监听器
新建三个监听器监听不同的队列(topic.man topic.woman topic.#)
package com.poplar.listener;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 主题型交换机-man主题
*
* @author yangyongjie
* @date 2023/11/6
*/
@Component
@RabbitListener(queues = "topic.man")
public class TopicManReceiver {
@RabbitHandler
public void process(Map testMessage) {
System.out.println("主题交换机消费者已收到消息-man-" + testMessage.get("messageData"));
}
}
package com.poplar.listener;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 主题型交换机-woman主题
*
* @author yangyongjie
* @date 2023/11/6
*/
@Component
@RabbitListener(queues = "topic.woman")
public class TopicWomanReceiver {
@RabbitHandler
public void process(Map testMessage) {
System.out.println("主题交换机消费者已收到消息-woman-" + testMessage.get("messageData"));
}
}
package com.poplar.listener;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 主题型交换机-总主题
*
* @author yangyongjie
* @date 2023/11/6
*/
@Component
@RabbitListener(queues = "topic.#")
public class TopicTotalReceiver {
@RabbitHandler
public void process(Map testMessage) {
System.out.println("主题交换机消费者已收到消息-total-" + testMessage.get("messageData"));
}
}
3.测试
postman调用两个接口
查看监听
3.扇形交换机(Fanout)
1.生产者
1.配置类
package com.poplar.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 扇型交换机配置类
*
* @author yangyongjie
* @date 2023/11/6
*/
@Configuration
public class FanoutRabbitExchangeConfig {
//创建三个队列 fanout.A fanout.B fanout.C
//将三个队列均绑定在交换机fanoutExchange上
//因为是扇形交换机,所以路由键无需配置,配置也没用
@Bean
public Queue queueA() {
return new Queue("fanout.A");
}
@Bean
public Queue queueB() {
return new Queue("fanout.B");
}
@Bean
public Queue queueC() {
return new Queue("fanout.C");
}
@Bean
FanoutExchange fanoutExchangeGenerate() {
return new FanoutExchange("fanoutExchangeTest");
}
@Bean
Binding bindingFanoutA() {
return BindingBuilder.bind(queueA()).to(fanoutExchangeGenerate());
}
@Bean
Binding bindingFanoutB() {
return BindingBuilder.bind(queueB()).to(fanoutExchangeGenerate());
}
@Bean
Binding bindingFanoutC() {
return BindingBuilder.bind(queueC()).to(fanoutExchangeGenerate());
}
}
2.消费者
1.监听器
新建三个监听器
package com.poplar.listener;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 扇型交换机接收器-A
*
* @author yangyongjie
* @date 2023/11/6
*/
@Component
@RabbitListener(queues = "fanout.A")
public class FanoutReceiverA {
@RabbitHandler
public void process(Map messageMap) {
System.out.println("扇形交换机-A消费者接收到的消息-" + messageMap.get("messageData"));
}
}
package com.poplar.listener;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 扇型交换机接收器-B
*
* @author yangyongjie
* @date 2023/11/6
*/
@Component
@RabbitListener(queues = "fanout.B")
public class
{
@RabbitHandler
public void process(Map messageMap) {
System.out.println("扇形交换机-B消费者接收到的消息-" + messageMap.get("messageData"));
}
}
package com.poplar.listener;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 扇型交换机接收器-C
*
* @author yangyongjie
* @date 2023/11/6
*/
@Component
@RabbitListener(queues = "fanout.C")
public class FanoutReceiverC {
@RabbitHandler
public void process(Map messageMap) {
System.out.println("扇形交换机-C消费者接收到的消息-" + messageMap.get("messageData"));
}
}
3.测试
调用接口
这时候可以看到
4.消息回调(消息确认)
所谓消息回调,就是指生产者推送消息成功,消费者接收消息成功
消息->server->交换机->队列
1.生产者
1.配置
server:
port: 9315
spring:
application:
name: test-rabbitmq-producer
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
#回调
publisher-returns: true
publisher-confirm-type: correlated
2.测试
1.server收到消息,但是没有转换机
新写一个接口
//测试消息到server没找到交换机
@GetMapping("/testMessageAck")
public String TestMessageAck() {
String messageData = "message: non-existent-exchange test message ";
map.put("messageId", messageId);
map.put("messageData", messageData);
map.put("createTime", createTime);
rabbitTemplate.convertAndSend("non-existent-exchange", "directRoutTest", map);
return "ok";
}
用postman调用后
2.server收到消息,有交换机但是没有队列
在DirectRabbitExchangeConfig类中新建一个交换机
//创建交换机但是不绑定队列
@Bean
DirectExchange TestNoQueueBind() {
return new DirectExchange("directExchangeTest2", true, false);
}
新写一个接口
//测试消息到server,找到交换机,但是没队列
@GetMapping("/testMessageAck2")
public String TestMessageAck2() {
String messageData = "message: directExchangeTest2 test message ";
map.put("messageId", messageId);
map.put("messageData", messageData);
map.put("createTime", createTime);
rabbitTemplate.convertAndSend("directExchangeTest2", "directRoutTest", map);
return "ok";
}
使用postman调用后
3.既没有交换机也没有队列
内容等同于没有交换机
4.成功
即调用成功效果
2.消费者
与生产者不同,消费者本身就是在监听消息,然后消费符合条件的消息。所以消费者的消息接收确认机制有三种
1.分类
1.自动确认
这是默认的消息确认方式 AcknowledgeMode.NONE
2.根据情况确认
3.手动确认
消费者收到消息之后,手动调用basic.ack
/basic.nack
/basic.reject
,rabbitMQ在收到这些消息之后才认为本次投递成功
basic.ack
表示肯定确认
basic.nack
表示否定确认
basic.reject
表示否定确认,与basic.nack
不同在于basic.reject
一次只能拒绝一条消息
消费者端以上三种方法都认为消息已经成功投递,但是只有basic.ack
表示消息已经正确处理
reject:两个参数,一个是当前消息的唯一id,另一个是是否可以重新入列
nack:三个参数,一个是当前消息的唯一id,一个是是否针对多条消息,最后一个是是否重新入列
2.测试
1.配置类
package com.poplar.config;
import com.poplar.listener.MyAckReceiver;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
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;
/**
* 消费者端手动确认消息配置类
*
* @author yangyongjie
* @date 2023/11/6
*/
@Configuration
public class MessageListenerConfig {
@Autowired
private CachingConnectionFactory connectionFactory;
@Autowired
private MyAckReceiver myAckReceiver;
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer() {
SimpleMessageListenerContainer simpleMessageListenerContainer = new SimpleMessageListenerContainer(connectionFactory);
simpleMessageListenerContainer.setConcurrentConsumers(1);
simpleMessageListenerContainer.setMaxConcurrentConsumers(1);
simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);//设置手动
simpleMessageListenerContainer.setQueueNames("directQueueTest");//可以是多个,用逗号分割
simpleMessageListenerContainer.setMessageListener(myAckReceiver);
return simpleMessageListenerContainer;
}
}
2.自己的实现类
package com.poplar.listener;
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;
/**
* @author yangyongjie
* @date 2023/11/6
*/
@Component
public class MyAckReceiver implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
String msg = message.toString();
String[] split = msg.split("'");
Map<String, String> messageMap = mapStringToMap(split[1].trim());
String messageId = messageMap.get("messageId");
String messageData = messageMap.get("messageData");
String createTime = messageMap.get("createTime");
//message.getMessageProperties().getConsumerQueue() 获取队列名称
System.out.println("MyAckReceiver messageId:" + messageId + "-messageData:" + messageData + "-createTime:" + createTime);
System.out.println("消息主题" + message.getMessageProperties().getConsumerQueue());
channel.basicAck(deliveryTag, true);//true表示可以被批处理
} catch (Exception e) {
channel.basicReject(deliveryTag, false);
System.out.println(e.getMessage());
}
}
//将{key=value,key=value,key=value} 格式转换成map
private Map<String, String> mapStringToMap(String str) {
String substring = str.substring(1, str.length() - 1);
String[] split = substring.split(",", 3);
Map<String, String> result = new HashMap<>();
for (String s : split) {
String[] split1 = s.split("=");
result.put(split1[0], split1[1]);
}
return result;
}
}