RabbitMQ(MQ介绍,RabbitMQ安装,SpringAMQP,消息json转换,@Bean方式声明队列和交换机)

目录

一、RabbitMQ介绍

1. MQ介绍

1 同步调用与异步调用

同步调用

异步调用

2. MQ介绍

什么是MQ

MQ的作用

3. 常见的MQ

2. RabbitMQ介绍

1 AMQP协议

2 RabbitMQ

3. 小结

二、RabbitMQ安装

1. 拉取RabbitMQ镜像

2. 安装RabbitMQ

3. RabbitMQ控制台

三、SpringAMQP

1. 准备代码环境

2. RabbitMQ工作模式

3. basic queue简单队列【了解】

4. work queues工作队列【了解】

5. Publish/Subscribe发布订阅(Fanout)

6. Direct(Routing)

7. Topic【重点】

8. 小结

四、其它内容

1. @Bean方式声明队列和交换机【掌握】

2. 消息json格式转换器

3. 小结

大总结


一、RabbitMQ介绍

1. MQ介绍

Message Queue消息队列

1 同步调用与异步调用

程序里所有的通信,有两种形式:

  • 同步通信:通信时必须得到结果才会执行下一步

  • 异步通信:通信时不必等待结果,可以直接处理下一步

同步调用

同步调用的缺点:

  • 业务链长,消耗时间增加,用户体验不好

  • 耦合性强

  • 流量洪峰服务器压力大

同步调用的好处:

  • 时效性强,可以立即得到结果

异步调用

异步调用的好处:

  • 异步调用,调用链短,用户等待时间短,体验好

  • 降低耦合,服务之间耦合性低了,任何一个服务出现问题,对其它服务的影响都非常小

  • 削峰填谷,中间件Broker具备一定的消息堆积与缓存能力,下游服务可以根据自己的能力慢慢处理

异步调用的坏处:

  • 业务复杂度增加:需要考虑数据一致性问题、消息丢失问题、消息重复消费问题、消息的顺序问题等等

  • 架构复杂度增加:对中间件Broker的依赖性增强了,必须保证Broker的高可用;一旦Broker出错,会对整个系统造成非常大的冲击

2. MQ介绍

什么是MQ

Message Queue,消息队列

消息中间件利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。

MQ的作用
  • 异步:实现服务之间异步通信

  • 削峰:实现流量的削峰填谷

  • 解耦:实现服务之间的耦合性降低

3. 常见的MQ

在使用MQ时:

  • 追求可用性:Kafka、 RocketMQ 、RabbitMQ,都支持集群部署

  • 追求可靠性:RocketMQ(或RabbitMQ)

  • 追求吞吐能力:Kafka(或RocketMQ)

  • 追求消息低延迟:RabbitMQ(或Kafka)

2. RabbitMQ介绍

1 AMQP协议

AMQP,Advanced Message Queuing Protocol,高级消息队列,是一种网络协议。它是应用层协议的一个开发标准,为面向消息的中间件而设计。

基于此协议的客户端与消息中间件可传递消息,并不受客户端、中间件不同产品、不同编程语言的限制。

  • Publisher:消息发布者

  • Exchange:交换机。用于分发消息

  • Routes:路由。消息被分发到目标队列上的过程

  • Queue:消息队列。用于存储消息数据

  • Consumer:消息消费者。从队列里取出消息、处理消息

2 RabbitMQ

Rabbit公司基于AMQP协议标准,开发了RabbitMQ1.0。RabbitMQ采用Erlang语言开发,Erlang是专门为开发高并发和分布式系统的一种语言,在电信领域广泛使用。

