一、RabbitMQ基础入门

一、MQ概述

1、什么是MQ

1、MQ(message queue)消息队列:从字面意思看,本质是一个队列,FIFO(先进先出),只不过队列中存放的内容是message,还是一种跨进程的通信机制,用于上下游传递消息。在互联网架构中,MQ是一种非常常见的上下游“逻辑解耦+物理解耦”的消息通信服务。使用MQ之后,消息发送上游只需要依赖MQ,不用依赖其他服务。
2、消息队列作用:
  • 应用解耦
  • 流量消峰
  • 异步处理

2、应用解耦

1、场景带入:假设应用中有订单系统、库存系统。用户创建订单后,订单系统直接调用库存系统的接口,假如库存系统无法访问,则订单扣减库存会失败,从而导致订单失败,订单系统与库存系统耦合。引入消息队列后,用户下单之后,订单系统完成持久化处理,将消息写入消息队列中,返回用户下单成功;库存系统订阅下单系统的消息,获取下单信息,进行库存操作。

在这里插入图片描述

3、流量消峰

1、场景带入:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,使用消息队列做缓冲。用户的请求,服务器接收后,首先写入消息队列,假如消息队列长度超过最大数量,则直接抛弃用户请求或转到错误页面。秒杀业务根据消息队列中的请求信息,再进行后续处理。

在这里插入图片描述

4、异步处理

1、场景带入:用户注册后,需要发送邮件和注册短信。传统方式有两种:串行和并行
2、串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个步骤全部完成后,返回给客户端。

在这里插入图片描述

3、并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信,以上三个任务完成后,返回给客户端。与串行的区别是:并行的方式可以提高处理的时间。假设三个节点都是50ms,不考虑网络等其他开销,则串行的方式是150ms,并行方式是100ms。

在这里插入图片描述

4、引入消息队列,将不是必须的业务逻辑,异步处理。用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍

在这里插入图片描述

5、常见消息队列中间件的特点

1、ActiveMQ:
  • 优点:单机吞吐量万级,时效性ms级,可用性高,基于主从架构实现高可用性,消息可靠性较低的概率丢失数据。
  • 缺点:官方社区现在对ActiveMQ 5.x维护越来越少,高吞吐量场景较少使用。
2、Kafka:
  • 优点:性能卓越,单机写入TPS约在百万条/秒,高吞吐,时效性ms级可用性高,是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用,消费者采用Pull方式获取消息,消息有序, 通过控制能够保证所有消息被消费且仅被消费一次。
  • 缺点:Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长,使用短轮询方式,实时性取决于轮询间隔时间,消费失败不支持重试;支持消息顺序, 但是一台代理宕机后,就会产生消息乱序,社区更新较慢。
3、RocketMQ:
  • 优点:单机吞吐量十万级,可用性非常高,分布式架构,消息可以做到0丢失,MQ功能较为完善,还是分布式的,扩展性好,支持10亿级别的消息堆积,不会因为堆积导致性能下降。
  • 缺点:支持的客户端语言不多,目前是java及c++
4、RabbitMQ:
  • 优点:由于erlang语言的高并发特性,性能较好;吞吐量到万级,MQ功能比较完备,健壮、稳定、易 用、跨平台、支持多种语言。
  • 缺点:学习成本高,商业版需要收费

二、RabbitMQ概述及安装配置

1、概述

1、RabbitMQ是一个在AMQP基础上完成的,可复用的企业消息系统,遵循Monzilla Public License开源协议
2、AMQP即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。
3、主要特征:
  • 保证可靠性:使用一些机制来保证可靠性,如持久化、传输确认、发布确认。
  • 灵活的路由功能
  • 可伸缩性:支持消息集群,多台RabbitMQ服务器可以组成一个集群。
  • 高可用性:RabbitMQ集群中的某个节点出现问题时,队列依然可用。
  • 支持多种协议。
  • 支持多语言客户端。
  • 提供良好的管理界面
  • 提供跟踪机制:如果消息出现异常,可以通过跟踪机制分析异常原因。
  • 提供插件机制:可通过插件进行多方面扩展。

