目录
RabbitMQ概念_MQ
消息队列
MQ全称Message Queue(消息队列),是在消息的传输过程中保 存消息的容器。多用于系统之间的异步通信。
1、同步通信相当于两个人当面对话,你一言我一语。必须及时回复
2、异步通信相当于通过第三方转述对话,可能有消息的延迟,但不需要二人时刻保持联系。
消息
两台计算机间传送的数据单位。消息可以非常简单,例如只包含文 本字符串;也可以更复杂,可能包含嵌入对象。
队列
数据结构中概念。在队列中,数据先进先出,后进后出。
RabbitMQ概念_MQ的优势
应用解耦
在电商平台中,用户下订单需要调用订单系统,此时订单系统还需 要调用库存系统、支付系统、物流系统完成业务。此时会产生两个问题:
1、如果库存系统出现故障,会造成整个订单系统崩溃。
2、如果需求修改,新增了一个X系统,此时必须修改订单系统的代码。
如果在系统中引入MQ,即订单系统将消息先发送到MQ中,MQ再 转发到其他系统,则会解决以下问题:
1、由于订单系统只发消息给MQ,不直接对接其他系统,如果库存系统出现故障,不影响整个订单。
2、如果需求修改,新增了一个X系统,此时无需修改订单系统的代码,只需修改MQ将消息发送给X系 统即可。
异步提速
如果订单系统同步访问每个系统,则用户下单等待时长如下:
如果引入MQ,则用户下单等待时长如下:
削峰填谷
假设我们的系统每秒只能承载1000请求,如果请求瞬间增多到每秒 5000,则会造成系统崩溃。此时引入mq即可解决该问题
使用了MQ之后,限制消费消息的速度为1000,这样一来,高峰期 产生的数据势必会被积压在MQ中,高峰就被“削”掉了,但是因为消 息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持 在1000,直到消费完积压的消息,这就叫做“填谷”。
RabbitMQ概念_MQ的劣势
1、系统可用性降低
系统引入的外部依赖越多,系统稳定性越差。一旦MQ宕机,就会对业务造成影响。
2、系统复杂度提高
MQ的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过MQ进行异步调 用。
3、一致性问题
A系统处理完业务,通过MQ给B、C、D三个系统发消息数据,如果B系统、C系统处理成功,D系 统处理失败,则会造成数据处理的不一致。
RabbitMQ概念_MQ应用场景
1、抢红包、秒杀活动、抢火车票等 这些业务场景都是短时间内需要处理大量请求,如果直接连接系 统处理业务,会耗费大量资源,有可能造成系统瘫痪。
而使用MQ后,可以先让用户将请求发送到MQ中,MQ会先保存 请求消息,不会占用系统资源,且MQ会进行消息排序,先请求的秒杀成功,后请求的秒杀失败。
2、消息分发
如电商网站要推送促销信息,该业务耗费时间较多,但对时效性要求不高,可以使用MQ做消息分发。
3、数据同步
假如我们需要将数据保存到数据库之外,还需要一段时间将数据 同步到缓存(如Redis)、搜索引擎(如Elasticsearch)中。此 时可以将数据库的数据作为消息发送到MQ中,并同步到缓存、 搜索引擎中。
4、异步处理
在电商系统中,订单完成后,需要及时的通知子系统(进销存系统发货,用户服务积分,发送短信)进行下一步操作。为了保证订单系统的高性能,应该直接返回订单结果,之后让MQ通知子系统做其他非实时的业务操作。这样能保证核心业务的高效及时。
5、离线处理
在银行系统中,如果要查询近十年的历史账单,这是非常耗时的操作。如果发送同步请求,则会花费大量时间等待响应。此时使 用MQ发送异步请求,等到查询出结果后获取结果即可。
RabbitMQ概念_AMQP
RabbitMQ是由Erlang语言编写的基于AMQP的MQ产品。
AMQP
即Advanced Message Queuing Protocol(高级消息队列协议),是 一个网络协议,专门为消息中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受不同中间件产品,不同开发语言等条件的限制。2006年AMQP规范发布,类比HTTP。
AMQP工作过程
生产者(Publisher)将消息发布到交换机(Exchange),交换机根据规则将消息分发给交换机绑定的队列(Queue),队列再将消息投递给订阅了此队列的消费者。
RabbitMQ概念_RabbitMQ工作原理
1、Producer
消息的生产者。也是一个向交换机发布消息的客户端应用程序。
2、Connection
连接。生产者/消费者和RabbitMQ服务器之间建立的TCP连接。
3、Channel
信道。是TCP里面的虚拟连接。例如:Connection相当于电缆, Channel相当于独立光纤束,一条TCP连接中可以创建多条信 道,增加连接效率。无论是发布消息、接收消息、订阅队列都是通过信道完成的。
4、Broker
消息队列服务器实体。即RabbitMQ服务器
5、Virtual host
虚拟主机。出于多租户和安全因素设计的,把AMQP的基本组件 划分到一个虚拟的分组中。每个vhost本质上就是一个mini版的 RabbitMQ服务器,拥有自己的队列、交换机、绑定和权限机 制。当多个不同的用户使用同一个RabbitMQ服务器时,可以划 分出多个虚拟主机。RabbitMQ默认的虚拟主机路径是 /
6、Exchange
交换机。用来接收生产者发送的消息,并根据分发规则,将这些消息分发给服务器中的队列中。不同的交换机有不同的分发规则。
7、Queue
消息队列。用来保存消息直到发送给消费者。它是消息的容器, 也是消息的终点。消息一直在队列里面,等待消费者链接到这个队列将其取走。
8、Binding
消息队列和交换机之间的虚拟连接,绑定中包含路由规则,绑定信息保存到交换机的路由表中,作为消息的分发依据。
9、Consumer
消息的消费者。表示一个从消息队列中取得消息的客户端应用程序。
RabbitMQ为什么使用信道而不直接使用TCP连接通信?
TCP连接的创建和销毁开销特别大。创建需要3次握手,销毁需要4次分手。高峰时每秒成千上万条TCP连接的创建会造成资源巨大的浪费。而且操作系统每秒处理TCP连接数也是有限制的, 会造成性能瓶颈。而如果一条线程使用一条信道,一条TCP链接可以容纳无限的信道,即使每秒成千上万的请求也不会成为性能的瓶颈。
RabbitMQ安装_安装Erlang
RabbitMQ是使用Erlang语言编写的,所以在安装RabbitMQ前需要先安装Erlang环境
1、安装Erlang所需的依赖
yum install -y epel-release
2、添加存储库条目
3、安装Erlang
yum install erlang-24.2.1
4、查看Erlang是否安装成功
erl -version
RabbitMQ安装_安装RabbitMQ
1、为了外部能够正常访问RabbitMQ服务,先关闭防火墙
# 关闭运行的防火墙 systemctl stop firewalld.service # 禁止防火墙自启动 systemctl disable firewalld.service
2、RabbitMQ是通过主机名进行访问的,必须给服务器添加主机名
# 修改文件 vim /etc/sysconfig/network # 添加如下内容 NETWORKING=yes HOSTNAME=itbaizhan # 修改文件 vim /etc/hosts # 添加如下内容 服务器ip itbaizhan3、使用rz命令上传RabbitMQ压缩文件
//安装调用本地文件夹 [root@localhost /]# yum install lrzsz //打开本地文件夹 [root@localhost /]# rz -bey4、安装RabbitMQ
# 解压RabbitMQ tar xf rabbitmq-server-generic-unix3.9.13.tar.xz # 重命名: mv rabbitmq_server-3.9.13 rabbitmq # 移动文件夹: mv rabbitmq /usr/local/5、配置环境变量
# 编辑/etc/profile文件 vim /etc/profile #添加如下内容 export PATH=$PATH:/usr/local/rabbitmq/sbin # 运行文件,让修改内容生效 source /etc/profile6、开启管控台插件
// rabbitmq-plugins enable rabbitmq_management [root@xiaotong /]# rabbitmq-plugins enable rabbitmq_management
7、后台运行
#启动rabbitmq rabbitmq-server -detached #停止rabbitmq rabbitmqctl stop8、通过管控台访问RabbitMQ
路径: http://ip地址:15672 ,用户名: guest ,密码: guest9、此时会提示guest账户只允许本地使用,我们可以配置允许使用 guest远程访问
# 创建配置文件夹 mkdir -p /usr/local/rabbitmq/etc/rabbitmq # 创建配置文件 vim /usr/local/rabbitmq/etc/rabbitmq/rabbitmq.conf # 添加如下内容 loopback_users=none # 重启RabbitMQ rabbitmqctl stop_app rabbitmqctl reset rabbitmqctl start_app
RabbitMQ安装_账户管理
guest账户默认只允许本地使用,我们可以创建新账户远程访问 RabbitMQ
1、创建账户
# 创建账户 rabbitmqctl add_user 用户名 密码2、给用户授予管理员角色
rabbitmqctl set_user_tags 用户名 administrator3、给用户授权
# "/"表示虚拟机 # itbaizhan表示用户名 # ".*" ".*" ".*" 表示完整权限 rabbitmqctl set_permissions -p "/" itbaizhan ".*" ".*" ".*"4、通过管控台访问rabbitmq
RabbitMQ安装_管控台
RabbitMQ安装_Docker安装
1、关闭RabbitMQ服务
rabbitmqctl stop2、在Centos7中安装docker
# 安装Docker curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun # 启动docker systemctl start docker3、拉取镜像
docker pull rabbitmq4、启动RabbitMQ容器
docker run -d --hostname itbaizhan --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq5、开启管控台插件
# 查询rabbitmq容器ID docker ps # 进入容器 docker exec -it 容器ID /bin/bash # 开启管控台插件 rabbitmq-plugins enable rabbitmq_management # 退出容器 ctrl+p+q6、通过管控台访问rabbitmq
路径: http://ip地址:15672 ,用户名: guest ,密码: guest
7、关闭RabbitMQ容器
docker stop rabbit
RabbitMQ简单模式_概念
RabbitMQ共有六种工作模式:
简单模式(Simple)
工作队列模 式(Work Queue)
发布订阅模式(Publish/Subscribe)
路由模式(Routing)
通配符模式(Topics)
远程调用模式(RPC, 不常用,课程不对此模式进行讲解)
首先我们讲解简单模式:
RabbitMQ简单模式_项目搭建
接下来我们使用JAVA代码操作RabbitMQ,让其按照简单模式进行工作。
JMS
由于MQ产品很多,操作方式各有不同,于是JAVA提供了一套规则 ——JMS,用于操作消息中间件。JMS即Java消息服务 (JavaMessage Service)应用程序接口,是一个Java平台中关于面 向消息中间件的API。JMS是JavaEE规范中的一种,类比JDBC。很多MQ产品都实现了JMS规范,例如ActiveMQ。RabbitMQ官方并没 有实现JMS规范,但是开源社区有JMS的实现包。
创建项目
1、启动RabbitMQ
# 开启管控台插件 rabbitmq-plugins enable rabbitmq_management # 启动rabbitmq rabbitmq-server -detached2、创建普通maven项目,添加RabbitMQ依赖:
<dependencies> <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqpclient</artifactId> <version>5.14.0</version> </dependency> </dependencies>
1. JMS是JAVA为设计的规范 消息中间件
2. 关于RabbitMQ,说法正确的是 RabbitMQ官方并没有实现JMS规范
RabbitMQ简单模式_编写生产者
接下来我们编写生产者代码创建消息:
// 生产者
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");
// 2.创建连接
Connection connection = connectionFactory.newConnection();
// 3.建立信道
Channel channel = connection.createChannel();
// 4.创建队列,如果队列已存在,则使用该队列
/**
* 参数1:队列名
* 参数2:是否持久化,true表示MQ重启后队列还在。
* 参数3:是否私有化,false表示所有消费者都可以访问,
true表示只有第一次拥有它的消费者才能访问
* 参数4:是否自动删除,true表示不再使用队列时自动删除队列
* 参数5:其他额外参数
*/
channel.queueDeclare("simple_queue",false,false,false,null);
// 5.发送消息
String message = "hello!rabbitmq!";
/**
* 参数1:交换机名,""表示默认交换机
* 参数2:路由键,简单模式就是队列名
* 参数3:其他额外参数
* 参数4:要传递的消息字节数组
*/
channel.basicPublish("","simple_queue",null,message.getBytes());
// 6.关闭信道和连接
channel.close();
connection.close();
System.out.println("===发送成功===");
}
}
运行生产者后,我们可以看到在RabbitMQ中创建了队列,队列中已经有了消息。
1. JAVA操作RabbitMQ时,调用的方法发送消息 Channel
RabbitMQ简单模式_编写消费者
接下来我们编写消费者代码消费消息:
// 消费者
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");
// 2.创建连接
Connection connection = connectionFactory.newConnection();
// 3.建立信道
Channel channel = connection.createChannel();
// 4.监听队列
/**
* 参数1:监听的队列名
* 参数2:是否自动签收,如果设置为false,则需要手动确认消息已收到,否则MQ会一直发送消息
* 参数3:Consumer的实现类,重写该类方法表示接受到消息后如何消费
*/
channel.basicConsume("simple_queue",true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("接受消息,消息为:"+message);
}
});
}
}
运行消费者后,我们可以看到在RabbitMQ中的消息已经被消费。
1. JAVA操作RabbitMQ时,调用的方法监听队列 Channel
RabbitMQ工作队列模式_概念
与简单模式相比,工作队列模式(Work Queue)多了一些消费者,该 模式也使用direct交换机,应用于处理消息较多的情况。特点如下:
1. RabbitMQ工作队列模式使用的交换机为 direct
2. RabbitMQ工作队列模式的特点为 一个生产者对应多个消费者
RabbitMQ工作队列模式_编写生产者
接下来我们编写生产者代码创建大量消息:
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");
// 2.创建连接
Connection connection = connectionFactory.newConnection();
// 3.建立信道
Channel channel = connection.createChannel();
// 4.创建队列,持久化队列
channel.queueDeclare("work_queue",true,false,false,null);
// 5.发送大量消息,参数3表示该消息为持久化消息,即除了保存到内存还会保存到磁盘中
for (int i = 1; i <= 100; i++) {
channel.basicPublish("","work_queue",MessageProperties.PERSISTENT_TEXT_PLAIN,
("你好,这是今天的第"+i+"条消息").getBytes());
}
// 6.关闭资源
channel.close();
connection.close();
}
}
效果如下:
1. 原生JAVA操作RabbitMQ发送持久化消息时,添加的参数为 MessageProperties.PERSISTENT_TEXT_PLAIN
RabbitMQ工作队列模式_编写消费者
接下来我们编写三个消费者监听同一个队列:
// 消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");
// 2.创建连接
Connection connection = connectionFactory.newConnection();
// 3.建立信道
Channel channel = connection.createChannel();
// 4.监听队列,处理消息
channel.basicConsume("work_queue",true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("消费者1消费消息,消息为:"+message);
}
});
}
}
// 消费者2
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");
// 2.创建连接
Connection connection = connectionFactory.newConnection();
// 3.建立信道
Channel channel = connection.createChannel();
// 4.监听队列,处理消息
channel.basicConsume("work_queue",true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("消费者2消费消息,消息为:"+message);
}
});
}
}
// 消费者3
public class Consumer3 {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");
// 2.创建连接
Connection connection = connectionFactory.newConnection();
// 3.建立信道
Channel channel = connection.createChannel();
// 4.监听队列,处理消息
channel.basicConsume("work_queue",true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,AMQP.BasicProperties properties,byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("消费者3消费消息,消息为:"+message);
}
});
}
}
效果如下:
关于RabbitMQ的工作队列模式,以下说法正确的是
1、 一条消息只会被一个消费者消费。
2、消息队列默认将消息平均发送给消费者。
3、工作队列模式应用于处理消息较多的情况。
RabbitMQ发布订阅模式_概念
在开发过程中,有一些消息需要不同消费者进行不同的处理,如电 商网站的同一条促销信息需要短信发送、邮件发送、站内信发送等。此时可以使用发布订阅模式(Publish/Subscribe)
1. RabbitMQ发布订阅模式的特点为 能将消息发送给多条队列
RabbitMQ发布订阅模式_编写生产者
接下来我们编写发布订阅模式的生产者:
// 生产者
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");
// 2.创建连接
Connection connection = connectionFactory.newConnection();
// 3.建立信道
Channel channel = connection.createChannel();
// 4.创建交换机
/**
* 参数1:交换机名
* 参数2:交换机类型
* 参数3:交换机持久化
*/
channel.exchangeDeclare("exchange_fanout",BuiltinExchangeType.FANOUT,true);
// 5.创建队列
channel.queueDeclare("SEND_MAIL",true,false,false,null);
channel.queueDeclare("SEND_MESSAGE",true,false,false,null);
channel.queueDeclare("SEND_STATION",true,false,false,null);
// 6.交换机绑定队列
/**
* 参数1:队列名
* 参数2:交换机名
* 参数3:路由关键字,发布订阅模式写""即可
*/
channel.queueBind("SEND_MAIL","exchange_fanout","");
channel.queueBind("SEND_MESSAGE","exchange_fanout","");
channel.queueBind("SEND_STATION","exchange_fanout","");
// 7.发送消息
for (int i = 1; i <= 10 ; i++) {
channel.basicPublish("exchange_fanout","",null,
("你好,尊敬的用户,秒杀商品开抢了!"+i).getBytes(StandardCharsets.UTF_8));
}
// 8.关闭资源
channel.close();
connection.close();
}
}
效果如下:
1. RabbitMQ发布订阅模式使用的交换机为 fanout
RabbitMQ发布订阅模式_编写消费者
接下来编写三个消费者,分别监听各自的队列。
// 站内信消费者
public class CustomerStation {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");// 默认虚拟机
//2.创建连接
Connection conn = connectionFactory.newConnection();
//3.建立信道
Channel channel = conn.createChannel();
// 4.监听队列
channel.basicConsume("SEND_STATION", true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
String message = new String(body, "utf-8");
System.out.println("发送站内信:"+message);
}
});
}
}
// 邮件消费者
public class Customer_Mail {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");// 默认虚拟机
//2.创建连接
Connection conn = connectionFactory.newConnection();
//3.建立信道
Channel channel = conn.createChannel();
// 4.监听队列
channel.basicConsume("SEND_MAIL", true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
String message = new String(body, "utf-8");
System.out.println("发送邮件:"+message);
}
});
}
}
// 短信消费者
public class Customer_Message {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");// 默认虚拟机
//2.创建连接
Connection conn = connectionFactory.newConnection();
//3.建立信道
Channel channel = conn.createChannel();
// 4.监听队列
channel.basicConsume("SEND_MESSAGE",true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
String message = new String(body, "utf-8");
System.out.println("发送短信:"+message);
}
});
}
}
也可以使用工作队列+发布订阅模式同时使用,两个消费者同时监听一个队列:
// 短信消费者2
public class Customer_Message2 {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("itbaizhan");
connectionFactory.setPassword("itbaizhan");
connectionFactory.setVirtualHost("/");// 默认虚拟机
//2.创建连接
Connection conn = connectionFactory.newConnection();
//3.建立信道
Channel channel = conn.createChannel();
// 4.监听队列
channel.basicConsume("SEND_MESSAGE",true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
String message = new String(body, "utf-8");
System.out.println("发送短信2:"+message);
}
});
}
}
效果如下:
RabbitMQ路由模式_概念
使用发布订阅模式时,所有消息都会发送到绑定的队列中,但很多 时候,不是所有消息都无差别的发布到所有队列中。比如电商网站 的促销活动,双十一大促可能会发布到所有队列;而一些小的促销 活动为了节约成本,只发布到站内信队列。此时需要使用路由模式 (Routing)完成这一需求。
特点:
1、每个队列绑定路由关键字RoutingKey
2、生产者将带有RoutingKey的消息发送给交换机,交换机根据RoutingKey转发到指定队列。路由模 式使用direct交换机。
1. RabbitMQ路由模式的特点为 能按照路由键将消息发送给指定队列
RabbitMQ路由模式_编写生产者
接下来我们编写路由模式的生产者:
// 生产者
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("itbaizhan");
connectionFactory.setPassword("itbaizhan");
connectionFactory.setVirtualHost("/");
// 2.创建连接
Connection connection = connectionFactory.newConnection();
// 3.建立信道
Channel channel = connection.createChannel();
// 4.创建交换机
channel.exchangeDeclare("exchange_routing",BuiltinExchangeType.DIRECT,true);
// 5.创建队列
channel.queueDeclare("SEND_MAIL2",true,false,false,null);
channel.queueDeclare("SEND_MESSAGE2",true,false,false,null);
channel.queueDeclare("SEND_STATION2",true,false,false,null);
// 6.交换机绑定队列
channel.queueBind("SEND_MAIL2","exchange_routing","import");
channel.queueBind("SEND_MESSAGE2","exchange_routing","import");
channel.queueBind("SEND_STATION2","exchange_routing","import");
channel.queueBind("SEND_STATION2","exchange_routing","normal");
// 7.发送消息
channel.basicPublish("exchange_routing","import",null,
"双十一大促活动".getBytes());
channel.basicPublish("exchange_routing","normal",null,
"小心促销活动".getBytes());
// 8.关闭资源
channel.close();
connection.close();
}
}
1. RabbitMQ路由模式使用的交换机为 direct
2. 关于RabbitMQ路由模式,说法正确的是 队列需要绑定路由关键字
RabbitMQ路由模式_编写消费者
接下来我们编写路由模式的消费者:
// 站内信消费者
public class Customer_Station {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("itbaizhan");
connectionFactory.setPassword("itbaizhan");
connectionFactory.setVirtualHost("/");// 默认虚拟机
//2.创建连接
Connection conn = connectionFactory.newConnection();
//3.建立信道
Channel channel = conn.createChannel();
// 4.监听队列
channel.basicConsume("SEND_STATION2", true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
String message = new String(body, "utf-8");
System.out.println("发送站内信:"+message);
}
});
}
}
// 邮件消费者
public class Customer_Mail {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("itbaizhan");
connectionFactory.setPassword("itbaizhan");
connectionFactory.setVirtualHost("/");
//2.创建连接
Connection conn = connectionFactory.newConnection();
//3.建立信道
Channel channel = conn.createChannel();
// 4.监听队列
channel.basicConsume("SEND_MAIL2", true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
String message = new String(body, "utf-8");
System.out.println("发送邮件:"+message);
}
});
}
}
// 短信消费者
public class Customer_Message {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("itbaizhan");
connectionFactory.setPassword("itbaizhan");
connectionFactory.setVirtualHost("/");// 默认虚拟机
//2.创建连接
Connection conn = connectionFactory.newConnection();
//3.建立信道
Channel channel = conn.createChannel();
// 4.监听队列
channel.basicConsume("SEND_MESSAGE2", true,new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "utf-8");
System.out.println("发送短信:"+message);
}
});
}
}
运行生产者和消费者,效果如下:
RabbitMQ通配符模式_概念
通配符模式(Topic)是在路由模式的基础上,给队列绑定带通配符的 路由关键字,只要消息的RoutingKey能实现通配符匹配,就会将消息转发到该队列。通配符模式比路由模式更灵活,使用topic交换机。
通配符规则:
1. RabbitMQ通配符模式的特点为 能按照通配符规则将消息发送给指定队列
2. 关于RabbitMQ通配符模式的匹配规则,说法正确的是 # 可以匹配任意多个单词, * 可以匹配任意一个单词
RabbitMQ通配符模式_编写生产者
接下来我们编写通配符模式的生产者:
// 生产者
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");
// 2.创建连接
Connection connection = connectionFactory.newConnection();
// 3.建立信道
Channel channel = connection.createChannel();
// 4.创建交换机
channel.exchangeDeclare("exchange_topic", BuiltinExchangeType.TOPIC,true);
// 5.创建队列
channel.queueDeclare("SEND_MAIL3",true,false,false,null);
channel.queueDeclare("SEND_MESSAGE3",true,false,false,null);
channel.queueDeclare("SEND_STATION3",true,false,false,null);
// 6.交换机绑定队列
channel.queueBind("SEND_MAIL3","exchange_topic","#.mail.#");
channel.queueBind("SEND_MESSAGE3","exchange_topic","#.message.#");
channel.queueBind("SEND_STATION3","exchange_topic","#.station.#");
// 7.发送消息
channel.basicPublish("exchange_topic","mail.message.station",null,
"双十一大促活动".getBytes());
channel.basicPublish("exchange_topic","station",null,
"小型促销活动".getBytes());
// 8.关闭资源
channel.close();
connection.close();
}
}
1. RabbitMQ通配符模式发送消息时,消息的的特点为 由多个单词构成,中间以 . 分割
2. RabbitMQ通配符模式使用的交换机为 topic
RabbitMQ通配符模式_编写消费者
接下来我们编写通配符模式的消费者:
// 站内信消费者
public class Customer_Station {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");// 默认虚拟机
//2.创建连接
Connection conn = connectionFactory.newConnection();
//3.建立信道
Channel channel = conn.createChannel();
// 4.监听队列
channel.basicConsume("SEND_STATION3", true,new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "utf-8");
System.out.println("发送站内信:"+message);
}
});
}
}
// 邮件消费者
public class Customer_Mail {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");// 默认虚拟机
//2.创建连接
Connection conn = connectionFactory.newConnection();
//3.建立信道
Channel channel = conn.createChannel();
// 4.监听队列
channel.basicConsume("SEND_MAIL3", true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
String message = new String(body, "utf-8");
System.out.println("发送邮件:"+message);
}
});
}
}
// 短信消费者
public class Customer_Message {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.162");
connectionFactory.setPort(5672);
connectionFactory.setUsername("xiaotong");
connectionFactory.setPassword("xiaotong");
connectionFactory.setVirtualHost("/");// 默认虚拟机
//2.创建连接
Connection conn = connectionFactory.newConnection();
//3.建立信道
Channel channel = conn.createChannel();
// 4.监听队列
channel.basicConsume("SEND_MESSAGE3", true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "utf-8");
System.out.println("发送短信:"+message);
}
});
}
}
SpringBoot整合RabbitMQ_项目搭建
之前我们使用原生JAVA操作RabbitMQ较为繁琐,接下来我们使用 SpringBoot整合RabbitMQ,简化代码编写。
1、创建SpringBoot项目,引入RabbitMQ起步依赖
<!-- RabbitMQ起步依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>2、编写配置文件
spring: rabbitmq: host: 192.168.0.162 port: 5672 username: xiaotong password: xiaotong virtual-host: / #日志格式 logging: pattern: console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
1. RabbitMQ默认虚拟机路径为 /
SpringBoot整合RabbitMQ_创建对列和交换机
SpringBoot整合RabbitMQ时,需要在配置类创建队列和交换机, 写法如下:
@Configuration
public class RabbitConfig {
private final String EXCHANGE_NAME = "boot_topic_exchange";
private final String QUEUE_NAME = "boot_queue";
// 创建交换机
@Bean("bootExchange")
public Exchange getExchange() {
return ExchangeBuilder
.topicExchange(EXCHANGE_NAME) // 交换机类型
.durable(true) // 是否持久化
.build();
}
// 创建队列
@Bean("bootQueue")
public Queue getMessageQueue() {
return new Queue(QUEUE_NAME); // 队列名
}
// 交换机绑定队列
@Bean
public Binding bindMessageQueue(@Qualifier("bootExchange") Exchange exchange, @Qualifier("bootQueue") Queue queue) {
return BindingBuilder
.bind(queue)
.to(exchange)
.with("#.message.#")
.noargs();
}
}
1. SpringBoot整合RabbitMQ时,在创建交换机和队列 配置类
SpringBoot整合RabbitMQ_编写生产者
SpringBoot整合RabbitMQ时,提供了工具类RabbitTemplate发送消息,编写生产者时只需要注入RabbitTemplate即可发送消息。
@SpringBootTest
public class TestProducer {
// 注入RabbitTemplate工具类
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage(){
/**
* 发送消息
* 参数1:交换机
* 参数2:路由key
* 参数3:要发送的消息
*/
rabbitTemplate.convertAndSend("boot_topic_exchange","message","双十一开始了!");
}
}
运行生产者代码,即可看到消息发送到了RabbitMQ中:
1. SpringBoot整合RabbitMQ时,对象可以发送消息 RabbitTemplate
SpringBoot整合RabbitMQ_编写消费者
我们编写另一个SpringBoot项目作为RabbitMQ的消费者
1、创建SpringBoot项目,引入RabbitMQ起步依赖
<!-- rabbitmq起步依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>2、编写配置文件
spring: rabbitmq: host: 192.168.0.162 port: 5672 username: xiaotong password: xiaotong virtual-host: / #日志格式 logging: pattern: console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'3、编写消费者,监听队列
@Component public class Consumer { // 监听队列 @RabbitListener(queues = "boot_queue") public void listen_message(Stringmessage){ System.out.println("发送短信:"+message); } }4、启动项目,可以看到消费者会消费队列中的消息
1. SpringBoot整合RabbitMQ时,监听队列的注解为 @RabbitListener
消息的可靠性投递_概念
RabbitMQ消息投递的路径为:生产者 ---> 交换机 ---> 队列 ---> 消费者
在RabbitMQ工作的过程中,每个环节消息都可能传递失败,那么 RabbitMQ是如何监听消息是否成功投递的呢?
首先我们准备两个SpringBoot项目,分别代表生产者和消费者,配置文件如下:
spring:
rabbitmq:
host: 192.168.0.162
port: 5672
username: xiaotong
password: xiaotong
virtual-host: /
#日志格式
logging:
pattern:
console: '%d{HH:mm:ss.SSS}
%clr(%-5level) --- [%-15thread]
%cyan(%-50logger{50}):%msg%n'
在生产者的配置类创建交换机和队列
@Configuration
public class RabbitConfig {
private final String EXCHANGE_NAME="my_topic_exchange";
private final String QUEUE_NAME="my_queue";
// 1.创建交换机
@Bean("bootExchange")
public Exchange getExchange(){
return ExchangeBuilder
.topicExchange(EXCHANGE_NAME) // 交换机类型
.durable(true) // 是否持久化
.build();
}
// 2.创建队列
@Bean("bootQueue")
public Queue getMessageQueue(){
return QueueBuilder
.durable(QUEUE_NAME) // 队列持久化
.build();
}
// 3.将队列绑定到交换机
@Bean
public Binding bindMessageQueue(@Qualifier("bootExchange") Exchange exchange, @Qualifier("bootQueue") Queue queue){
return BindingBuilder.bind(queue).to(exchange).with("my_routing").noargs();
}
}
1. 在RabbitMQ中,可以监听消息是否从生产者成功传递到交换机 confirm
2. 在RabbitMQ中,可以监听消费者是否成功处理消息 Ack
消息的可靠性投递_确认模式
确认模式(confirm)可以监听消息是否从生产者成功传递到交换机,使用方法如下:
1、生产者配置文件开启确认模式
spring: rabbitmq: host: 192.168.0.162 port: 5672 username: xiaotong password: xiaotong virtual-host: / # 开启确认模式 publisher-confirm-type: correlated2、生产者定义确认模式的回调方法
@SpringBootTest public class ProducerTest { @Autowired private RabbitTemplate rabbitTemplate; @Test public void testConfirm(){ // 定义确认模式的回调方法,消息向交换机发送后会调用confirm方法 rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { /** * 被调用的回调方法 * @param correlationData 相关配置信息 * @param ack 交换机是否成功收到了消息 * @param cause 失败原因 */ @Override public void confirm(CorrelationData correlationData,boolean ack, String cause) { if (ack){ System.out.println("confirm接受成功!"); }else{ System.out.println("confirm接受失败,原因为:"+cause); // 做一些处理。 } } }); rabbitTemplate.convertAndSend("my_topic_exchange","my_routing","send message..."); } }
1. 在RabbitMQ中,需要在中配置才能开启确认模式 生产者
2. 在RabbitMQ中,RabbitTemplate调用方法定义确认模式的回调方法 setConfirmCallback
消息的可靠性投递_退回模式
退回模式(return)可以监听消息是否从交换机成功传递到队列, 使用方法如下:
1、生产者配置文件开启退回模式
spring: rabbitmq: host: 192.168.0.162 port: 5672 username: xiaotong password: xiaotong virtual-host: / # 开启确认模式 publisher-confirm-type: correlated # 开启回退模式 publisher-returns: true2、生产者定义退回模式的回调方法
@SpringBootTest public class ProducerTest { @Autowired private RabbitTemplate rabbitTemplate; @Test public void testReturn(){ // 定义退回模式的回调方法。交换机发送到队列失败后才会执行returnedMessage方法 rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() { /** * @param returned 失败后将失败信息封装到参数中 */ @Override public void returnedMessage(ReturnedMessage returned) { System.out.println("消息对象:"+returned.getMessage()); System.out.println("错误码:"+returned.getReplyCode()); System.out.println("错误信息:"+returned.getReplyText()); System.out.println("交换机:"+returned.getExchange()); System.out.println("路由键:"+returned.getRoutingKey()); // 处理消息... } }); rabbitTemplate.convertAndSend("my_topic_exchange","my_routing1","send message..."); } }
1. 在RabbitMQ中,退回模式可以监听 交换机是否成功传递到队列
2. 在RabbitMQ中,RabbitTemplate调用方法定义退回模式的回调方法 setReturnsCallback
消息的可靠性投递_Ack
在RabbitMQ中,消费者接收到消息后会向队列发送确认签收的消 息,只有确认签收的消息才会被移除队列。这种机制称为消费者消息确认(Consumer Acknowledge,简称Ack)。类似快递员派送 快递也需要我们签收,否则一直存在于快递公司的系统中。 消息分为自动确认和手动确认。自动确认指消息只要被消费者接收 到,无论是否成功处理消息,则自动签收,并将消息从队列中移 除。但是在实际开发中,收到消息后可能业务处理出现异常,那么消息就会丢失。此时需要设置手动签收,即在业务处理成功再通知签收消息,如果出现异常,则拒签消息,让消息依然保留在队列当中。
1、消费者配置开启手动签收
spring: rabbitmq: host: 192.168.0.162 port: 5672 username: xiaotong password: xiaotong virtual-host: / # 开启手动签收 listener: simple: acknowledge-mode: manual2、消费者处理消息时定义手动签收和拒绝签收的情况
@Component public class AckConsumer { @RabbitListener(queues = "my_queue") public void listenMessage(Message message, Channel channel) throws IOException, InterruptedException { // 消息投递序号,消息每次投递该值都会+1 long deliveryTag = message.getMessageProperties().getDeliveryTag(); try { int i = 1/0; //模拟处理消息出现bug System.out.println("成功接受到消息:"+message); // 签收消息 /** * 参数1:消息投递序号 * 参数2:是否一次可以签收多条消息 */ channel.basicAck(deliveryTag,true); }catch (Exception e){ System.out.println("消息消费失败!"); Thread.sleep(2000); // 拒签消息 /** * 参数1:消息投递序号 * 参数2:是否一次可以拒签多条消息 * 参数3:拒签后消息是否重回队列 */ channel.basicNack(deliveryTag,true,true); } } }
1. 在RabbitMQ中,Ack可以监听 消费者是否成功消费消息
2. 在RabbitMQ中,使用Ack手动签收一般在代码中的表现形式为 在 try 中签收消息,在 catch 中拒签消息
RabbitMQ高级特性_消费端限流
之前我们讲过MQ可以对请求进行“削峰填谷”,即通过消费端限流的 方式限制消息的拉取速度,达到保护消费端的目的。
消费端限流的写法如下:
1、生产者批量发送消息
@Test public void testSendBatch() { // 发送十条消息 for (int i = 0; i < 10; i++) { rabbitTemplate.convertAndSend("my_topic_exchange", "my_routing", "send message..."+i); } }2、消费端配置限流机制
spring: rabbitmq: host: 192.168.0.162 port: 5672 username: xiaotong password: xiaotong virtual-host: / listener: simple: # 限流机制必须开启手动签收 acknowledge-mode: manual # 消费端最多拉取5条消息消费,签收后不满5条才会继续拉取消息。 prefetch: 53、消费者监听队列
@Component public class QosConsumer{ @RabbitListener(queues = "my_queue") public void listenMessage(Message message, Channel channel) throws IOException, InterruptedException { // 1.获取消息 System.out.println(new String(message.getBody())); // 2.模拟业务处理 Thread.sleep(3000); // 3.签收消息 channel.basicAck(message.getMessageProperties().getDeliveryTag(), true); } }
1. 在RabbitMQ中,使用消费端限流必须开启 手动签收消息
RabbitMQ高级特性_利用限流实现不公平分发
在RabbitMQ中,多个消费者监听同一条队列,则队列默认采用的轮询分发。但是在某种场景下这种策略并不是很好,例如消费者1处 理任务的速度非常快,而其他消费者处理速度却很慢。此时如果采用公平分发,则消费者1有很大一部分时间处于空闲状态。此时可以采用不公平分发,即谁处理的快,谁处理的消息多。
使用方法如下:
1、生产者批量发送消息
@Test public void testSendBatch() { // 发送十条消息 for (int i = 0; i < 10; i++) { rabbitTemplate.convertAndSend("my_topic_exchange", "my_routing", "send message..."+i); } }2、消费端配置不公平分发
spring: rabbitmq: host: 192.168.0.162 port: 5672 username: xiaotong password: xiaotong virtual-host: / listener: simple: # 限流机制必须开启手动签收 acknowledge-mode: manual # 消费端最多拉取1条消息消费,这样谁处理的快谁拉取下一条消息,实现了不公平分发 prefetch: 13、编写两个消费者
@Component public class UnfairConsumer { // 消费者1 @RabbitListener(queues = "my_queue") public void listenMessage1(Message message, Channel channel) throws Exception { //1.获取消息 System.out.println("消费者1:"+new String(message.getBody(),"UTF-8")); //2. 处理业务逻辑 Thread.sleep(500); // 消费者1处理快 //3. 手动签收 channel.basicAck(message.getMessageProperties().getDeliveryTag(),true); } // 消费者2 @RabbitListener(queues = "my_queue") public void listenMessage2(Message message, Channel channel) throws Exception { //1.获取消息 System.out.println("消费者2:"+new String(message.getBody(),"UTF-8")); //2. 处理业务逻辑 Thread.sleep(3000);// 消费者2处理慢 //3. 手动签收 channel.basicAck(message.getMessageProperties().getDeliveryTag(),true); } }
1. 在RabbitMQ中,实现不公平分发需要 将消费端限流为1
RabbitMQ高级特性_消息存活时间
RabbitMQ可以设置消息的存活时间(Time To Live,简称TTL), 当消息到达存活时间后还没有被消费,会被移出队列。RabbitMQ 可以对队列的所有消息设置存活时间,也可以对某条消息设置存活时间。
设置队列所有消息存活时间
1、在创建队列时设置其存活时间:
@Configuration public class RabbitConfig2 { private final String EXCHANGE_NAME="my_topic_exchange2"; private final String QUEUE_NAME="my_queue2"; // 1.创建交换机 @Bean("bootExchange2") public Exchange getExchange2(){ return ExchangeBuilder .topicExchange(EXCHANGE_NAME) .durable(true). build(); } // 2.创建队列 @Bean("bootQueue2") public Queue getMessageQueue2(){ return QueueBuilder .durable(QUEUE_NAME) .ttl(10000) //队列的每条消息存活10s .build(); } // 3.将队列绑定到交换机 @Bean public Binding bindMessageQueue2(@Qualifier("bootExchange2") Exchange exchange,@Qualifier("bootQueue2") Queue queue){ return BindingBuilder.bind(queue).to(exchange).with("my_routing").noargs(); } }2、生产者批量生产消息,测试存活时间
@Test public void testSendBatch2() throws InterruptedException { // 发送十条消息 for (int i = 0; i < 10; i++) { rabbitTemplate.convertAndSend("my_topic_exchange2", "my_routing", "send message..."+i); } }
1. 在RabbitMQ中,当消息到达存活时间后还没有被消费,则 会被自动清除
2. 以下关于RabbitMQ的说法,正确的是 可以对队列的所有消息设置存活时间,也可以对某条消息设置 存活时间
设置单条消息存活时间
@Test
public void testSendMessage() {
//设置消息属性
MessageProperties messageProperties = new MessageProperties();
//设置存活时间
messageProperties.setExpiration("10000");
// 创建消息对象
Message message = new Message("send message...".getBytes(StandardCharsets.UTF_8), messageProperties);
// 发送消息
rabbitTemplate.convertAndSend("my_topic_exc hange", "my_routing", message);
}
注意:
1 如果设置了单条消息的存活时间,也设置了队列的存活时间,以时间短的为准。
2 消息过期后,并不会马上移除消息,只有消息消费到队列顶端时,才会移除该消息。
@Test public void testSendMessage2() { for (int i = 0; i < 10; i++) { if (i == 5) { // 1.创建消息属性 MessageProperties messageProperties = new MessageProperties(); // 2.设置存活时间 messageProperties.setExpiration("10000"); // 3.创建消息对象 Message message = new Message(("send message..." + i).getBytes(),messageProperties); // 4.发送消息 rabbitTemplate.convertAndSend("my_topic_exchange", "my_routing", message); } else { rabbitTemplate.convertAndSend("my_topic_exchange", "my_routing", "send message..." + i); } } }在以上案例中,i=5的消息才有过期时间,10s后消息并没有 马上被移除,但该消息已经不会被消费了,当它到达队列顶端时会被移除。
1. 在RabbitMQ中,如果设置了单条消息的存活时间,也设置了队 列的存活时间, 以时间短的为准
2. RabbitMQ消息过期后,只有消息在队列顶端时,才会被立即移除。
RabbitMQ高级特性_优先级队列
假设在电商系统中有一个订单催付的场景,即客户在一段时间内未 付款会给用户推送一条短信提醒,但是系统中分为大型商家和小型 商家。比如像苹果,小米这样大商家一年能给我们创造很大的利 润,所以在订单量大时,他们的订单必须得到优先处理,此时就需要为不同的消息设置不同的优先级,此时我们要使用优先级队列。
优先级队列用法如下:
1、创建队列和交换机
@Configuration public class RabbitConfig3 { private final String EXCHANGE_NAME="priority_exchange"; private final String QUEUE_NAME="priority_queue"; // 1.创建交换机 @Bean(EXCHANGE_NAME) public Exchange priorityExchange(){ return ExchangeBuilder .topicExchange(EXCHANGE_NAME) .durable(true). build(); } // 2.创建队列 @Bean(QUEUE_NAME) public Queue priorityQueue(){ return QueueBuilder .durable(QUEUE_NAME) //设置队列的最大优先级,最大可以设置到255,官网推荐不要超过10,,如果设置太高比较浪费资源 .maxPriority(10) .build(); } // 3.将队列绑定到交换机 @Bean public Binding bindPriority(@Qualifier(EXCHANGE_NAME) Exchange exchange, @Qualifier(QUEUE_NAME) Queue queue){ return BindingBuilder.bind(queue).to(exchange).with("my_routing").noargs(); } }2、编写生产者
@Test public void testPriority() { for (int i = 0; i < 10; i++) { if (i == 5) { // i为5时消息的优先级较高 MessageProperties messageProperties = new MessageProperties(); messageProperties.setPriority(9); Message message = new Message(("send message..." +i).getBytes(StandardCharsets.UTF_8), messageProperties); rabbitTemplate.convertAndSend("priority_exchange", "my_routing", message); } else { rabbitTemplate.convertAndSend("priority_exchange", "my_routing", "send message..." + i); } } }3、编写消费者
@Component public class PriorityConsumer { @RabbitListener(queues = "priority_queue") public void listenMessage(Message message, Channel channel) throws Exception { //获取消息 System.out.println(new String(message.getBody())); //手动签收 channel.basicAck(message.getMessageProperties().getDeliveryTag(),true); } }
1. 在RabbitMQ中,消息的优先级数值越大, 越先被消费
RabbitMQ死信队列_概念
在MQ中,当消息成为死信(Dead message)后,消息中间件可以 将其从当前队列发送到另一个队列中,这个队列就是死信队列。而 在RabbitMQ中,由于有交换机的概念,实际是将死信发送给了死 信交换机(Dead Letter Exchange,简称DLX)。死信交换机和死信队列和普通的没有区别。
在RabbitMQ中,什么情况下消息会成为死信
1、队列消息长度到达限制。
2、消费者拒签消息,并且不把消息重新放入原队列。
3、消息到达存活时间未被消费。
在RabbitMQ中,消息成为死信后,会 发送到死信交换机
RabbitMQ死信队列_代码实现
创建死信队列
@Configuration
public class RabbitConfig4 {
private final String DEAD_EXCHANGE = "dead_exchange";
private final String DEAD_QUEUE = "dead_queue";
private final String NORMAL_EXCHANGE = "normal_exchange";
private final String NORMAL_QUEUE = "normal_queue";
// 死信交换机
@Bean(DEAD_EXCHANGE)
public Exchange deadExchange(){
return ExchangeBuilder
.topicExchange(DEAD_EXCHANGE)
.durable(true)
.build();
}
// 死信队列
@Bean(DEAD_QUEUE)
public Queue deadQueue(){
return QueueBuilder
.durable(DEAD_QUEUE)
.build();
}
// 死信交换机绑定死信队列
@Bean
public Binding bindDeadQueue(@Qualifier(DEAD_EXCHANGE) Exchange exchange,@Qualifier(DEAD_QUEUE)Queue queue){
return BindingBuilder
.bind(queue)
.to(exchange)
.with("dead_routing")
.noargs();
}
// 普通交换机
@Bean(NORMAL_EXCHANGE)
public Exchange normalExchange(){
return ExchangeBuilder
.topicExchange(NORMAL_EXCHANGE)
.durable(true)
.build();
}
// 普通队列
@Bean(NORMAL_QUEUE)
public Queue normalQueue(){
return QueueBuilder
.durable(NORMAL_QUEUE)
.deadLetterExchange(DEAD_EXCHANGE) // 绑定死信交换机
.deadLetterRoutingKey("dead_routing") // 死信队列路由关键字
.ttl(10000) // 消息存活10s
.maxLength(10) // 队列最大长度为10
.build();
}
// 普通交换机绑定普通队列
@Bean
public Binding bindNormalQueue(@Qualifier(NORMAL_EXCHANGE) Exchange exchange,@Qualifier(NORMAL_QUEUE)Queue queue){
return BindingBuilder
.bind(queue)
.to(exchange)
.with("my_routing")
.noargs();
}
}
1. 在RabbitMQ中,普通队列绑定死信队列时,需要绑定 死信交换机和死信队列的路由关键字
测试死信队列
1、生产者发送消息
@Test public void testDlx(){ // 存活时间过期后变成死信 // rabbitTemplate.convertAndSend("normal_exchange","my_routing","测试死信"); // 超过队列长度后变成死信 // for (int i = 0; i < 20; i++) { // rabbitTemplate.convertAndSend("normal_exchange","my_routing","测试死信"); // } // 消息拒签但不返回原队列后变成死信 rabbitTemplate.convertAndSend("normal_exchange","my_routing","测试死信"); }2、消费者拒收消息
@Component public class DlxConsumer { @RabbitListener(queues = "normal_queue") public void listenMessage(Message message, Channel channel) throws IOException { // 拒签消息 channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,false); } }
RabbitMQ延迟队列_概念
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
例如:用户下单后,30分钟后查询订单状态,未支付则会取消订单。
但RabbitMQ中并未提供延迟队列功能,我们可以使用死信队列实现延迟队列的效果。
1. 延迟队列的特点是 只有到达指定时间后,才会被消费。
2. 在RabbitMQ中,我们可以用实现延迟队列 死信队列
RabbitMQ延迟队列_死信队列实现
接下来我们使用死信队列实现延迟队列
1、创建SpringBoot订单模块,添加SpringMVC、RabbitMQ、 lombok依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>2、编写配置文件
spring: rabbitmq: host: 192.168.0.162 port: 5672 username: xiaotong password: xiaotong virtual-host: / # 日志格式 logging: pattern: console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'3、创建队列和交换机
@Configuration public class RabbitConfig { // 订单交换机和队列 private final String ORDER_EXCHANGE = "order_exchange"; private final String ORDER_QUEUE = "order_queue"; // 过期订单交换机和队列 private final String EXPIRE_EXCHANGE = "expire_exchange"; private final String EXPIRE_QUEUE = "expire_queue"; // 过期订单交换机 @Bean(EXPIRE_EXCHANGE) public Exchange deadExchange(){ return ExchangeBuilder .topicExchange(EXPIRE_EXCHANGE) .durable(true) .build(); } // 过期订单队列 @Bean(EXPIRE_QUEUE) public Queue deadQueue(){ return QueueBuilder .durable(EXPIRE_QUEUE) .build(); } // 将过期订单队列绑定到交换机 @Bean public Binding bindDeadQueue(@Qualifier(EXPIRE_EXCHANGE) Exchange exchange,@Qualifier(EXPIRE_QUEUE) Queue queue){ return BindingBuilder .bind(queue) .to(exchange) .with("expire_routing") .noargs(); } // 订单交换机 @Bean(ORDER_EXCHANGE) public Exchange normalExchange(){ return ExchangeBuilder .topicExchange(ORDER_EXCHANGE) .durable(true) .build(); } // 订单队列 @Bean(ORDER_QUEUE) public Queue normalQueue(){ return QueueBuilder .durable(ORDER_QUEUE) .ttl(10000) // 存活时间为10s,模拟30min .deadLetterExchange(EXPIRE_EXCHANGE) // 绑定死信交换机 .deadLetterRoutingKey("expire_routing") //死信交换机的路由关键字 .build(); } // 将订单队列绑定到交换机 @Bean public Binding bindNormalQueue(@Qualifier(ORDER_EXCHANGE) Exchange exchange,@Qualifier(ORDER_QUEUE) Queue queue){ return BindingBuilder .bind(queue) .to(exchange) .with("order_routing") .noargs(); } }4、编写下单的控制器方法,下单后向订单交换机发送消息
@RestController public class OrderController { @Autowired private RabbitTemplate rabbitTemplate; //下单 @GetMapping("/place/{orderId}") public String placeOrder(@PathVariable String orderId){ System.out.println("处理订单数据..."); // 将订单id发送到订单队列 rabbitTemplate.convertAndSend("order_exchange", "order_routing", orderId); return "下单成功,修改库存"; } }5、编写监听死信队列的消费者
// 过期订单消费者 @Component public class ExpireOrderConsumer { // 监听队列 @RabbitListener(queues = "expire_queue") public void listenMessage(String orderId){ System.out.println("查询"+orderId+"号订单的状态,如果已支付则无需处理,如果未支付则需要回退库存"); } }6、下单测试:访问http://localhost:8080/place/10001
RabbitMQ延迟队列_插件实现
在使用死信队列实现延迟队列时,会遇到一个问题:RabbitMQ只 会移除队列顶端的过期消息,如果第一个消息的存活时长较长,而 第二个消息的存活时长较短,则第二个消息并不会及时执行。
RabbitMQ虽然本身不能使用延迟队列,但官方提供了延迟队列插件,安装后可直接使用延迟队列。
安装延迟队列插件
1、使用rz将插件上传至虚拟机
2、安装插件
# 将插件放入RabbitMQ插件目录中 mv rabbitmq_delayed_message_exchange3.9.0.ez /usr/local/rabbitmq/plugins/ # 启用插件 rabbitmq-plugins enable rabbitmq_delayed_message_exchange3 、重启RabbitMQ服务
#停止rabbitmq rabbitmqctl stop #启动rabbitmq rabbitmq-server restart -detached此时登录管控台可以看到交换机类型多了延迟消息
使用延迟队列
1、创建延迟交换机和延迟队列
@Configuration public class RabbitConfig2 { private final String DELAYED_EXCHANGE = "delayed_exchange"; private final String DELAYED_QUEUE = "delayed_queue"; //1.延迟交换机 @Bean(DELAYED_EXCHANGE) public Exchange delayedExchange() { // 创建自定义交换机 Map<String, Object> args = new HashMap<>(); args.put("x-delayed-type", "topic"); // topic类型的延迟交换机 return new CustomExchange(DELAYED_EXCHANGE, "x-delayed-message", true, false, args); } //2.延迟队列 @Bean(DELAYED_QUEUE) public Queue delayedQueue() { return QueueBuilder .durable(DELAYED_QUEUE) .build(); } // 3.绑定 @Bean public Binding bindingDelayedQueue(@Qualifier(DELAYED_QUEUE) Queue queue,@Qualifier(DELAYED_EXCHANGE) Exchange exchange) { return BindingBuilder.bind(queue).to(exchange).with("order_routing").noargs(); } }2、编写下单的控制器方法
@GetMapping("/place2/{orderId}") public String placeOrder2(@PathVariable String orderId) { System.out.println("处理订单数据..."); // 设置消息延迟时间为10秒 MessagePostProcessor messagePostProcessor = new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { message.getMessageProperties().setDelay(10000); return message; } }; // 将订单id发送到订单队列 rabbitTemplate.convertAndSend("delayed_exchange", "order_routing", orderId,messagePostProcessor); return "下单成功,修改库存"; }3、编写延迟队列的消费者
@RabbitListener(queues = "delayed_queue") public void listenMessage(String orderId){ System.out.println("查询"+orderId+"号订 单的状态,如果已支付则无需处理,如果未支付则需要回退库存"); }4、下单测试:访问http://localhost:8080/place/10002
1. 在RabbitMQ中,使用延迟插件后,延迟交换机的类型是 x-delayed-message
RabbitMQ集群_集群搭建
在生产环境中,当单台RabbitMQ服务器无法满足消息的吞吐量及 安全性要求时,需要搭建RabbitMQ集群。
1、设置两个RabbitMQ服务
# 关闭RabbitMQ服务 rabbitmqctl stop # 设置服务一 RABBITMQ_NODE_PORT=5673 RABBITMQ_NODENAME=rabbit1 rabbitmq-server start -detached # 设置服务二 RABBITMQ_NODE_PORT=5674 RABBITMQ_SERVER_START_ARGS="- rabbitmq_management listener [{port,15674}]" RABBITMQ_NODENAME=rabbit2 rabbitmq-server start -detached2、将两个服务设置到同一集群中
# 关闭服务2 rabbitmqctl -n rabbit2 stop_app # 重新设置服务2 rabbitmqctl -n rabbit2 re # 将服务2加入服务1中 rabbitmqctl -n rabbit2 join_cluster rabbit1@localhost # 启动服务2 rabbitmqctl -n rabbit2 start_app
1. 单机版的RabbitMQ服务无法满足真实应用的要求时,应当 搭建RabbitMQ集群
RabbitMQ集群_镜像队列
搭建了集群后,虽然多个节点可以互相通信,但队列只保存在了一 个节点中,如果该节点故障,则整个集群都将丢失消息。
# 关闭服务1
rabbitmqctl -n rabbit1 stop_app
此时我们需要引入镜像队列机制,它可以将队列消息复制到集群中的其他节点上。如果一个节点失效了,另一个节点上的镜像可以保证服务的可用性。
在管控台点击 Admin ---> Policies 设置镜像队列
此时某个节点故障则不会影响整个集群。
1. 在RabbitMQ集群中,通过设置可以为队列创建副本 镜像队列
RabbitMQ集群_负载均衡
无论是生产者还是消费者,只能连接一个RabbitMQ节点,而在我 们使用RabbitMQ集群时,如果只连接一个RabbitMQ节点,会造成 该节点的压力过大。我们需要平均的向每个RabbitMQ节点发送请 求,此时需要一个负载均衡工具帮助我们分发请求,接下来使用 Haproxy做负载均衡:
1、安装Haproxy
yum -y install haproxy2、配置Haproxy
vim /etc/haproxy/haproxy.cfg添加如下内容:
# 以下为修改内容 defaults # 修改为tcp mode tcp # 以下为添加内容 listen rabbitmq_cluster # 对外暴露端口 bind 0.0.0.0:5672 mode tcp balance roundrobin # 代理RabbitMQ的端口 server node1 127.0.0.1:5673 check inter 5000 rise 2 fall 2 server node2 127.0.0.1:5674 check inter 5000 rise 2 fall 2 listen stats # Haproxy控制台路径 bind 192.168.0.162:8100 mode http option httplog stats enable stats uri /rabbitmq-stats stats refresh 5s3、启动Haproxy
haproxy -f /etc/haproxy/haproxy.cfg4、访问Haproxy控制台:http://192.168.0.162:8100/rabbitmq-st ats
5、生产者连接Haproxy发送消息
// 生产者 public class Producer { public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.0.162"); connectionFactory.setPort(5672); connectionFactory.setUsername("guest"); connectionFactory.setPassword("guest"); connectionFactory.setVirtualHost("/"); Connection conn = connectionFactory.newConnection(); Channel channel = conn.createChannel(); channel.queueDeclare("simple_queue", false, false, false, null); channel.basicPublish("", "simple_queue", null,"hello!rabbitmq!".getBytes()); channel.close(); conn.close(); } }
1. 在RabbitMQ集群中,通过设置可以帮助我们给不同节点分发请求 负载均衡