官网地址:RabbitMQ: One broker to queue them all | RabbitMQ

  • Broker:消息中间件,指的就是RabbitMQ服务器

  • Virtual Host:虚拟主机。当多个不同用户使用同一个RabbitMQ服务时,可以划分出多个vhost,每个用户在自己的vhost内创建exchange和queue等,互不干扰

  • Connection:消息生产者、消费者 与 RabbitMQ之间建立的TCP连接

  • Channel:Channel作为轻量级的Connection,可以极大的减少建立Connection的开销。

    如果每一次访问RabbitMQ都建立一个TCP Connection,将会造成巨大的性能开销,性能非常低下。

    Channel是在Connection内部建立的逻辑连接,Channel之间完全隔离。如果应用程序支持多线程,通常每个线程创建独立的Channel进行通讯, AMQP method包含了channel id,帮助客户端和broker识别channel,所以channel之间是完全隔离的

  • Exchange:message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到queue中去。常用的类型有:direct(point-to-point), topic(publish-subscribe),fanout(multicast)

  • Queue:消息最终被送到这里,等待Consumer取走

  • Binding:exchange和queue之间的虚拟连接,binding中可以包含routing key。Binding信息被保存到exchange的查询表中,用作message的分发依据

3. 小结

 

MQ是什么:是消息队列

MQ的好处:

  • 异步:服务之间实现异步通信(异步交互传输数据)

  • 削峰:可以堆积消息,应用流量洪峰

  • 解耦:服务之间的耦合性降低了

MQ的缺点:

  • 业务复杂度增加:防止消息丢失、消息重复,要保证数据的一致性等等问题

  • 系统架构复杂度增加:必须保证MQ的高可用

RabbitMQ的几个概念:

  • Producer:生产者,是发送消息的代码

  • Consumer:消费者,是接收消息的代码

  • Broker:中间件,指的就是RabbitMQ

  • Exchange:交换机,作用是路由消息到队列

  • Queue:队列,真正存储消息的队列

  • VirtualHost:虚拟主机。每个虚拟主机里可以有多个交换机和队列, 不同虚拟主机之间互相隔离

二、RabbitMQ安装

1. 拉取RabbitMQ镜像

方式一:在线拉取镜像

docker pull rabbitmq:3.8-management

方式二:从本地加载镜像【我们采用这种方式】

把资料中的《mq.tar》上传到虚拟机CentOS里

在CentOS里执行命令加载镜像:

#先切换到mq.tar所在的目录

#再执行命令加载镜像:已经加载过了,不需要重复加载
docker load -i mq.tar

#加载后,查看一下镜像。找一下有没有rabbitmq这个镜像
docker images

2. 安装RabbitMQ

执行下面的命令来运行MQ容器:

docker run \
 -e RABBITMQ_DEFAULT_USER=itcast \
 -e RABBITMQ_DEFAULT_PASS=123321 \
 -v mq-plugins:/plugins \
 --name mq \
 --hostname mq \
 -p 15672:15672 \
 -p 5672:5672 \
 -d \
 --restart=always \
 rabbitmq:3-management

  • Java程序连接RabbitMQ:使用端口5672

  • RabbitMQ控制台页面: http://ip:15672, 登录帐号:itcast, 密码:123321

3. RabbitMQ控制台

打开浏览器输入地址:http://ip:15672, 登录帐号:itcast, 密码:123321

在控制台里,可以查看、管理 交换机、队列等等

三、SpringAMQP

1. 准备代码环境

SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。

SpringAmqp的官方地址:Spring AMQP

  • AMQP:规定了 一个MQ技术应该实现的规范

  • SpringAMQP:是 Java代码收发消息的 API规范

SpringAMQP提供了三个功能:

  • 自动声明队列、交换机及其绑定关系

  • 基于注解的监听器模式,异步接收消息

  • 封装了RabbitTemplate工具,用于发送消息

为了演示SpringAMQP的功能,我们需要先准备代码环境,步骤如下:

  1. 创建project,删除其src目录,然后添加依赖

  2. 创建生产者模块

  3. 创建消费者模块

1 创建project

创建maven类型的project,不选择骨架,直接设置工程坐标然后下一步

创建后,删除src目录

修改pom.xml添加依赖坐标如下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.9.RELEASE</version>
</parent>

<properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
</properties>

<dependencies>
    <!--AMQP依赖,包含RabbitMQ-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</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>

2 创建生产者模块

在project里创建module,起名称为demo-producer

依赖

不需要添加依赖,父工程里已经导入了依赖,子模块直接继承父工程里的依赖就足够了