2、核心概念

在这里插入图片描述

1、Message:消息,消息是不具名的,它是由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括路由键(Routing Key)、优先权(Priority)、指出该消息可能需要持久性存储(delivery-mode)等。
2、Publisher: 消息生产者,也是一个向交换机发布消息的客户端应用程序。
3、Exchange:
  • 交换机一方面它接收来自生产者的消息,另一方面它将消息推送到队列中(Queue),交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是多个队列,亦或是把消息丢弃,这个得由交换机类型决定。
  • 消息到达Broker的第一站,根据分发规则,匹配查询表中的路由键(Routing Key),分发消息到Queue中。常用的类型有:direct(point-to-point)、topic(publish-subscribe)、fanout(multicast)、headers。
4、Queue:消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
5、Consumer:消息消费者,表示一个从消息队列中取得消息的客户端应用程序,同一个应用程序既可以是生产者又是可以是消费者。
6、Connection:Publisher/Consumer和Broker之间的TCP连接。
7、Channel:信道,如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低。Channel是在Connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的Channel进行通讯,AMQP method包含了Channel Id帮助客户端和Message Broker识别Channel,所以Channel之间是完全隔离的。Channel作为轻量级的Connection极大减少了操作系统建立TCP Connection的开销。
8、Virtual Host:虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个VHost本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制。VHost是AMQP概念的基础,必须在连接时指定, RabbitMQ默认的VHost是/
9、Broker: 接收和分发消息的应用,RabbitMQ Server就是 Message Broker
10、Binding:绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。 Exchange和Queue的绑定可以是多对多的关系。

3、安装说明

1、说明:由于RabbitMQ是基于Erlang语言的,因此先安装Erlang
2、下载地址:
3、安装之前先要确保服务器已经安装过JDK,安装并配置JDK请参考
# 1、如果之前安装过erlang则先删除
yum remove erlang*
# 2、安装C++编译环境
yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel unixODBC unixODBC-devel httpd python-simplejson

4、安装Erlang

# 1、解压erlang安装包
tar -zxvf otp_src_20.1.tar.gz
# 2、进入解压好的文件夹
cd otp_src_20.1
# 3、erlang指定安装在/usr/local/RabbitMQ/erlang目录下
./configure --prefix=/usr/local/RabbitMQ/erlang --enable-smp-support --enable-threads --enable-sctp --enable-kernel-poll --enable-hipe --with-ssl --without-javac
# 4、编译与安装
make && make install
# 5、配置环境变量,编辑/etc/profile文件(第一种修改配置文件的方式
vim /etc/profile
# 6、添加erlang路径到文件末尾
export PATH=$PATH:/usr/local/RbbitMQ/erlang/bin
# 7、保存并退出vim编辑,并重新加载profile文件
source /etc/profile
# 8、检测erlang是否安装好,任意目录下输入如下命令,进入erlang
erl
# 9、退出erlang
halt().
1、注意:如果执行第3步提示如下错误的解决方案
configure: error: No curses library functions found
configure: error: /bin/sh ‘/usr/local/rabbitMQ/otp_src_20.1/erts/configure’ failed for erts
# 安装ncurses-devel插件之后再执行第3步安装命令
yum install ncurses-devel
2、任意目录输入erl进入界面表示环境变量配置成功

在这里插入图片描述

5、安装RabbitMQ

# 1、由于下载的安装包为.xz文件,先将.xz解压为.tar
xz -d rabbitmq-server-generic-unix-3.7.0.tar.xz
# 2、解压rabbitmq-server-generic-unix-3.7.0.tar文件
tar -xvf rabbitmq-server-generic-unix-3.7.0.tar
# 3、配置环境变量,向/etc/profile文件写入配置(第二种修改配置文件的方式)
echo 'export PATH=$PATH:/usr/local/RabbitMQ/rabbitmq_server-3.7.0/sbin' >> /etc/profile
# 4、重新加载profile文件
source /etc/profile

6、RabbitMQ相关操作命令

