基于Springboot搭建java项目(十四)——SpringBoot集成RabbitMq

Springboot 集成RabbitMQ

一、RabbitMQ的介绍和安装

1、一些概念

RabbitMQ是一个开源的消息代理和队列服务器,用来实现各个应用服务间的数据共享(跨平台 ,跨语言)。RabbitMQ是使用erlang语言编写的,并且基于AMQP协议实现。

所有的消息队列产品模型抽象上来说,都是类似的过程。生产者创建消息,然后发布到消息队列中,由消费者进行消费。

而rabbitMQ也是类似的,有生产者,消费者角色。其内部结构如下图所示。

基于Springboot搭建java项目(十四)——SpringBoot集成RabbitMq_第1张图片

那么接下来我们就来介绍一下RabbitMQ中的这些概念。

  1. Message:

消息,就是我们需要传递和共享的信息,消息由一些列的可选属性组成,包括路由键,优先级,是否持久化等信息

  1. Publisher

消息的生产者,也是一个向交换机发布消息的客户端应用程序。

  1. Exchange:

交换机,这是RabbitMQ中的一个非常重要的概念,在rabbitMq中,生产者产生的消息都不是直接发送到队列中去的,而是发送到了交换机中,交换机会通过一定的规则绑定队列,交换机会根据相应的路由规则发送给对服务器中的队列。

  1. Binding:

绑定, 用于交换机和消息列队之间的关联。一个绑定就是基于路由键(routing-key)将交换机和消息队列连接起来的路由规则。所以可以将交换机理解成一个有绑定有成的路由表。

  1. Queue:

消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可以投入一个或多个队列中。消息一直在对队列里边,等待消费者连接到这个队列将其消费。

  1. Connection:

网络连接,比如一个TCP连接。

  1. Channel

信道,多路复用连接中的一条独立的双向数据流通道。信道是简历在真实的TCP连接内的虚拟连接。AMQP命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成的。因为对于操作系统过来说建立和销毁TCP都是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP连接。

  1. Consumer

消息的消费者,表示一个从消息队列中取得消息的客户端应用。

  1. Virtual Host

虚拟主机,标识一批交换机、消息队列和相关对象。 虚拟主机是相同的身份认证和加密环境的独立服务器域。 每个vhost本质就是一个mini版的rabbitMQ服务器,拥有自己的队列,交换机,绑定和权限机制。vhost是AMQP概念的基础,必须在连接时指定,RabbitMQ的默认vhost是/.

  1. Broker

标识消息队列服务器实体。

2、Exchange类型

Exchange分发消息的时候根据类型的不同分发策略有所区别,目前常见的有四种类型: direct、fanout、topic、headers。 headers匹配AMQP消息的header而不是路由键,此外headers交换机和direct交换机完成一直但是性能差很多,几乎用不到了,所以直接看另外三种类型。

2.1 direct交换机

基于Springboot搭建java项目(十四)——SpringBoot集成RabbitMq_第2张图片

消息中的路由键(routing key)如果和Binding中的bing key一致,交换机就将消息发送到队列的队列中。路由键要完全匹配,单个传播。

2.2 fanout交换机

基于Springboot搭建java项目(十四)——SpringBoot集成RabbitMq_第3张图片

每个发到fanout类型交换机的消息都会分到所有绑定的队列上去。fanout交换器不处理路由键,只是简单的将队列绑定到交换机上,每个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout类型转发消息是最快的。

2.3 topic交换机

基于Springboot搭建java项目(十四)——SpringBoot集成RabbitMq_第4张图片

topic交换机通过模式匹配分配路由的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用.隔开。它同样会识别两个通配符: # 和* 。 #匹配0个或多个单词, * 匹配一个单词

RabbitMQ的介绍、下载和安装以及可视化的页面的使用请参考:https://wanght.blog.csdn.net/article/details/102810522

在Linux服务器上有安装Docker的情况下推荐使用docker安装部署,简单方便。

二、集成RabbitMQ

利用springboot框架集成RabbitMq。

1、maven导入jar

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-amqpartifactId>
        dependency>

2、yaml配置文件

spring:
  #配置rabbitMq 服务器
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: root
    password: root
    #虚拟host 可以不设置,使用server默认host
    virtual-host: JCcccHost

3、properties配置文件

spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=root
spring.rabbitmq.password=root
spring.rabbitmq.publisher-confirm-type=correlated
spring.rabbitmq.publisher-returns=true

4、RabbitConfig

package com.it520.bookkeeping.config;

import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 * @Author : huliupan
 * @CreateTime : 2022-10-13
 * @Description : RabbitMQ的config
 **/