配置

修改配置文件application.yaml

 spring:
  application:
    name: demo-producer
  rabbitmq:
    host: 192.168.200.137 #RabbitMQ服务的ip
    port: 5672            #RabbitMQ服务的端口
    username: itcast      #RabbitMQ的帐号
    password: 123321      #RabbitMQ的密码

引导类

创建引导类,没有什么特殊的要求

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoProducerApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoProducerApplication.class, args);
    }
}

3 创建消费者模块

在project里创建module,起名称为demo-consumer

依赖

不需要添加依赖,父工程里已经导入了依赖,子模块直接继承父工程里的依赖就足够了

配置

修改配置文件application.yaml

spring:
  application:
    name: demo-producer
  rabbitmq:
    host: 192.168.200.137 #RabbitMQ服务的ip
    port: 5672            #RabbitMQ服务的端口
    username: itcast      #RabbitMQ的帐号
    password: 123321      #RabbitMQ的密码

引导类

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoConsumerApplication.class, args);
    }
}

2. RabbitMQ工作模式

RabbitMQ提供了6种工作模式,参考:RabbitMQ Tutorials | RabbitMQ

  • basic queue:简单模式

  • work queues:工作队列集群消费

  • Publish/Subscribe:发布订阅模式,也称为Fanout,是一种消息广播模式

  • Routing:路由模式,也称为Direct模式

  • Topics:主题模式

  • RPC远程调用模式:远程调用,其实算不上MQ,这里不做介绍

无论哪种模式,可能都需要用到交换机、队列、交换机与队列的绑定。如何声明这些队列和交换机?

  • 在控制台页面上直接创建。不经常用

  • 使用@Bean的方式,声明交换机、队列、绑定关系

  • 使用注解方式,可以在消费者一方直接声明交换机、队列、绑定关系,适合于复杂的绑定关系声明

3. basic queue简单队列【了解】

1 模式说明

basic queue是RabbitMQ中最简单的一种队列模式:生产者把消息直接发送到队列queue,消费者从queue里直接接收消息。

2 使用示例

生产者

package com.itheima;

import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;


@SpringBootTest
public class DemoProducerTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 简单模式:发送消息示例
     */
    @Test
    public void test01Simple(){
        rabbitTemplate.convertAndSend("demo01.simple.queue", "hello,simple queue");
    }
}

消费者

  1. 创建一个类,用于监听消息。类上需要添加@Component注解

  2. 类里定义一个方法,用于处理消息。

    方法上需要添加注解 @RabbitListener(queuesToDeclare = @Queue("队列名称"))

    方法上需要添加一个String类型的形参:是接收到的消息内容

package com.itheima.listener;

import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;


@Component
public class Demo01SimpleListener {

    @RabbitListener(queuesToDeclare = @Queue("demo01.simple.queue"))
    public void listen(String msg){
        System.out.println("msg = " + msg);
    }
}

3 测试

先启动消费者监听消息,再由生产者发送消息

因为:我们在消费者代码里声明了队列,只有先启动消费者,才会在RabbitMQ里创建队列,然后才能正常发送消息

4 注意事项

要想使用RabbitMQ收发消息,必须要保证已经有队列和交换机已经存在,才可以正常收发。

  • 可以直接在RabbitMQ控制台里创建队列、交换机和绑定关系。 然后再启动代码收发消息,先启动生产者或先消费者都行

  • 可以在生产者一方使用@Bean声明队列、交换机和绑定关系。然后必须先启动生产者服务发送消息,再启动消费者监听消息

  • 可以在消费者一方使用@RabbitListener声明队列、交换机和绑定关系。然后必须先启动消费者监听消息,再运行生产者发送消息

4. work queues工作队列【了解】

假如只有一个消费者处理消息,那么处理消息的速度就有可能赶不上发送消息的速度。该如何同时处理更多的消息呢?

可以在同一个队列上创建多个竞争的消费者,以便消费者可以同时处理更多的消息

1 模式说明

多个消费者相互竞争,从同一个队列里获取消息。生产者发送的消息将被所有消费者分摊消费