# 后台运行RabbitMQ命令
rabbitmq-server -detached
# 停止RabbitMQ
rabbitmqctl stop
# 检查状态
rabbitmqctl status
# 查看进程
ps aux|grep rabbit
# 启动管理界面
rabbitmq-plugins enable rabbitmq_management

7、访问管理界面

1、管理界面端口(15672
2、rabbitmq的默认端口是(5672
3、IP地址:15672

在这里插入图片描述

三、RabbitMQ用户管理

1、用户级别

1、超级管理员administrator,可以登录控制台,查看所有信息,可以对用户和策略进行操作
2、监控者monitoring,可以登录控制台,可以查看节点的相关信息,比如进程数,内存磁盘使用情况
3、策略制定者policymaker,可以登录控制台,制定策略,但是无法查看节点信息
4、普通管理员management仅能登录控制台
5、其他,无法登录控制台,一般指的是提供者和消费者

2、添加用户(命令方式)

# 1、创建账号及密码
./rabbitmqctl add_user 用户名 密码
# 2、设置用户级别
./rabbitmqctl set_user_tags 用户名 用户级别
# 3、给用户分配VHost的权限
./rabbitmqctl set_permissions -p "VHost_Name" 用户名 ".*" ".*" ".*"
# 4、修改用户名密码
./rabbitmqctl change_password 用户名 新密码
# 5、删除用户
./rabbitmqctl delete_user 用户名
# 6、客户端与虚拟主机的连接数(并发数),-1表示取消限制
./rabbitmqctl set_vhost_limits -p 虚拟主机名 '{"max-connections": 数量}'

3、添加用户(Web方式)

1、浏览器进入管理界面,默认用户和密码都是 guest,guest具有最高权限,只能在本机登录,所以先用命令模式创建用户并登录。
2、添加用户并为用户分配可以访问的虚拟主机

在这里插入图片描述

3、为用户分配访问的虚拟主机,默认情况下是没有任何可以访问的,可以添加一个主机,然后分配权限

在这里插入图片描述

4、给指定用户分配虚拟主机

在这里插入图片描述

在这里插入图片描述

5、设置完成后,回到用户界面确认

在这里插入图片描述

4、交换机和队列创建

1、创建队列

在这里插入图片描述

2、创建交换机

在这里插入图片描述

3、交换机绑定队列,点击需要绑定队列的交换机名称后,进入绑定页面

在这里插入图片描述

四、交换器、路由键、绑定

1、概念

1、Exchange(交换器):RabbitMQ消息传递模型的核心思想是生产者生产的消息从不会直接发送到队列。实际上,通常生产者甚至都不知道这些消息传递传递到了哪些队列中。 相反,生产者只能将消息发送到交换机(exchange),交换机工作的内容非常简单,一方面它接收来自生产者的消息,另一方面将消息路由到一个或多个队列中。交换机必须确切知道如何处理收到的消息。是应该把这些消息放到特定队列,还是说把他们到许多队列中,还是说应该丢弃它们。这就的由交换机的类型来决定。
2、RoutingKey(路由键):生产者将消息发送给交换器的时候,一般会指定一个RoutingKey,用来指定这个消息的路由规则,而这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能生效。在交换器类型和绑定键固定的情况下,生产者可以在发送消息给交换器时,通过指定路由键来决定消息流向哪里。
3、Binding(绑定):RabbitMQ中通过绑定将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键(BindingKey),这样RabbitMQ就知道如何正确地将消息路由到具体队列了。

在这里插入图片描述

说明:生产者将消息发送给交换器时,会指定一个RoutingKey,当BindingKey与RoutingKey相匹配时,消息会路由到对应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许使用相同的BindingKey。BindingKey并不是在所有的情况下都生效,它依赖于交换器类型,比如fanout类型的交换器就会无视BindingKey,而是将消息路由到所有绑定到该交换器的队列中

2、交换器类型

1、RabbitMQ常用的交换器类型有direct(直接)、fanout(扇出)、topic(主题)、headers(标题)这四种
2、fanout exchange:它会把所有发送到该交换器的消息路由到所有与该交换机绑定的队列中。它不处理路由键, 只是简单的将队列绑定到交换器上。类似广播一样(发布订阅模式),它是转发消息是最快的。
3、direct exchange:消息中的路由键(RoutingKey)如果和Binding中的BindingKey完全匹配, 交换器就将消息发到对应的队列中。

在这里插入图片描述

4、topic exchange:与direct类型的交换器相似,也是将消息路由到BindingKey与RoutingKey相匹配的队列中,但是它是通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。
  • RoutingKey与BindingKey为一个点号“.”分隔的字符串(被点号分隔开的每一段独立的字符串称为一个单词),比如:java.util.concurrent
  • 同样会识别两个特殊字符串“*”和“#”,用做模糊匹配,“#”匹配0个或多个单词,“*”匹配一个单词

在这里插入图片描述

5、headers exchange:不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的Headers属性进行匹配。在绑定队列和交换器时制定一组键值对,当消息发送到交换器时,RabbitMQ会获取到该消息的Headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。

3、环境准备

1、创建一个Maven项目
2、父工程依赖
<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itan</groupId>
    <artifactId>Learning</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.boot.version>2.3.1.RELEASE</spring.boot.version>
        <maven-resources-plugin.version>3.1.0</maven-resources-plugin.version>
        <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
        <maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <!-- java编译插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.version}</version>
                <configuration>
                    <compilerVersion>${java.version}</compilerVersion>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
            <!-- 跳过单元测试 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven-surefire-plugin.version}</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>

        <resources>
            <resource>
                <directory>src/main/webapp</directory>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>
