springboot整合rabbitmq

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
image.png

4.启动

访问 http://localhost:15672
如果访问不了
在sbin目录下执行
.\rabbitmqctl start_app
报错如下
image.png
去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
image.png

6.rabbitmq查看

image.png
可以看到自己在配置类中设置的队列

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调用接口
image.png
可以看见每次有新的消息产生都会被监听到
image.png

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.重新启动消费者服务

image.png
可以看到可以同时接收到同一个消息队列的消息且没有重复


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调用两个接口
image.png
image.png
查看监听
image.png
image.png
image.png

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.测试

调用接口
image.png
这时候可以看到
image.png
image.png

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调用后
image.png

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调用后
image.png

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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值