注意: 一个队列里的一条消息,只能被消费一次,不可能多个消费者同时消费处理

对于任务过重,或者任务较多的情况,使用工作队列可以提高任务处理的速度

例如:短信通知服务。 订单完成后要发短信通知

2 示例代码

生产者

在测试类里发送消息

    @Test
    public void test02WorkQueue(){
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("demo02.work.queue","hello, 这是消息"+i);
        }
    }

消费者

package com.itheima.listener;

import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;


@Component
public class Demo02WorkQueueListener {

    @RabbitListener(queuesToDeclare = @Queue("demo02.work.queue"))
    public void listener1(String msg){
        System.out.println("消费者1收到消息msg = " + msg);
    }

    @RabbitListener(queuesToDeclare = @Queue("demo02.work.queue"))
    public void listener2(String msg){
        System.out.println("消费者2收到消息msg = " + msg);
    }
}

3 注意事项

在WorkQueues模式的默认情况下,一个队列里的所有消息,将平均分配给每个消费者。这种情况并没有考虑到消费者的实际处理能力,显然是有问题的。

例如:生产者发送了50条消息,有两个消费者,各接收到了25条消息。假如

  • 消费者1,每秒能处理100条消息。 很快就能处理完消息

  • 消费者2,每秒能处理10条消息。 消息堆积越来越多

要解决这个问题其实非常简单:让每个消费者一次性只拉取1条消息

修改消费者的配置文件application.yaml:

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 #消费者一次抓取几条消息

5. Publish/Subscribe发布订阅(Fanout)

在上一章节中,我们创建了一个工作队列。工作队列背后的假设是,每个任务只传递给一个消费者。在这一节中,我们将做一些完全不同的事情——我们将向多个消费者传递一条消息。这种模式称为“发布/订阅”。

1 模式说明

  • P:生产者,用于发送消息。但是生产者要把消息发给交换机(X)

  • X:Exchange交换机

    • 接收消息,接收生产者发送的消息

    • 处理消息,把消息投递给某个或某些队列,或者把消息丢弃。具体会如何操作,由Exchange类型决定:

      • Fanout:广播,把消息交给绑定的所有队列(交换机绑定了哪些队列,就把消息投递给这些队列)

      • Direct:定向,把消息交给符合指定routing key的队列

      • Topic:通配符,把消息交给符合routing pattern的队列

    • 注意:交换机只负责转发消息,不具备存储消息的能力。所以如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,消息将会丢失

  • Queue:消息队列,接收消息、缓存消息

  • C:消费者,等待消息、处理消息

2 示例代码

为了说明这种模式,我们将构建一个简单的日志系统。它将由两个程序组成:

  • 生产者程序将发出日志消息

  • 消费者程序将接收日志消息

    • 第一组消费者,接收到日志消息并保存到磁盘上

    • 第二组消费者,接收到日志消息并打印到控制台

生产者

    @Test
    public void test03Fanout(){
        //参数1:交换机名。参数2:路由key。参数3:消息内容
        rabbitTemplate.convertAndSend("demo03.fanout.exchange","demo03.key", "这是一条广播消息");
    }

消费者

package com.itheima.listener;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;


@Component
public class Demo03FanoutListener {

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("demo03.queue1"),
            exchange = @Exchange(value = "demo03.fanout.exchange", type = ExchangeTypes.FANOUT)
    ))
    public void listener1(String msg){
        System.out.println("消费者1收到消息msg = " + msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("demo03.queue2"),
            exchange = @Exchange(value = "demo03.fanout.exchange", type = ExchangeTypes.FANOUT)
    ))
    public void listener2(String msg){
        System.out.println("消费者2收到消息msg = " + msg);
    }
}

6. Direct(Routing)

在上一个章节中,我们构建了一个简单的日志系统。我们能够向许多消费者广播日志消息。

在本节中,我们将向其添加一个功能:我们将使消费者能够仅订阅消息的子集。例如:

  • 只能将关键错误消息定向到日志文件(以节省磁盘空间)

  • 同时仍然能够在控制台上打印所有日志消息。