3、选中项目创建子工程rabbitmq_web,是一个springboot项目
<?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>com.itan</groupId>
        <artifactId>Learning</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>com.itan</groupId>
    <artifactId>rabbitmq-web</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rabbitmq-web</name>
    <description>rabbitmq-web</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>

        <!--fastjson依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>

        <!--commons-lang3依赖-->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

        <!--RabbitMQ依赖-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.8.0</version>
        </dependency>

        <!--操作文件流的依赖-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 资源文件拷贝插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>${maven-resources-plugin.version}</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <!-- java编译插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <!--spring-boot maven 插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.1.RELEASE</version>
                <configuration>
                    <finalName>${project.artifactId}</finalName>
                    <mainClass>com.itan.nioweb.NioWebApplication</mainClass>
                    <fork>true</fork>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
4、创建RabbitMQUtils连接工具类
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @Date: 2022/3/24
 * MQ连接工具类
 */
public class RabbitMQUtils {
    public static Connection getConnection() throws IOException, TimeoutException {
        //1、创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2、设置RabbitMQ连接地址
        factory.setHost("101.26.156.147");
        //3、设置端口号
        factory.setPort(5672);
        //4、虚拟主机
        factory.setVirtualHost("test_host");
        //5、用户名
        factory.setUsername("test");
        //6、密码
        factory.setPassword("123456");
        //7、通过工厂对象获取与MQ的连接
        Connection connection = factory.newConnection();
        return connection;
    }
}

4、RabbitMQ简单模式

1、简单模式:一个队列只有一个消费者,生产者将消息发送到队列,消费者从队列中取出数据

在这里插入图片描述

/**
 * @Author: ye.yanbin
 * @Date: 2022/3/24
 * 消息生产者
 */