@Configuration
public class RabbitConfig {

    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
        rabbitTemplate.setMandatory(true);

        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("ConfirmCallback:     "+"相关数据:"+correlationData);
                System.out.println("ConfirmCallback:     "+"确认情况:"+ack);
                System.out.println("ConfirmCallback:     "+"原因:"+cause);
            }
        });

        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            @Override
            public void returnedMessage(ReturnedMessage returnedMessage) {
                System.out.println("ReturnCallback:     "+"消息:"+returnedMessage.getMessage());
                System.out.println("ReturnCallback:     "+"回应码:"+returnedMessage.getReplyCode());
                System.out.println("ReturnCallback:     "+"回应信息:"+returnedMessage.getReplyText());
                System.out.println("ReturnCallback:     "+"交换机:"+returnedMessage.getExchange());
                System.out.println("ReturnCallback:     "+"路由键:"+returnedMessage.getRoutingKey());
            }
        });

        return rabbitTemplate;
    }

}

三、RabbitMQ的使用

1、 direct交换机(路由模式)

完全匹配 > 当消息的routing-key 与 exchange和queue间的binding-key完全匹配时,将消息分发到该queue

首先是配置类,在配置类中我们需要声明交换机,队列和绑定关系。

package com.it520.bookkeeping.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 * @Author : huliupan
 * @CreateTime : 2022/10/13
 * @Description :直连交换机的配置
 **/
@Configuration
public class DirectRabbitConfig {

    public static final String DIRECT_EXCHANGE_NAME = "TestDirectExchange";
    public static final String DIRECT_QUEUE_NAME = "TestDirectQueue";
    public static final String DIRECT_QUEUE_NAME1 = "TestDirectQueue1";
    public static final String DIRECT_EXCHANGE_ROUTE_KEY = "TestDirectRouting";

    //队列 起名:TestDirectQueue
    @Bean
    public Queue TestDirectQueue() {
        // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
        // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
        //   return new Queue("TestDirectQueue",true,true,false);

        //一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue(DIRECT_QUEUE_NAME, true);
    }

    //队列 起名:TestDirectQueue
    @Bean
    public Queue TestDirectQueue1() {
        return new Queue(DIRECT_QUEUE_NAME1, true);
    }

    //Direct交换机 起名:TestDirectExchange
    @Bean
    DirectExchange TestDirectExchange() {
        //  return new DirectExchange("TestDirectExchange",true,true);
        return new DirectExchange(DIRECT_EXCHANGE_NAME, true, false);
    }

    //绑定  将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting
    @Bean
    Binding bindingDirect() {
        return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with(DIRECT_EXCHANGE_ROUTE_KEY);
    }

    @Bean
    Binding bindingDirect1() {
        return BindingBuilder.bind(TestDirectQueue1()).to(TestDirectExchange()).with(DIRECT_EXCHANGE_ROUTE_KEY);
    }

    @Bean
    DirectExchange lonelyDirectExchange() {
        return new DirectExchange("lonelyDirectExchange");
    }

}

这里我们创建了一个叫TestDirectExchange的交换机,绑定了TestDirectQueue和TestDirectQueue1两个队列.

消息的生产者,我们通过一个Controller来进行模拟,直接引用rabbitTemplate

package com.it520.booking.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 : huliupan
 * @CreateTime : 2022/10/13
 * @Description :
 **/
@RestController
public class SendMessageController {

    @Autowired
    RabbitTemplate rabbitTemplate;  //使用RabbitTemplate,这提供了接收/发送等等方法

    @GetMapping("/sendDirectMessage")
    public String sendDirectMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "test message, hello!";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map<String,Object> map=new HashMap<>();
        map.put("messageId",messageId);
        map.put("messageData",messageData);
        map.put("createTime",createTime);
        //将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
        rabbitTemplate.convertAndSend(DIRECT_EXCHANGE_NAME, "TestDirectRouting", map);
        return "ok";
    }

}

将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange

这事我们就要创建消费者来对发送到队列的消息进行消费。

package com.it520.bookkeeping.receiver;

import com.it520.bookkeeping.config.DirectRabbitConfig;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class DirectReceiver {

    @RabbitHandler
    @RabbitListener(queues = DirectRabbitConfig.DIRECT_QUEUE_NAME)//监听的队列名称 TestDirectQueue
    public void process(Map testMessage) {
        System.out.println("DirectReceiver消费者1收到消息  : " + testMessage.toString());
    }

    @RabbitHandler
    @RabbitListener(queues = DirectRabbitConfig.DIRECT_QUEUE_NAME)//监听的队列名称 TestDirectQueue
    public void process1(Map testMessage) {
        System.out.println("DirectReceiver消费者2收到消息  : " + testMessage.toString());
    }

    @RabbitHandler
    @RabbitListener(queues = DirectRabbitConfig.DIRECT_QUEUE_NAME1)//监听的队列名称 TestDirectQueue1
    public void process2(Map testMessage) {
        System.out.println("DirectReceiver消费者3收到消息  : " + testMessage.toString());
    }

}