1 模式说明

  • 队列在绑定交换机时,需要给队列指定一个Routing Key(路由key)

  • 生产者在发送消息时,必须指定消息的Routing Key

  • 交换机根据消息的RoutingKey进行判断:只有队列的RoutingKey 与 消息的RoutingKey完全相同,才会收到消息

2 示例代码

生产者

    @Test
    public void test04Direct(){
        rabbitTemplate.convertAndSend("demo04.direct.exchange","demo04.error", "这是error消息");
        
        rabbitTemplate.convertAndSend("demo04.direct.exchange","demo04.info", "这是info消息");
    }

消费者

package com.itheima.listener;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;


@Component
public class Demo04DirectListener {

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("demo04.direct.queue2"),
            exchange = @Exchange(value = "demo04.direct.exchange", type = ExchangeTypes.DIRECT),
            key = {"demo04.info", "demo04.error"}
    ))
    public void listener1(String msg){
        System.out.println("消费者1(监听demo04.erorr和demo04.info)收到消息msg = " + msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("demo04.direct.queue1"),
            exchange = @Exchange(value = "demo04.direct.exchange", type = ExchangeTypes.DIRECT),
            key = {"demo04.error"}
    ))
    public void listener2(String msg){
        System.out.println("消费者2(监听demo04.erorr)收到消息msg = " + msg);
    }
}

7. Topic【重点】

在上一个章节中,我们改进了日志系统。我们没有使用仅能进行消息广播的FANOUT,而是使用了DIRECT,实现了了有选择地接收日志。

虽然使用DIRECT改进了我们的系统,但它仍然有局限性——它不能基于多个标准进行路由,例如:

  • 第一组消费者,要接收所有系统的所有日志消息,打印到控制台

  • 第二组消费者,要接收所有系统的错误日志消息,和订单系统的所有日志消息,保存到磁盘

为了在日志系统中实现这一点,我们需要了解更复杂的TOPIC交换机。

1 模式说明

  • RoutingKey:发送到TOPIC的消息不能有任意的routing键,它:

    • 必须是由点分隔的单词列表

    • 可以有任意多个单词,最多255个字节

    • 可使用* 星号,匹配一个单词

    • 可使用#,匹配0个或多个单词

  • 使用特定RoutingKey发送的消息,将被传递到使用匹配Key绑定的所有队列。

2 使用示例

生产者

    @Test
    public void test05Topic(){
        rabbitTemplate.convertAndSend("demo05.topic.exchange","order.info", "这是一条订单普通消息");
        rabbitTemplate.convertAndSend("demo05.topic.exchange","order.error", "这是一条订单错误消息");
    }

消费者

package com.itheima.listener;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;


@Component
public class Demo05TopicListener {

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("demo05.queue1"),
            exchange = @Exchange(value = "demo05.topic.exchange", type = ExchangeTypes.TOPIC),
            key = "order.*"
    ))
    public void listener1(String msg){
        System.out.println("消费者1(监听order.*)收到消息msg = " + msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("demo05.queue2"),
            exchange = @Exchange(value = "demo05.topic.exchange", type = ExchangeTypes.TOPIC),
            key = "*.error"
    ))
    public void listener2(String msg){
        System.out.println("消费者2(监听*.error)收到消息msg = " + msg);
    }
}

8. 小结

 

 

要使用SpringAMQP收发消息,准备工作:

  • 添加spring-amqp的依赖坐标

  • 无论是生产者还是消费者,都需要:配置RabbitMQ的信息

spring:
  rabbitmq:
    host: 192.168.200.130 #RabbitMQ服务的ip
    port: 5672            #RabbitMQ服务的端口
    username: itcast      #RabbitMQ的帐号
    password: 123321      #RabbitMQ的密码
    virtual-host: /       #RabbitMQ的VirtualHost,如果不设置,就使用默认的/ 

简单模式收发消息:

  • 发消息:

    rabbitTemplate.convertAndSend("队列名称", 消息内容)

  • 收消息:在任意一个bean对象里增加方法