public class ProducerTest1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //消息内容
        String msg = "Hello World";
        //1、获取连接
        Connection connection = RabbitMQUtils.getConnection();
        //2、从连接中创建通道
        Channel channel = connection.createChannel();

        /**
         * 定义队列(使用java代码在MQ中新建一个队列)
         * 参数1:队列名称
         * 参数2:消息是否持久化,队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失,如果想重启之后还存在就要使队列持久化,
         *        保存到Erlang自带的Mnesia数据库中,当rabbitmq重启之后会读取该数据库
         * 参数3:该队列是否只供一个消费者进行消费 是否进行共享,true可以多个消费者消费
         * 参数4:是否自动删除,最后一个消费者端开连接以后该队列是否自动删除, true自动删除
         * 参数5:设置当前队列的参数
         */
        channel.queueDeclare("queue1",false,false,false,null);
        /**
         * 3、发送消息
         * 参数1:交换机名称,如果直接发送信息到队列,则交换机名称为""
         * 参数2:目标队列名称,表示要将消息发送到那个队列
         * 参数3:设置当前消息的属性(超时时间)
         * 参数4:消息的内容
         */
        channel.basicPublish("","queue1", null, msg.getBytes());
        System.out.println("发送消息:" + msg);
        //4、关闭通道和连接
        channel.close();
        connection.close();
    }
}
运行程序之后,发现新创建了一个队列,队列中有一条消息,没有消息消费者,此消息会一直存在

在这里插入图片描述

/**
 * @Date: 2022/3/24
 * 消息消费者
 */
public class ConsumerTest1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、获取连接
        Connection connection = RabbitMQUtils.getConnection();
        //2、从连接中创建通道
        Channel channel = connection.createChannel();
        /**
         * 3、DefaultConsumer类实现了Consumer接口,通过传入一个通道,
         * 告诉服务器我们需要那个通道的消息,如果通道中有消息,就会执行回调函数handleDelivery
         */
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是从消息队列中获取的数据
                String msg = new String(body);
                System.out.println("消费消息:" + msg);
            }
        };
        /**
         * 4、消费者消费消息
         * 参数1:队列名称
         * 参数2:消息成功消费之后是否需要自动应答,true代表自动应答,false代表手动应答(自动手动应答对应消息确认机制)
         * 参数3:消息消费者
         */
        channel.basicConsume("queue1",true, consumer);
    }
}
运行之后,消息已经被消费,队列中的消息数为0

在这里插入图片描述

5、RabbitMQ工作模式

1、工作模式:多个消费者监听同一个队列,RabbitMQ采用轮询的方式将消息平均分发给消费者但多个消费者中最终只有一个消费者能成功消费消息(消费者之间是竞争关系,一个消息只能被处理一次,轮询分发)。

在这里插入图片描述

/**
 * @Date: 2022/3/24
 * 消息生产者
 */
public class ProducerTest2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        System.out.println("请输入消息:");
        Scanner scanner = new Scanner(System.in);
        //消息内容
        String msg = null;
        while (!"exit".equals(msg = scanner.nextLine())) {
            //1、获取连接
            Connection connection = RabbitMQUtils.getConnection();
            //2、从连接中创建通道
            Channel channel = connection.createChannel();
            /**
             * 3、发送消息
             * 参数1:交换机名称,如果直接发送信息到队列,则交换机名称为""
             * 参数2:目标队列名称,表示要将消息发送到那个队列
             * 参数3:设置当前消息的属性(超时时间)
             * 参数4:消息的内容
             */
            channel.basicPublish("","queue1", null, msg.getBytes());
            System.out.println("发送消息:" + msg);
            //4、关闭通道和连接
            channel.close();
            connection.close();
        }
    }
}
/**
 * @Date: 2022/3/24
 * 消息消费者
 */
public class ConsumerTest2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1、获取连接
        Connection connection = RabbitMQUtils.getConnection();
        //2、从连接中创建通道
        Channel channel = connection.createChannel();
        /**
         * 3、DefaultConsumer类实现了Consumer接口,通过传入一个通道,
         * 告诉服务器我们需要那个通道的消息,如果通道中有消息,就会执行回调函数handleDelivery
         */
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是从消息队列中获取的数据
                String msg = new String(body);
                System.out.println("消费消息:" + msg);
            }
        };
        System.out.println("消费者1启动等待消费......");
        /**
         * 消费者消费消息
         * 参数1:队列名称
         * 参数2:消息成功消费之后是否需要自动应答,true代表自动应答,false代表手动应答(自动手动应答对应消息确认机制)
         * 参数3:消息消费者
         */
        channel.basicConsume("queue1",true, consumer);
    }
}
启动两个消费者,可以通过Allow multiple instances允许多个实例执行,再启动生产者发送10个消息,通过结果可以看到两个消费者都有5条消息,并且是按照有序的一个接收一次消息。