调用发送信息的接口DirectReceiver消费者1和DirectReceiver消费者2随机消费消息,DirectReceiver消费者3也会消费消息

2 、fanout交换机(订阅模式)

与binding-key和routing-key无关,将接受到的消息分发给有绑定关系的所有队列(不论binding-key和routing-key是什么)

配置交换机:

package com.it520.bookkeeping.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 : huliupan
 * @CreateTime : 2022/10/13
 * @Description :Fanout交换机配置
 **/

@Configuration
public class FanoutRabbitConfig {
    
    public static final String FANOUT_EXCHANGE_NAME = "fanoutExchange";
    public static final String FANOUT_QUEUE_A = "fanout.A";
    public static final String FANOUT_QUEUE_B = "fanout.B";
    public static final String FANOUT_QUEUE_C = "fanout.C";

    /**
     *  创建三个队列 :fanout.A   fanout.B  fanout.C
     *  将三个队列都绑定在交换机 fanoutExchange 上
     *  因为是扇型交换机, 路由键无需配置,配置也不起作用
     */


    @Bean
    public Queue queueA() {
        return new Queue(FANOUT_QUEUE_A);
    }

    @Bean
    public Queue queueB() {
        return new Queue(FANOUT_QUEUE_B);
    }

    @Bean
    public Queue queueC() {
        return new Queue(FANOUT_QUEUE_C);
    }

    @Bean
    FanoutExchange fanoutExchange() {
        return new FanoutExchange(FANOUT_EXCHANGE_NAME);
    }

    @Bean
    Binding bindingExchangeA() {
        return BindingBuilder.bind(queueA()).to(fanoutExchange());
    }

    @Bean
    Binding bindingExchangeB() {
        return BindingBuilder.bind(queueB()).to(fanoutExchange());
    }

    @Bean
    Binding bindingExchangeC() {
        return BindingBuilder.bind(queueC()).to(fanoutExchange());
    }
}

这里也是用一个Fanout类型的交换机绑定了三个队列,要注意在这种模式下,是不需要指定routing-Key的,因为所有绑定的队列都会收到消息。

生产者代码如下:

    @GetMapping("/sendFanoutMessage")
    public String sendFanoutMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "message: testFanoutMessage ";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map<String, Object> map = new HashMap<>();
        map.put("messageId", messageId);
        map.put("messageData", messageData);
        map.put("createTime", createTime);
        rabbitTemplate.convertAndSend(FanoutRabbitConfig.FANOUT_EXCHANGE_NAME, null, map);
        return "ok";
    }

消息的消费者:

package com.it520.bookkeeping.receiver;

import com.it520.bookkeeping.config.FanoutRabbitConfig;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @Author : huliupan
 * @CreateTime : 2022/10/13
 * @Description :Fanout交换机的消费者
 **/
@Component
public class FanoutReceiver {

    @RabbitHandler
    @RabbitListener(queues = FanoutRabbitConfig.FANOUT_QUEUE_A)
    public void processA(Map testMessage) {
        System.out.println("FanoutReceiverA消费者收到消息  : " +testMessage.toString());
    }

    @RabbitHandler
    @RabbitListener(queues = FanoutRabbitConfig.FANOUT_QUEUE_B)
    public void processB(Map testMessage) {
        System.out.println("FanoutReceiverB消费者收到消息  : " +testMessage.toString());
    }

    @RabbitHandler
    @RabbitListener(queues = FanoutRabbitConfig.FANOUT_QUEUE_C)
    public void processC(Map testMessage) {
        System.out.println("FanoutReceiverC消费者收到消息  : " +testMessage.toString());
    }


}

调用发送消息的接口,三个消费者都能接收到消息

3、 topic交换机(主题交换机)

会根据routing-Key的匹配规则,将消息发送到符合规则的队列中。

配置类:

package com.it520.bookkeeping.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 : huliupan
 * @CreateTime : 2022/10/13
 * @Description :Topic交换机配置
 **/

@Configuration
public class TopicRabbitConfig {
    //绑定键
    public final static String TOPIC_MAN = "topic.man";
    public final static String TOPIC_WOMAN = "topic.woman";
    public final static String TOPIC_QUEUE_1 = "topic.queue1";
    public final static String TOPIC_QUEUE_2 = "topic.queue2";
    public final static String TOPIC_QUEUE_3 = "topic.queue3";
    public final static String TOPIC_JH = "topic.#";
    public final static String TOPIC_XH = "topic.*";
    public final static String TOPIC_ONLY_JH = "#";
    public final static String TOPIC_EXCHANGE_NAME = "topicExchange";