@RabbitListener(queueToDeclare=@Queue("队列名称"))
public void listener(String msg){
    //msg就是收到的消息内容
}

工作队列收发消息:一个队列可以有多个消费者,共同处理消息。提升消费能力,处理消息堆积

  • 发消息:

    rabbitTemplate.convertAndSend("队列名称", 消息内容)

  • 收消息:在任意一个bean对象里增加多个方法,每个方法就是一个消费者

@RabbitListener(queueToDeclare=@Queue("队列名称"))
public void listener1(String msg){
    //msg就是收到的消息内容
}

@RabbitListener(queueToDeclare=@Queue("队列名称"))
public void listener2(String msg){
    //msg就是收到的消息内容
}

有交换机以后,生产者发消息用的方法是:

//发消息时的路由key必须是精确的,不要加通配符
rabbitTemplate.convertAndSend("交换机名", "路由key", 消息内容);

有交换机以后,消费者收消息的配置:

  • 告诉RabbitMQ,首先:如果没有队列、没有交换机,要帮我们自动创建

    FanoutExchange,扇出,用于发布订阅模式(广播模式)。一条消息可以被多个消费者接收

    DirectExchange,路由,用于Routing模式。一条消息根据路由key,发送到RoutingKey相等的队列上

    TopicExchange,用于Topic模式。一条消息,根据路由key,发送到RoutingKey匹配的队列上

  • 告诉RabbitMQ,然后:xxx交换机,你把符合路由key的消息,路由到指定的队列里。我要监听这个队列

@RabbitListener(bindings=@QueueBinding(
	value = @Queue("队列名称"),
    exchange = @Exchange(value="交换机名称", type=ExchangeTypes.交换机类型),
    key = {"路由key1", "路由key2"}
))
public void listener(String msg){
    
}

四、其它内容

1. @Bean方式声明队列和交换机【掌握】

1 队列和交换机的声明方式

要使用RabbitMQ发送消息的话,就必须提前声明好队列和交换机。

而声明队列和交换机的方式是多种多样的:

  • 手动创建:在RabbitMQ控制台页面上,直接手动创建队列和交换机,并进行绑定

    这种方式需要在控制台上页面创建并绑定,然后再编写程序,不太方便

  • @Bean方式:使用@Bean的方式声明交换机和队列,在程序启动运行时,由代码进行声明

    这种方式配置比较麻烦

  • 注解方式:使用注解方式声明交换机和队列,在监听消息时,由代码进行声明

    使用相对简单,在监听消息时,一个注解综合性配置消息队列、交换机并进行绑定

2 @Bean声明方式的示例

我们以使用最多的Topic为例,演示一下@Bean方式的交换机与队列声明

生产者声明队列和交换机

package com.itheima.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class DemoRabbitConfig {

    /**
     * 声明一个名称为demo.topic.exchange的交换机
     */
    @Bean
    public TopicExchange topicExchange(){
        return ExchangeBuilder.topicExchange("demo.topic.exchange").build();
    }

    /**
     * 声明一个名称为demo.topic.queue1的队列
     */
    @Bean
    public Queue topicQueue1(){
        return QueueBuilder.durable("demo.topic.queue1").build();
    }

    /**
     * 声明一个名称为demo.topic.queue2的队列
     */
    @Bean
    public Queue topicQueue2(){
        return QueueBuilder.durable("demo.topic.queue2").build();
    }

    /**
     * 将交换机demo.topic.exchange
     * 和队列demo.topic.queue1
     * 绑定起来,路由key是:demo.*
     */
    @Bean
    public Binding topicQueue1Binding(Queue topicQueue1, TopicExchange topicExchange){
        return BindingBuilder.bind(topicQueue1).to(topicExchange).with("demo.*");
    }

    /**
     * 将交换机demo.topic.exchange
     * 和队列demo.topic.queue2
     * 绑定起来,路由key是:#.key
     */
    @Bean
    public Binding topicQueue2Binding(Queue topicQueue2, TopicExchange topicExchange){
        return BindingBuilder.bind(topicQueue2).to(topicExchange).with("#.key");
    }
}