在这里插入图片描述

6、RabbitMQ订阅模式

1、订阅模式:一条消息可以被多个消费者同时获取,生产者将消息发送到交换机 exchange ,消费者将自己对应的队列注册到交换机,当发送消息后所有注册的队列的消费者都可以收到消息。

在这里插入图片描述

2、新建一个fanout模式的交换器,并给交换器绑定两个队列

在这里插入图片描述

/**
 * @Date: 2022/3/24
 * 消息生产者 --- 订阅模式
 */
public class ProducerTest3 {
    public static void main(String[] args) throws IOException, TimeoutException {
        System.out.println("请输入消息:");
        Scanner scanner = new Scanner(System.in);
        //消息内容
        String msg = null;
        while (!"exit".equals(msg = scanner.nextLine())) {
            //1、获取连接
            Connection connection = RabbitMQUtils.getConnection();
            //2、从连接中创建通道
            Channel channel = connection.createChannel();
            /**
             * 3、发送消息
             * 参数1:交换机名称,如果直接发送信息到队列,则交换机名称为""
             * 参数2:不为空表示路由键,为空表示在此交换机中的所有队列都能接收到消息
             * 参数3:设置当前消息的属性(超时时间)
             * 参数4:消息的内容
             */
            channel.basicPublish("test_exchange","", null, msg.getBytes());
            System.out.println("发送消息:" + msg);
            //4、关闭通道和连接
            channel.close();
            connection.close();
        }
    }
}
1、消费者:将ConsumerTest2代码作如下修改,并设置Allow multiple instances,启动两次

在这里插入图片描述

2、运行结果,消费者1与消费者2都同时收到两条消息

在这里插入图片描述

7、RabbitMQ路由模式之redirect交换器

1、路由模式:一个交换机绑定多个消息队列,每个消息队列都有自己唯一的 key ,每个消息队列有一个消费者监听。

在这里插入图片描述

2、新建一个direct模式的交换器(消息中的路由键(routing key)如果和Binding中的binding key一致,交换器才将消息发到对应的队列中)并绑定两个新的队列,并分别设置路由键a,b

在这里插入图片描述

/**
 * @Author: ye.yanbin
 * @Date: 2022/3/24
 * 消息生产者 --- 路由模式
 */
public class ProducerTest4 {
    public static void main(String[] args) throws IOException, TimeoutException {
        System.out.println("请输入消息:");
        Scanner scanner = new Scanner(System.in);
        //消息内容
        String msg = null;
        while (!"exit".equals(msg = scanner.nextLine())) {
            //1、获取连接
            Connection connection = RabbitMQUtils.getConnection();
            //2、从连接中创建通道
            Channel channel = connection.createChannel();
            //若消息中包含a,则发送到路由键为a的队列中
            if (msg.contains("a")) {
                /**
                 * 3、发送消息
                 * 参数1:交换机名称,如果直接发送信息到队列,则交换机名称为""
                 * 参数2:不为空表示路由键(发送到绑定路由键对应的队列中),为空表示在此交换机中的所有队列都能接收到消息
                 * 参数3:设置当前消息的属性(超时时间)
                 * 参数4:消息的内容
                 */
                channel.basicPublish("direct_exchange","a", null, msg.getBytes());
            } else if (msg.contains("b")) {
                channel.basicPublish("direct_exchange","b", null, msg.getBytes());
            }
            System.out.println("发送消息:" + msg);
            //4、关闭通道和连接
            channel.close();
            connection.close();
        }
    }
}
1、消费者:将ConsumerTest2代码作如下修改,并设置Allow multiple instances,启动两次

在这里插入图片描述

2、运行结果,消费者a只接收到路由键为a的消息,消费者b只接收到路由键为b的消息

在这里插入图片描述

8、RabbitMQ路由模式之topic交换器

1、创建一个topic类型的交换器,并创建队列,绑定路由键

在这里插入图片描述