    @Bean
    public Queue firstQueue() {
        return new Queue(TopicRabbitConfig.TOPIC_MAN);
    }

    @Bean
    public Queue secondQueue() {
        return new Queue(TopicRabbitConfig.TOPIC_WOMAN);
    }

    @Bean
    public Queue threeQueue() {
        return new Queue(TopicRabbitConfig.TOPIC_QUEUE_1);
    }

    @Bean
    public Queue fourQueue() {
        return new Queue(TopicRabbitConfig.TOPIC_QUEUE_2);
    }

    @Bean
    public Queue fiveQueue() {
        return new Queue(TopicRabbitConfig.TOPIC_QUEUE_3);
    }

    @Bean
    TopicExchange exchange() {
        return new TopicExchange(TOPIC_EXCHANGE_NAME);
    }


    //将firstQueue和topicExchange绑定,而且绑定的键值为topic.man
    //这样只要是消息携带的路由键是topic.man,才会分发到该队列
    @Bean
    Binding bindingExchangeMessage() {
        return BindingBuilder.bind(firstQueue()).to(exchange()).with(TOPIC_MAN);
    }

    //将secondQueue和topicExchange绑定,而且绑定的键值为topic.woman
    //这样只要是消息携带的路由键是topic.woman,才会分发到该队列
    @Bean
    Binding bindingExchangeMessage1() {
        return BindingBuilder.bind(secondQueue()).to(exchange()).with(TOPIC_WOMAN);
    }

    //将threeQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#
    // 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列
    @Bean
    Binding bindingExchangeMessage2() {
        return BindingBuilder.bind(threeQueue()).to(exchange()).with(TOPIC_JH);
    }

    //将fourQueue和topicExchange绑定,而且绑定的键值为topic.*
    //这样只要是消息携带的路由键是topic.接一个字段,才会分发到该队列
    @Bean
    Binding bindingExchangeMessage3() {
        return BindingBuilder.bind(fourQueue()).to(exchange()).with(TOPIC_XH);
    }

    //将fiveQueue和topicExchange绑定,而且绑定的键值为*
    //这样只要是消息携带的路由键是所有,都会分发到该队列
    @Bean
    Binding bindingExchangeMessage4() {
        return BindingBuilder.bind(fiveQueue()).to(exchange()).with(TOPIC_ONLY_JH);
    }
}

交换机绑定了五个队列,分别接收的五个不同ROUTE_KEY的值

消息发送者的请求代码:

    @GetMapping("/sendTopicMessage")
    public String sendTopicMessage(@RequestParam("routeKey") String routeKey) {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "message: "+ routeKey;
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map<String, Object> manMap = new HashMap<>();
        manMap.put("messageId", messageId);
        manMap.put("messageData", messageData);
        manMap.put("createTime", createTime);
        rabbitTemplate.convertAndSend(TopicRabbitConfig.TOPIC_EXCHANGE_NAME, routeKey, manMap);
        return "ok";
    }

建立消息的消费者

package com.it520.bookkeeping.receiver;

import com.it520.bookkeeping.config.TopicRabbitConfig;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @Author : huliupan
 * @CreateTime : 2022/10/13
 * @Description :Topic交换机的消费者
 **/

@Component
public class TopicReceiver {

    @RabbitHandler
    @RabbitListener(queues = TopicRabbitConfig.TOPIC_MAN)
    public void process(Map testMessage) {
        System.out.println("TopicTotalReceiver消费者man收到消息  : " + testMessage.toString());
    }


    @RabbitHandler
    @RabbitListener(queues = TopicRabbitConfig.TOPIC_WOMAN)
    public void process1(Map testMessage) {
        System.out.println("TopicTotalReceiver消费者woman收到消息  : " + testMessage.toString());
    }


    @RabbitHandler
    @RabbitListener(queues = TopicRabbitConfig.TOPIC_QUEUE_1)
    public void process2(Map testMessage) {
        System.out.println("TopicTotalReceiver消费者topic.#收到消息  : " + testMessage.toString());
    }


    @RabbitHandler
    @RabbitListener(queues = TopicRabbitConfig.TOPIC_QUEUE_2)
    public void process3(Map testMessage) {
        System.out.println("TopicTotalReceiver消费者topic.*收到消息  : " + testMessage.toString());
    }


    @RabbitHandler
    @RabbitListener(queues = TopicRabbitConfig.TOPIC_QUEUE_3)
    public void process4(Map testMessage) {
        System.out.println("TopicTotalReceiver消费者*收到消息  : " + testMessage.toString());
    }
}

可以通过接口的传参来控制ROUTE_KEY来观察消费者的消费情况

你可能感兴趣的:(java-rabbitmq,rabbitmq,java,spring,boot)