生产者发送消息

    @Test
    public void test05(){
        rabbitTemplate.convertAndSend("demo.topic.exchange", "demo.1", "消息demo.1");
        rabbitTemplate.convertAndSend("demo.topic.exchange", "xxx.key", "消息xxx.key");
    }

消费者监听消息

package com.itheima.listener;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;


@Component
public class Demo06Listener {

    @RabbitListener(queues = "demo.topic.queue1")
    public void listener1(String msg){
        System.out.println("消费者1(从队列demo.topic.queue1)收到消息msg = " + msg);
    }

    @RabbitListener(queues = "demo.topic.queue2")
    public void listener2(String msg){
        System.out.println("消费者2(从队列demo.topic.queue2)收到消息msg = " + msg);
    }
}

测试

先运行消费者代码,开始监听队列。如果队列不存在,会自动创建并进行绑定

再运行生产者代码,发送消息

2. 消息json格式转换器

1 说明

使用RabbitTemplate发送消息时,Spring会采用JDK的序列化技术对消息内容进行序列化:

  • 生产者发出的消息内容会被序列化成字节数组,再发送出去。

  • 消费者收到的消息,也是字节数组,Spring会进行反序列化还原

而JDK的序列化技术存在一些问题:

  • 序列化的字节数组,通常体积比较大

  • 序列化和反序列化容易产生安全漏洞

  • 可读性差

我们可以配置一个消息转换器:把消息转换成json格式字符串发送出去,消费者接收到json

2 示例

1. 添加json转换的依赖坐标

注意:需要在生产者和消费者双方都添加。我们这里可以直接添加到父工程的pom.xml里

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.10</version>
</dependency>

2. 配置消息转换器

注意:需要在生产者和消费者双方都添加。我们在两个模块的引导类里添加:

/**
 * 配置json消息转换器,不要导错了:
 *  org.springframework.amqp.support.converter.MessageConverter
 *  org.springframework.amqp.support.converter.Jackson2JsonMessageConverter
 */
@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter();
}

3. 测试

  1. 关闭消费者:暂时不接收消息,等发送消息后,我们要先去控制台上查看消息内容的格式

  2. 生产者发送消息

package com.itheima;

import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.HashMap;
import java.util.Map;

@SpringBootTest
public class Demo07SerializeTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void test(){
        Map<String, Object> msg = new HashMap<>();
        msg.put("id", 2);
        msg.put("title", "山东要热成灿东了 高温黄色预警高挂局部可达39℃");
        rabbitTemplate.convertAndSend("serialize.queue", msg);
    }
}

     3. 在RabbitMQ控制台上查看消息:查看收到的消息,是json格式的

    4. 启动消费者,接收到消息内容  

package com.itheima.listener;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;


@Slf4j
@Component
public class Demo07SerializeListener {

    @RabbitListener(queuesToDeclare = @Queue("serialize.queue"))
    public void handleSerializeQueue1(Map<String,Object> msg){
        log.info("从{}接收到消息:{}", "serialize.queue1", msg);
    }
}

3. 小结

使用@Bean的方式声明队列、交换机和绑定关系:

 发消息 发消息

//声明交换机
@Bean
public XxxExchange exchange(){
    return ExchangeBuilder.xxxExchange("交换机名").build();
}

//声明队列
@Bean
public Queue queue(){
    return QueueBuilder.durable("队列名称").build();
}

//声明队列和交换机的绑定关系
@Bean
public Binding queueBinding(XxxExchange exchange, Queue queue){
    return BindingBuilder.bind(queue).to(exchange).with("路由key通配符");
}

 发消息

rabbitTemplate.convertAndSend("交换机名", "消息的路由key", 消息内容);

收消息

@RabbitListener(queueToDeclare=@Queue("队列名"))
public void listener(String msg){
    
}

消息转换器:

  • 如果不配置消息转换器:默认情况是

    生产者把消息对象序列化成字节,传输到MQ。MQ里存储的字节码;消费者收到字节码后反序列化成Java对象

  • 可以配置json格式的消息转换器:

    生产者把消息对象转换成json字符串,传输到MQ。MQ存储的是json字符串。消费者收到json字符串再还原成Java对象