/**
 * @Date: 2022/3/24
 * 消息生产者--topic类型交换器
 */
public class TopicProducer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //获取连接
        Connection connection = RabbitMQUtils.getConnection();
        //从连接中创建通道
        Channel channel = connection.createChannel();
        //消息集合k:路由键,v:消息内容
        Map<String, String> bindingKeyMap = new HashMap<>();
        bindingKeyMap.put("com.itan.client", "消息发送到topic_queue1与topic_queue2");
        bindingKeyMap.put("com.java.client", "消息发送到topic_queue2");
        bindingKeyMap.put("com.demo", "消息发送到topic_queue2");
        bindingKeyMap.put("java.itan.demo", "消息发送到topic_queue1");
        bindingKeyMap.put("java.util.demo", "没有接收队列");
        //循环发送消息
        for (Map.Entry<String, String> entry : bindingKeyMap.entrySet()) {
            /**
             * 发送消息
             * 参数1:交换机名称,如果直接发送信息到队列,则交换机名称为""
             * 参数2:目标队列名称,表示要将消息发送到那个队列
             * 参数3:设置当前消息的属性
             * 参数4:消息的内容
             */
            channel.basicPublish("topic_exchange",entry.getKey(), null, entry.getValue().getBytes());
            System.out.println("路由键为:" + entry.getKey() + " 消息内容:" + entry.getValue());
        }
        //关闭通道和连接
        channel.close();
        connection.close();
    }
}
/**
 * @Date: 2022/3/24
 * 消息消费者1 --- topic类型交换器
 */
public class TopicConsumer1 {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        System.out.println("消费者1等待消费......");
        //获取连接
        Connection connection = RabbitMQUtils.getConnection();
        //从连接中创建通道
        Channel channel = connection.createChannel();
        /**
         * 接收消息时的回调
         * 参数 consumerTag:与消费者关联的消费者标签
         * 参数 message:传递的消息
         */
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            //从消息队列中获取的数据
            byte[] body = message.getBody();
            String msg = new String(body);
            System.out.println("绑定键为:" + message.getEnvelope().getRoutingKey() + " 消息内容为:" + msg);
        };
        /**
         * 消费者取消消费时的回调,若消息正常消费,则不触发此回调
         * 参数 consumerTag:与消费者关联的消费者标签
         */
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息消费被取消:" + consumerTag);
        };
        /**
         * 消费者消费消息
         * 参数1:队列名称
         * 参数2:消息成功消费之后是否需要自动应答,true代表自动应答,false代表手动应答(自动手动应答对应消息确认机制)
         * 参数3:接收消息时的回调
         * 参数4:消费者取消消费时的回调
         */
        channel.basicConsume("topic_queue1", true, deliverCallback, cancelCallback);
    }
}
/**
 * @Date: 2022/3/24
 * 消息消费者2 --- topic类型交换器
 */
public class TopicConsumer2 {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        System.out.println("消费者2等待消费......");
        //获取连接
        Connection connection = RabbitMQUtils.getConnection();
        //从连接中创建通道
        Channel channel = connection.createChannel();
        /**
         * 接收消息时的回调
         * 参数 consumerTag:与消费者关联的消费者标签
         * 参数 message:传递的消息
         */
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            //从消息队列中获取的数据
            byte[] body = message.getBody();
            String msg = new String(body);
            System.out.println("绑定键为:" + message.getEnvelope().getRoutingKey() + " 消息内容为:" + msg);
        };
        /**
         * 消费者取消消费时的回调,若消息正常消费,则不触发此回调
         * 参数 consumerTag:与消费者关联的消费者标签
         */
        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("消息消费被取消:" + consumerTag);
        };
        /**
         * 消费者消费消息
         * 参数1:队列名称
         * 参数2:消息成功消费之后是否需要自动应答,true代表自动应答,false代表手动应答(自动手动应答对应消息确认机制)
         * 参数3:接收消息时的回调
         * 参数4:消费者取消消费时的回调
         */
        channel.basicConsume("topic_queue2", true, deliverCallback, cancelCallback);
    }
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值