大总结

什么是MQ:是消息队列

MQ的作用:服务之间数据交互时的一种方式。和Feign对比

  • 异步:实现服务之间的异步通信

  • 削峰:MQ可以堆积消息,可以应对流量洪峰

  • 解耦:实现服务之间耦合性的进一步降低

注意:MQ消息队列和即时通信是两种不同的技术

  • MQ:用于服务之间的异步数据交互。和Feign对比,Feign是同步通信,MQ是异步通信

  • 即时通信:用于聊天的,单聊、群聊等等。通常要借助于第三方服务,比如:环信云,融联云等等

常见的MQ:

  • RabbitMQ:性能好,延迟低

  • RocketMQ:稳定可靠,可以做到消息0丢失

  • Kafka:吞吐量大,可以实现海量数据交互传输,通常用于大数据领域

RabbitMQ实现了AMQP协议

  • AMQP协议是:高级消息队列协议,规定了MQ应该遵循的规范。规定了

    生产者:发送消息的一方

    中间件:Broker,指的是MQ

    交换机:Exchange,用于把消息路由到队列

    队列:Queue,真正可以堆积缓存消息的队列

    消费者:接收消息的一方

  • RabbitMQ实现了AMQP协议:也有以上AMQP的角色,除此之外还有

    虚拟主机VirtualHost:同一虚拟主机里的队列和交换机可以绑定。不同虚拟主机之间是互相隔离的

Java操作RabbitMQ收发消息:SpringAMQP

  • 是Spring提供一套API规范,用于收发消息的。默认提供了操作RabbitMQ的类和方法

RabbitMQ的消息模式:

  • 简单队列:生产者直接发消息到MQ队列;消费者直接从MQ队列里接收消息

  • 工作队列:生产者直接发消息到MQ队列;可以有多个消费者直接从MQ队列里接收消息

    可以增加一个队列的消费能力,快速处理消息

  • 广播模式:生产者发送的消息,可以被多个消费者同时都收到

    生产者把消息发送到FanoutExchange交换机;

    FanoutExchange把消息路由到绑定的所有队列

    每个队列都可以有一个消费者接收消息

  • 路由模式:生产者发送的消息,可以自主决定被哪些消费者接收到

    生产者把消息发送到DirectExchange交换机;

    DirectExchange根据消息的RoutingKey,路由到 相等的队列上

    每个队列都可以有一个消费者接收消息

  • Topic模式:生产者发送的消息,可以自主决定被哪些消费者接收到

    生产者把消息发送到DirectExchange交换机;

    DirectExchange根据消息的RoutingKey,路由到 路由key相匹配的 队列上

    每个队列都可以有一个消费者接收消息

以Topic模式为例,收发消息:@RabbitListener声明交换机和队列

发消息

rabbitTemplate.convertAndSend("交换机名", "路由key", 消息内容);

收消息

@RabbitListener(binding=@QueueBinding(
    value = @Queue("队列名称"),
    exchange = @Exchange(value="交换机名称", type=ExchangeType.交换机类型),
    key = {"a.*", "#.b"}
))
public void listener(String msg){
    
}

以Topic模式为例,收发消息:@Bean声明交换机和队列

  • 声明队列和交换机

@Configuration
public class RabbitConfig{
    @Bean
    public TopicExchange exchange(){
        return ExchangeBuilder.topicExchange("交换机名").build();
    }
    
    @Bean
    public Queue queue(){
        return QueueBuilder.durable("队列名称").build();
    }
    
    @Bean
    public Binding binding(TopicExchange exchange, Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("a.*");
    }
}

发消息

rabbitTemplate.convertAndSend("交换机名", "路由key", 消息内容);

收消息

@RabbitListener(queueToDeclare = @Queue("队列名称"))
public void listener(String msg){
    
}

消息转换器:可以把消息内容转换成json,通过MQ进行传输

  1. 先添加依赖坐标:给生产者和消费者都添加

  2. 再配置消息转换器:给生产者和消费者都配置

@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值