RabbitMQ学习笔记
RabbitMQ
概述
RabbitMQ是实现了高级消息队列协议(AMQP)
的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而群集和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。
AMPQ协议
AMQP,即Advanced Message Queuing Protocol(高级消息队列)
,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。
RabbitMQ安装
RabbitMQ
是基于Erlang
语言开发,要使用RabbitMQ
需要先安装Erlang
的语言环境
对于Erlang
的版本和RabbitMQ
的版本依赖有严格要求,具体可以参考:Erlang与RabbitMQ版本对应关系
特别注意点
:Erlang 24.x 版本,必须要求Centos 8 以上。
使用官方的云
方式安装
1、
在Centos 下执行,
# 使用在线安装 Erlang 方式,设置安装源
curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | sudo bash
2、
安装命令执行
# 安装 erlang
yum install erlang
3、
为安装RabbitMQ
和erlang
设置安装密钥
# 导入安装的密钥
rpm --import https://packagecloud.io/rabbitmq/erlang/gpgkey
rpm --import https://packagecloud.io/rabbitmq/rabbitmq-server/gpgkey
4、
准备安装的RabbitMQ
的执行脚本
curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.rpm.sh | sudo bash
5、
下载RabbitMQ-Server
这里给出下载路径并上传到Centos
,可以根据版本自行选择。
下载地址: https://www.rabbitmq.com/download.html
6、
准备安装执行需要的两个插件和密钥
# 密钥
rpm --import https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc
# 插件 epel-release
yum -y install epel-release
# 插件 socat
yum -y install socat
7、
将第5步下载的文件上传到Centos
并进行执行以下命令进行安装
# 注意安装包名称
rpm -ivh rabbitmq-server-3.8.16-1.el7.noarch.rpm
8、
启动rabbitmq的管理平台插件
rabbitmq-plugins enable rabbitmq_management
9、
启动RabbitMQ,并关闭防火墙
# 启动
systemctl start rabbitmq-server
# 查看启动状态
systemctl status rabbitmq-server
# 停止
systemctl stop rabbitmq-server
# 关闭防火墙
systemctl stop firewalld
访问测试 ip:15672,如下安装成功
下载安装包方式安装
1、
准备安装文件
RabbitMQ-Server
下载地址: https://www.rabbitmq.com/download.html
erlang
下载地址:https://github.com/rabbitmq/erlang-rpm/releases
特别注意点
:Erlang 24.x 版本,必须要求Centos 8 以上。
2、
上传到Centos
进行安装
# 解压 erlang
rpm -Uvh erlang-23.3.4.1-1.el7.x86_64.rpm
# 安装 erlang
yum install -y erlang
# 测试安装
erl -v
# 安装依赖 socat
yum install -y socat
# 解压rabbitmq
rpm -Uvh rabbitmq-server-3.8.16-1.el7.noarch.rpm
# 安装rabbitmq
yum -y install rabbitmq
# 安装 rabbitmq-server
yum -y install rabbitmq-server
# 启动服务
systemctl start rabbit-server
# 关闭防火墙
systemctl stop firewalld
# 开启rabbitmq的管理平台插件
rabbitmq-plugins enable rabbitmq_management
访问测试即可
创建登录账户
# 创建账户和密码 第一个账户,第二个密码
rabbitmqctl add_user admin admin
# 给账户分配权限
rabbitmqctl set_user_tags admin administrator
# 可以单独配置权限
# 设置虚拟主机,也叫虚拟路径
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"
可分配权限说明
/*
user 有5种 tags :
management :访问 management plugin;
policymaker :访问 management plugin 和管理自己 vhosts 的策略和参数;
monitoring :访问 management plugin 和查看所有配置和通道以及节点信息;
administrator :一切权限;
None :无配置
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
none
不能访问 management plugin
management
用户可以通过AMQP做的任何事外加:
列出自己可以通过AMQP登入的virtual hosts
查看自己的virtual hosts中的queues, exchanges 和 bindings
查看和关闭自己的channels 和 connections
查看有关自己的virtual hosts的“全局”的统计信息,包含其他用户在这些virtual hosts中的活动。
policymaker
management可以做的任何事外加:
查看、创建和删除自己的virtual hosts所属的policies和parameters
monitoring
management可以做的任何事外加:
列出所有virtual hosts,包括他们不能登录的virtual hosts
查看其他用户的connections和channels
查看节点级别的数据如clustering和memory使用情况
查看真正的关于所有virtual hosts的全局的统计信息
administrator
policymaker和monitoring可以做的任何事外加:
创建和删除virtual hosts
查看、创建和删除users
查看创建和删除permissions
关闭其他用户的connections
*/
使用添加的账户登录:
使用Docker安装
# 安装rabbitmq和图形化界面,并配置登录账户和密码,开放对应端口
docker run -di --name rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 5672:5672 -p 15672:15672 rabbitmq:3-management
RabbitMQ的原理
RabbitMQ组成
RabbitMQ
的组成原理图,如下所示。
在了解运行原理图之前,我们先了解几个专有名词
Broker
:简单来说就是消息队列服务器实体。
Exchange
:消息交换机,它指定消息按什么规则,路由到哪个队列。
Queue
:消息队列载体,每个消息都会被投入到一个或多个队列。
Binding
:绑定,它的作用就是把exchange和queue按照路由规则绑定起来,Exchange 和Queue的绑定可以是多对多的关系。
Routing Key
:路由关键字,exchange根据这个关键字进行消息投递。
vhost
:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。
Producer
:消息生产者,就是投递消息的程序。
Consumer
:消息消费者,就是接受消息的程序。
Channel
:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。
运行原理
1、
生产者(Producer)通过连接通道(Channel),连接交换机(Exchanges),并对其发送消息
2、
交换机,通过绑定的路由规则(Routing Key),将接收到的消息发送到相应的队列(Queue)
3、
消费者(Consumer),通过连接通道(Channel),连接队列(Queue),并获取信息。
通信过程(AMQP原理)
假设生产者和消费者注册了相同的Broker
,Exchange
和Queue
。生产者发送的消息最终会被消费者消费。基本的通信流程大概如下所示:
1、
生产者生产消息,发送给服务器端的Exchange
2、
Exchange收到消息,根据ROUTINKEY
,将消息转发给匹配的Queue
3、
Queue收到消息,将消息发送给订阅者消费者
4、
消费者收到消息,发送ACK给队列确认收到消息
5、
Queue收到ACK,删除队列中缓存的此条消息
Consumer收到消息时需要显式的向rabbit broker发送basic.ack消息或者consumer订阅消息时设置auto_ack参数为true。在通信过程中,队列对ACK的处理有以下几种情况:
- 如果consumer接收了消息,发送ack,rabbitmq会删除队列中这个消息,发送另一条消息给consumer。
- 如果cosumer接受了消息, 但在发送ack之前断开连接,rabbitmq会认为这条消息没有被deliver,在consumer在次连接的时候,这条消息会被redeliver。
- 如果consumer接受了消息,但是程序中有bug,忘记了ack,rabbitmq不会重复发送消息。
- rabbitmq2.0.0和之后的版本支持consumer reject某条(类)消息,可以通过设置requeue参数中的reject为true达到目地,那么rabbitmq将会把消息发送给下一个注册的consumer。
交换机类型
RabbitMQ
主要包含四种交换机类型:
Direct exchange
:直连交换机,转发消息到routigKey指定的队列,如果消息的routigKey和binding的routigKey直接匹配的话,消息将会发送到该队列
Fanout exchange
:发布订阅交换机,转发消息到所有绑定队列(速度最快),不管消息的routigKey信息和binding的参数表头部信息和值是什么,消息将会发送到所有的队列
Topic exchange
:主题交换机,按规则转发消息(最灵活),如果消息的routigKey和binding的routigKey符合通配符匹配的话,消息将会发送到该队列
Headers exchange
:首部交换机 ,如果消息的头部信息和binding的参数表中匹配的话,消息将会发送到该队列。
RabbitMQ消息模型
RabbitMQ的消息模型主要有以下五种模型:
创建Maven
项目,并导入以下依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.12.0</version>
</dependency>
封装获取RabbitMQ
连接的工具类
package com.wyx.utils;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RabbitMQUtils {
public static Connection newConnection() {
//定义连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置RabbitMQ服务
factory.setHost("192.168.137.129");
// 3. 设置协议端口号
factory.setPort(5672);
// 4. 设置用户名
factory.setUsername("admin");
// 5. 设置密码
factory.setPassword("admin");
// 6. 设置vhost虚拟主机,类似于sql的库
factory.setVirtualHost("/");
// 7. 获取连接
Connection connection = null;
try {
connection = factory.newConnection();
} catch (IOException | TimeoutException e) {
try {
if (connection !=null) connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
e.printStackTrace();
}
return connection;
}
}
点对点模式
原理图
:
交换机
:direct
工作原理
:
点对点模式也称为一对一模式
。 一个生产者
投递消息给队列只能允许
有一个消费者
进行消费 ,如果集群的话会进行均摊消费 服务器配置不一样均摊就不一样
生产端
:
package com.wyx.deom;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
private static final String QUEUE_NAME="queue1";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1. 获得连接
connection = RabbitMQUtils.newConnection();
// 2. 创建通道
channel = connection.createChannel();
// 3. 创建队列声明
//通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 4. 发送消息
String msg="生产者发送的信息";
/**
* 消息发布方法
* param1:Exchange的名称,如果没有指定,则使用Default Exchange
* param2:routingKey,消息的路由Key,是用于Exchange(交换机)将消息转发到指定的消息队列
* param3:消息包含的属性
* param4:消息体
* 这里没有指定交换机,消息将发送给默认交换机,每个队列也会绑定那个默认的交换机,但是不能显示绑定或解除绑定
* 默认的交换机,routingKey等于队列名称
* String exchange, String routingKey, BasicProperties props, byte[] body
*/
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
} catch (Exception e) {
e.printStackTrace();
}finally {
// 5. 关闭连接
try {
if(channel != null){
channel.close();
}
if (connection != null) connection.close();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
}
消费端
:
package com.wyx.deom;
import com.rabbitmq.client.*;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
public class Consumer {
//队列名字
private static final String QUEUE_NAME = "queue1";
public static void main(String[] args) throws Exception {
// 1. 获取连接
Connection connection = RabbitMQUtils.newConnection();
// 2. 获取通道
Channel channel = connection.createChannel();
//通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//3.获取消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
/**
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
(收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
* String consumerTag, Envelope envelope, BasicProperties properties, byte[] body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("接收到的消息:"+msg);
}
};
//4.监听队列
channel.basicConsume(QUEUE_NAME,true,defaultConsumer);
}
}
工作队列模式
原理图
:
交换机
:direct
工作原理
:
多个消费端消费
同一个队列中的消息,队列采用轮询的方式将消息是平均发送给消费者。一条消息只会被一个消费端接收,消费者在处理完某条消息后,才会收到下一条消息。
消费方式
:轮询 或 公平分发(能者多劳)
生产者
package com.wyx.work;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
private static final String QUEUE_NAME="queue1";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1. 获得连接
connection = RabbitMQUtils.newConnection();
// 2. 创建通道
channel = connection.createChannel();
// 3. 创建队列声明
//通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 4. 发送消息
for (int i = 0; i < 50; i++) {
String msg="生产者发送的信息"+i;
/**
* 消息发布方法
* param1:Exchange的名称,如果没有指定,则使用Default Exchange
* param2:routingKey,消息的路由Key,是用于Exchange(交换机)将消息转发到指定的消息队列
* param3:消息包含的属性
* param4:消息体
* 这里没有指定交换机,消息将发送给默认交换机,每个队列也会绑定那个默认的交换机,但是不能显示绑定或解除绑定
* 默认的交换机,routingKey等于队列名称
* String exchange, String routingKey, BasicProperties props, byte[] body
*/
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// 5. 关闭连接
try {
if(channel != null){
channel.close();
}
if (connection != null) connection.close();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
}
消费者1
package com.wyx.work;
import com.rabbitmq.client.*;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
public class Consumer1 {
//队列名字
private static final String QUEUE_NAME = "queue1";
public static void main(String[] args) throws Exception {
// 1. 获取连接
Connection connection = RabbitMQUtils.newConnection();
// 2. 获取通道
Channel channel = connection.createChannel();
//通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//3.获取消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
/**
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
(收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
* String consumerTag, Envelope envelope, BasicProperties properties, byte[] body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("接收到的消息:"+msg);
}
};
//4.监听队列
channel.basicConsume(QUEUE_NAME,true,defaultConsumer);
}
}
消费者2
package com.wyx.work;
import com.rabbitmq.client.*;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
public class Consumer2 {
//队列名字
private static final String QUEUE_NAME = "queue1";
public static void main(String[] args) throws Exception {
// 1. 获取连接
Connection connection = RabbitMQUtils.newConnection();
// 2. 获取通道
Channel channel = connection.createChannel();
//通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//3.获取消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
/**
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
(收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
* String consumerTag, Envelope envelope, BasicProperties properties, byte[] body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("接收到的消息:"+msg);
}
};
//4.监听队列
channel.basicConsume(QUEUE_NAME,true,defaultConsumer);
}
}
默认为轮询分发
:即每个消费者一人一条消息
,但是一般情况下,因为我们要提高系统的并发性
会通过设置能者多劳模式。
能者多劳模式
消费者1:
package com.wyx.work;
import com.rabbitmq.client.*;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
public class Consumer1 {
//队列名字
private static final String QUEUE_NAME = "queue1";
public static void main(String[] args) throws Exception {
// 1. 获取连接
Connection connection = RabbitMQUtils.newConnection();
// 2. 获取通道
Channel channel = connection.createChannel();
//通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.basicQos(1);
//3.获取消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
/**
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
(收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
* String consumerTag, Envelope envelope, BasicProperties properties, byte[] body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("接收到的消息:"+msg);
//这里是模拟,所有使用线程,正常情况下不能使用
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//4.监听队列
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
消费者2
package com.wyx.work;
import com.rabbitmq.client.*;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
public class Consumer2 {
//队列名字
private static final String QUEUE_NAME = "queue1";
public static void main(String[] args) throws Exception {
// 1. 获取连接
Connection connection = RabbitMQUtils.newConnection();
// 2. 获取通道
Channel channel = connection.createChannel();
//通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//修改一次只接收一条消息
channel.basicQos(1);
//3.获取消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
/**
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
(收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
* String consumerTag, Envelope envelope, BasicProperties properties, byte[] body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("接收到的消息:"+msg);
//这里是模拟,所有使用线程,正常情况下不能使用
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改位置
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//4.监听队列,修改第二个参数为false
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
发布订阅模式
原理图
:
交换机
:fanout
工作原理
:简单例子,用户注册时,要发送短信和邮件通知,如果是一个系统,就会通过先发送一个再发宁外一个系统耦合性增加,不利于程序并发,用发布订阅模式,只需要将使用两个队列(手机号码和邮箱),让一个交换机同时给两个队列发送消息即可,接收者也能同时接收。也可用做公众号订阅设计,
该模式下生产者并不是直接操作队列,而是将数据发送给交换机,由交换机将数据发送给与之绑定的队列。从运行结果中可以看到,两中类型的消费者(Email,Phone)都收到相同数量的消息。
生产者:
package com.wyx.PublishSubscribe;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
// 声明交换机名称
private final static String EXCHANGE_NAME = "exchange_fanout";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1. 获得连接
connection = RabbitMQUtils.newConnection();
// 2. 创建通道
channel = connection.createChannel();
// 3. 声明交换机类型(分发:发布/订阅模式)
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 4. 发送消息
for (int i = 0; i < 10; i++) {
String msg="生产者发送的信息"+i;
/**
* 消息发布方法
* param1:Exchange的名称,如果没有指定,则使用Default Exchange
* param2:routingKey,消息的路由Key,是用于Exchange(交换机)将消息转发到指定的消息队列
* param3:消息包含的属性
* param4:消息体
* 这里没有指定交换机,消息将发送给默认交换机,每个队列也会绑定那个默认的交换机,但是不能显示绑定或解除绑定
* 默认的交换机,routingKey等于队列名称
* String exchange, String routingKey, BasicProperties props, byte[] body
*/
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// 5. 关闭连接
try {
if(channel != null) channel.close();
if (connection != null) connection.close();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
}
Email消费者
package com.wyx.PublishSubscribe;
import com.rabbitmq.client.*;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
public class Consumer2 {
//交换机名称
private final static String EXCHANGE_NAME = "exchange_fanout";
//队列名字
private static final String QUEUE_NAME = "queue_email";
public static void main(String[] args) throws Exception {
// 1. 获取连接
Connection connection = RabbitMQUtils.newConnection();
// 2. 获取通道
Channel channel = connection.createChannel();
// 3. 声明交换机(分发:发布/订阅模式)
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 4. 通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 5. 将队列绑定到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
//修改一次只接收一条消息
channel.basicQos(1);
// 6.获取消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
/**
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
(收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
* String consumerTag, Envelope envelope, BasicProperties properties, byte[] body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("["+QUEUE_NAME+"]"+"接收到的消息:"+msg);
//这里是模拟,所有使用线程,正常情况下不能使用
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//4.监听队列,修改第二个参数为false
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
Phone消费者
package com.wyx.PublishSubscribe;
import com.rabbitmq.client.*;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
public class Consumer1 {
//交换机名称
private final static String EXCHANGE_NAME = "exchange_fanout";
//队列名字
private static final String QUEUE_NAME = "queue_phone";
public static void main(String[] args) throws Exception {
// 1. 获取连接
Connection connection = RabbitMQUtils.newConnection();
// 2. 获取通道
Channel channel = connection.createChannel();
// 3. 声明交换机(分发:发布/订阅模式)
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
// 4. 通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 5. 将队列绑定到交换机
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
//修改一次只接收一条消息
channel.basicQos(1);
// 6.获取消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
/**
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
(收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
* String consumerTag, Envelope envelope, BasicProperties properties, byte[] body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("["+QUEUE_NAME+"]"+"接收到的消息:"+msg);
//这里是模拟,所有使用线程,正常情况下不能使用
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//4.监听队列,修改第二个参数为false
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
路由模式
原理图
:
交换机
:direct
系统需要针对日志做分析,首先所有的日志级别的日志都需要保存,其次error日志级别的日志需要单独做处理。这时就可以使用路由模式来处理了,声明交换机使用路由模式,每个日志级别的日志对应一个路由(error,info,warning)。声明一个保存日志队列用于接受所有日志,绑定交换机并绑定所有路由。声明第二个队列用于处理error级别日志,绑定交换机且只绑定error路由。
路由模式是在使用交换机的同时,生产者指定路由发送数据,消费者绑定路由接受数据。与发布/订阅模式不同的是,发布/订阅模式只要是绑定了交换机的队列都会收到生产者向交换机推送过来的数据。而路由模式下加了一个路由设置,生产者向交换机发送数据时,会声明发送给交换机下的那个路由,只有当消费者的队列绑定了交换机并且声明了路由,才会收到数据。
简单解释:就是在发布订阅模式下添加路由规则,满足规则才发送信息。
生产者:
package com.wyx.Routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
// 声明交换机名称
private final static String EXCHANGE_NAME = "routing_exchange_direct";
//路由名称warning
private final static String ROUTING_KEY_WARNING = "warning";
//路由名称info
private final static String ROUTING_KEY_INFO = "info";
//路由名称error
private final static String ROUTING_KEY_ERROR = "error";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1. 获得连接
connection = RabbitMQUtils.newConnection();
// 2. 创建通道
channel = connection.createChannel();
// 3. 声明交换机类型
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 4. 发送消息
for (int i = 0; i < 5; i++) {
String msg="生产者发送的信息"+i;
/**
* 消息发布方法
* param1:Exchange的名称,如果没有指定,则使用Default Exchange
* param2:routingKey,消息的路由Key,是用于Exchange(交换机)将消息转发到指定的消息队列
* param3:消息包含的属性
* param4:消息体
* 这里没有指定交换机,消息将发送给默认交换机,每个队列也会绑定那个默认的交换机,但是不能显示绑定或解除绑定
* 默认的交换机,routingKey等于队列名称
* String exchange, String routingKey, BasicProperties props, byte[] body
*/
// 警告路由
channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY_WARNING,null,(msg+ROUTING_KEY_WARNING).getBytes());
// 信息路由
channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY_INFO,null,(msg+ROUTING_KEY_INFO).getBytes());
// 错误路由
channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY_ERROR,null,(msg+ROUTING_KEY_ERROR).getBytes());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// 5. 关闭连接
try {
if(channel != null) channel.close();
if (connection != null) connection.close();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
}
ErrorConsumer
package com.wyx.Routing;
import com.rabbitmq.client.*;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
public class ErrorConsumer {
// 声明交换机名称
private final static String EXCHANGE_NAME = "routing_exchange_direct";
//路由名称error
private final static String ROUTING_KEY_ERROR = "error";
//队列名称
private static final String QUEUE_NAME = "error_queue";
public static void main(String[] args) throws Exception {
// 1. 获取连接
Connection connection = RabbitMQUtils.newConnection();
// 2. 获取通道
Channel channel = connection.createChannel();
// 3. 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 4. 通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 5. 将队列绑定到交换机和指定路由
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME,ROUTING_KEY_ERROR);
//修改一次只接收一条消息
channel.basicQos(1);
// 6.获取消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
/**
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
(收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
* String consumerTag, Envelope envelope, BasicProperties properties, byte[] body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("["+QUEUE_NAME+"]"+"接收到的消息:"+msg);
// 手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//4.监听队列,修改第二个参数为false
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
InfoConsumer
package com.wyx.Routing;
import com.rabbitmq.client.*;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
public class InfoConsumer {
// 声明交换机名称
private final static String EXCHANGE_NAME = "routing_exchange_direct";
//路由名称info
private final static String ROUTING_KEY_INFO = "info";
//队列名称
private static final String QUEUE_NAME = "info_queue";
public static void main(String[] args) throws Exception {
// 1. 获取连接
Connection connection = RabbitMQUtils.newConnection();
// 2. 获取通道
Channel channel = connection.createChannel();
// 3. 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 4. 通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 5. 将队列绑定到交换机和指定路由
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME,ROUTING_KEY_INFO);
//修改一次只接收一条消息
channel.basicQos(1);
// 6.获取消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
/**
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
(收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
* String consumerTag, Envelope envelope, BasicProperties properties, byte[] body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("["+QUEUE_NAME+"]"+"接收到的消息:"+msg);
// 手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//4.监听队列,修改第二个参数为false
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
WarningConsumer
package com.wyx.Routing;
import com.rabbitmq.client.*;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
public class WarningConsumer {
// 声明交换机名称
private final static String EXCHANGE_NAME = "routing_exchange_direct";
//路由名称warning
private final static String ROUTING_KEY_WARNING = "warning";
//队列名称
private static final String QUEUE_NAME = "Warning_queue";
public static void main(String[] args) throws Exception {
// 1. 获取连接
Connection connection = RabbitMQUtils.newConnection();
// 2. 获取通道
Channel channel = connection.createChannel();
// 3. 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 4. 通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 5. 将队列绑定到交换机和指定路由
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME,ROUTING_KEY_WARNING);
//修改一次只接收一条消息
channel.basicQos(1);
// 6.获取消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
/**
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
(收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
* String consumerTag, Envelope envelope, BasicProperties properties, byte[] body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("["+QUEUE_NAME+"]"+"接收到的消息:"+msg);
// 手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//4.监听队列,修改第二个参数为false
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
主题模式
原理图
:
交换机
:topic
主题模式是路由模式的一种变体,实现的功能一样,但是对于路由规则,可以使用表达式来进行模糊分配,路由规则如下:
必须由一个英文句点号.
分隔的字符串(我们将被句点号.
分隔开的每一段独立的字符串称为一个单词),比如 "lazy.orange.fox"
。topics routingKey 中可以存在两种特殊字符*
与#
,用于做模糊匹配,其中*
用于匹配一个单词,#
用于匹配多个单词(可以是零个)。
消费者
package com.wyx.Topics;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
// 声明交换机名称
private final static String EXCHANGE_NAME = "exchange_topic";
//路由规则
private static final String ROUTING_KEY = "com.info.error";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1. 获得连接
connection = RabbitMQUtils.newConnection();
// 2. 创建通道
channel = connection.createChannel();
// 3. 声明交换机类型
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 4. 发送消息
for (int i = 0; i < 5; i++) {
String msg="生产者发送的信息"+i;
/**
* 消息发布方法
* param1:Exchange的名称,如果没有指定,则使用Default Exchange
* param2:routingKey,消息的路由Key,是用于Exchange(交换机)将消息转发到指定的消息队列
* param3:消息包含的属性
* param4:消息体
* 这里没有指定交换机,消息将发送给默认交换机,每个队列也会绑定那个默认的交换机,但是不能显示绑定或解除绑定
* 默认的交换机,routingKey等于队列名称
* String exchange, String routingKey, BasicProperties props, byte[] body
*/
channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY,null,msg.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// 5. 关闭连接
try {
if(channel != null) channel.close();
if (connection != null) connection.close();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
}
消费者
package com.wyx.Topics;
import com.rabbitmq.client.*;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
public class TopicConsumer {
// 声明交换机名称
private final static String EXCHANGE_NAME = "exchange_topic";
//队列名称
private static final String QUEUE_NAME = "*_topic_queue";
//路由规则
private static final String ROUTING_KEY_INFO = "com.*";
public static void main(String[] args) throws Exception {
// 1. 获取连接
Connection connection = RabbitMQUtils.newConnection();
// 2. 获取通道
Channel channel = connection.createChannel();
// 3. 声明交换机(分发:发布/订阅模式)
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 4. 通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 5. 将队列绑定到交换机和指定路由
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME,ROUTING_KEY_INFO);
//修改一次只接收一条消息
channel.basicQos(1);
// 6.获取消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
/**
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
(收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
* String consumerTag, Envelope envelope, BasicProperties properties, byte[] body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("["+QUEUE_NAME+"]"+"接收到的消息:"+msg);
// 手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//4.监听队列,修改第二个参数为false
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
消费者
package com.wyx.Topics;
import com.rabbitmq.client.*;
import com.wyx.utils.RabbitMQUtils;
import java.io.IOException;
public class TopicConsumer1 {
// 声明交换机名称
private final static String EXCHANGE_NAME = "exchange_topic";
//队列名称
private static final String QUEUE_NAME = "#_topic_queue";
//路由规则
private static final String ROUTING_KEY_INFO = "#.error";
public static void main(String[] args) throws Exception {
// 1. 获取连接
Connection connection = RabbitMQUtils.newConnection();
// 2. 获取通道
Channel channel = connection.createChannel();
// 3. 声明交换机(分发:发布/订阅模式)
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 4. 通道绑定队列
/**
* 声明队列,如果Rabbit中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
*/
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
// 5. 将队列绑定到交换机和指定路由
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME,ROUTING_KEY_INFO);
//修改一次只接收一条消息
channel.basicQos(1);
// 6.获取消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
/**
* 消费者接收消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重传标志
(收到消息失败后是否需要重新发送)
* @param properties
* @param body
* @throws IOException
* String consumerTag, Envelope envelope, BasicProperties properties, byte[] body
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg=new String(body,"utf-8");
System.out.println("["+QUEUE_NAME+"]"+"接收到的消息:"+msg);
// 手动应答
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//4.监听队列,修改第二个参数为false
channel.basicConsume(QUEUE_NAME,false,defaultConsumer);
}
}
SpringBoot整合RabbitMQ
1、
创建SpringBoot项目
2、
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3、
查看RabbitMQ的自动配置类 RabbitAutoConfiguration
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
}
由配置类可以看出:我们对RabbitMQ的所有配置均在 RabbitProperties
类中
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitProperties {
private static final int DEFAULT_PORT = 5672;
private static final int DEFAULT_PORT_SECURE = 5671;
/**
* RabbitMQ host. Ignored if an address is set.
*/
private String host = "localhost";
/**
* RabbitMQ port. Ignored if an address is set. Default to 5672, or 5671 if SSL is
* enabled.
*/
private Integer port;
/**
* Login user to authenticate to the broker.
*/
private String username = "guest";
/**
* Login to authenticate against the broker.
*/
private String password = "guest";
/**
* SSL configuration.
*/
private final Ssl ssl = new Ssl();
/**
* Virtual host to use when connecting to the broker.
*/
private String virtualHost;
/**
* Comma-separated list of addresses to which the client should connect. When set, the
* host and port are ignored.
*/
private String addresses;
/**
* Mode used to shuffle configured addresses.
*/
private AddressShuffleMode addressShuffleMode = AddressShuffleMode.NONE;
/**
* Requested heartbeat timeout; zero for none. If a duration suffix is not specified,
* seconds will be used.
*/
@DurationUnit(ChronoUnit.SECONDS)
private Duration requestedHeartbeat;
/**
* Number of channels per connection requested by the client. Use 0 for unlimited.
*/
private int requestedChannelMax = 2047;
/**
* Whether to enable publisher returns.
*/
private boolean publisherReturns;
/**
* Type of publisher confirms to use.
*/
private ConfirmType publisherConfirmType;
/**
* Connection timeout. Set it to zero to wait forever.
*/
private Duration connectionTimeout;
/**
* Continuation timeout for RPC calls in channels. Set it to zero to wait forever.
*/
private Duration channelRpcTimeout = Duration.ofMinutes(10);
/**
* Cache configuration.
*/
private final Cache cache = new Cache();
/**
* Listener container configuration.
*/
private final Listener listener = new Listener();
private final Template template = new Template();
private List<Address> parsedAddresses;
}
由上面类的类,我们可以看出需要配置和 rabbitmq
相关的信息,只需要将在application.yaml
中以 spring.rabbitmq
开头即可,下面我们就配置属性来连接RabbitMQ
spring:
rabbitmq:
host: 192.168.137.130
port: 5672
username: admin
password: admin
继续查看 RabbitAutoConfiguration
,给我们注入了 RabbitTemplate
和 AmqpAdmin
注入这两个组件的作用
RabbitTemplate
:可以发送消息、接收消息。
AmqpAdmin
:操作Exchange、Queue、Binding等,比如创建、删除、解绑。
设置交换机和队列
根据RabbitMQ
的规则,我们知道想要使用RabbitMQ
我们必须有交换机和队列。那么下面我们将会介绍三种创建交换机和队列的方法。
1、
使用AmqpAdmin
来创建
package com.wyx;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ExchangeQueue {
@Autowired
AmqpAdmin amqpAdmin;
@Test
void exchangeQueue(){
/*
* 可以 new 的交换机分别为
* 1、DirectExchange 直连交换机
* 2、FanoutExchange 发布订阅模式交换机
* 3、TopicExchange 主题模式交换机
* 4、HeaderExchange 头信息模式交换机
* 5、CustomExchange 自定义模式交换机
* 第一个参数、交换机名
* 第二个参数、是否持久化
* 第三个参数、是否自动删除
* */
//创建交换机
System.out.println(amqpAdmin);
amqpAdmin.declareExchange(new DirectExchange("declare.exchange",true,false));
//创建队列
amqpAdmin.declareQueue(
/*
* 参数一、队列名,
* 参数二、是否持久化,
* 参数三、exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
* 参数四、autoDelete:默认是false,是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
* */
new Queue("declare.queue",true)
);
// 绑定队列和交换机
amqpAdmin.declareBinding(
/*
* 参数一、目的地,一般是队列名,可以是交换机
* 参数二、绑定类型,可以是队列,或者交换机 Binding.DestinationType.QUEUE、Binding.DestinationType.EXCHANGE
* 参数三、交换机名
* 参数四、是否携带其他参数类型为 map
* */
new Binding("declare.queue", Binding.DestinationType.QUEUE,
"declare.exchange","",null)
);
/*
* amqpAdmin 其他方法
* amqpAdmin.deleteExchange(); 移除交换机
amqpAdmin.deleteQueue(); 移除队列
amqpAdmin.removeBinding(); 移除绑定规则
amqpAdmin.getQueueInfo(); 获取队列信息
amqpAdmin.getQueueProperties(); 获取队列配置信息
* */
System.out.println(amqpAdmin.getQueueInfo("declare.queue"));
//QueueInformation [name=declare.queue, messageCount=0, consumerCount=0]
System.out.println(amqpAdmin.getQueueProperties("declare.queue"));
//{QUEUE_CONSUMER_COUNT=0, QUEUE_MESSAGE_COUNT=0, QUEUE_NAME=declare.queue}
}
}
2、
使用Spring容器
创建
将需要的东西注入容器,如果队列或交换机存在,容器会停止创建。
package com.wyx.conf.rabbit;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ExchangesQueueConf {
@Bean
DirectExchange directExchange(){
/*
* 参数一、交换机名
* 参数二、是否持久化
* 参数三、是否自动删除
* */
return new DirectExchange("directExchange",true,false);
}
@Bean
Queue queue1(){
/*
* 参数一、队列名,
* 参数二、是否持久化,
* 参数三、exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
* 参数四、autoDelete:默认是false,是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
* */
return new Queue("queue1",true);
}
@Bean
Binding binding(){
// 绑定队列
return BindingBuilder.bind(queue1()).to(directExchange()).withQueueName();
}
}
3、
第三种方式,我们不需要建立交换机,我们只需要在消费者一端使用注解连接需要监听的交换机或队列和路由
,然后再生产者中直接发送消息到对应的交换机,当消费者存在时
,交换机和队列,路由会自动创建
。具体可以看
消息的发送与接收
package com.wyx;
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.Arrays;
import java.util.HashMap;
import java.util.List;
@SpringBootTest
class SpringBootRabbitMqApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
List<Object> list = Arrays.asList(1, "hello,rabbitmq", "wangyuxing");
/*
* rabbitTemplate 发送消息主要有两种方法,
* send 需要自己定义一个Message,比较麻烦
*
* convertAndSend 需要传入一个Object,自动序列化发送给rabbitmq,object默认被当成消息体。
* convertAndSend 的参数
* 参数一:交换机
* 参数二:路由
* 参数三:对象
* */
// rabbitTemplate.convertAndSend("declare.exchange","declare.queue",list);
rabbitTemplate.convertAndSend("declare.queue",list);
/*
* receiveAndConvert 获取消息,一次只获取一个
* 这里没有使用监听,所以给的队列必须存在,否则会报错
* */
//System.out.println(rabbitTemplate.receiveAndConvert("declare.queue"));
}
}
发送完后,我们查看Web
管理界面,发现消息是乱码,这里是以为序列化的原因,我们需要自己配置序列化规则
配置序列化
package com.wyx.conf.rabbit;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
// 注入消息转换序列化使用 jackson2 来进行序列化
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
再次运行查看结果:
使用监听器监听消息
1、
再主启动类上添加自动配置rabbitmq
的注解
@EnableRabbit
2、
编写方法
package com.wyx.service;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class Consumer {
// 当消息队列中,有消息,那么它们便会取出来,只能是队列已经存在,不存在可以需要自定
@RabbitListener(queues = "declare.queue")
public void rabbitListener(List list){
System.out.println("接收到消息"+list);
}
}
具体还有很多关于 @RabbitListener
的使用方法,可以参考一下博客
https://www.cnblogs.com/yihuihui/p/12600797.html
RabbitMQ高级
过期消息
在RabbitMQ
中,消息可以设置过期时间,过期时间就相对于消息在规定的时间内,可以消费者消费,如果过了过期时间,那么将会移除消息。消费者就不可消费。
设置过期时间主要两种方法
设置过期队列
设置过期队列后,投递到队列中的消息,会在过期队列设置的时间后过期。
-
配置交换机和队列,并绑定
@Bean DirectExchange ttlDirectExchange(){ return new DirectExchange("ttlDirectExchange",true,false); } @Bean Queue ttlQueue(){ HashMap<String, Object> args = new HashMap<>(); /* * x-message-ttl 参数名来源于rabbitmq 控制台 * 还有很多参数,具体用法参考: * https://www.cnblogs.com/refuge/p/10354579.html * */ args.put("x-message-ttl",5000); /* * 参数一、队列名, * 参数二、是否持久化, * 参数三、exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable * 参数四、autoDelete:默认是false,是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。 * 参数五、一个map集合的参数存放,对队列一些参数描述 * */ return new Queue("ttl.direct.queue", true, false, false,args); } @Bean Binding ttlBinding(){ // 绑定队列 return BindingBuilder.bind(ttlQueue()).to(ttlDirectExchange()).with("ttl"); }
-
发送消息
@Autowired RabbitTemplate rabbitTemplate; @Test void ttlQueue(){ /* * 参数一:交换机 * 参数二:路由 * 参数三:对象 * */ List<Object> list = Arrays.asList(1, "hello,rabbitmq", "wangyuxing"); rabbitTemplate.convertAndSend("ttlDirectExchange","ttl",list); }
运行测试即可;
设置过期消息
除了可以设置过期消息队列外,我们也可以对消息设置过期时间,到达时间后自动过期,如果过期队列和过期消息同时设置,你们时间小的生效。
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
List<Object> list = Arrays.asList(1, "hello,rabbitmq", "wangyuxing");
/*
* rabbitTemplate 发送消息主要有两种方法,
* send 需要自己定义一个Message,比较麻烦
*
* convertAndSend 需要传入一个Object,自动序列化发送给rabbitmq,object默认被当成消息体。
* convertAndSend 的参数
* 参数一:交换机
* 参数二:路由
* 参数三:对象
* 参数四:MessagePostProcessor对象,用于设置消息属性
* */
MessagePostProcessor message = m -> {
// 设置消息的一些信息
m.getMessageProperties().setExpiration("5000");
m.getMessageProperties().setContentEncoding("UTF-8");
return m;
};
rabbitTemplate.convertAndSend("declare.queue","queue1",list,message);
}
死信队列
简单来说:就是当前队列中的信息过期后,如果不想直接丢掉,你们我们可以创建一个队列来存放丢掉的消息,
那么这个队列就称为死信队列。
死信队列生成的原因除了消息时间过期还有另外两种触发方式
- 消息被拒(使用手动应答机制拒绝接收消息)
- 队列长度达到最大不在接收消息
-
创建死信队列
@Bean DirectExchange DLEDirectExchange(){ return new DirectExchange("ttlDirectExchange",true,false); } @Bean Queue DLEQueue(){ return new Queue("dle.direct.queue", true, false, false); } @Bean Binding DLEBinding(){ // 绑定队列 return BindingBuilder.bind(DLEQueue()).to(DLEDirectExchange()).with("dle"); }
-
创建新的队列设置消息过期后需要绑定的死信队列
@Bean DirectExchange ttlDirectExchange(){ return new DirectExchange("ttlDirectExchange",true,false); } @Bean Queue ttlQueue(){ HashMap<String, Object> args = new HashMap<>(); args.put("x-message-ttl",5000); //过期时间 args.put("x-dead-letter-exchange","dleDirectExchange"); //绑定的死信交换机 args.put("x-dead-letter-routing-key","dle"); // 死信交换机的路由key /* * 参数一、队列名, * 参数二、是否持久化, * 参数三、exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable * 参数四、autoDelete:默认是false,是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。 * 参数五、一个map集合的参数存放,对队列一些参数描述 * */ return new Queue("ttl.direct.queue", true, false, false,args); } @Bean Binding ttlBinding(){ // 绑定队列 return BindingBuilder.bind(ttlQueue()).to(ttlDirectExchange()).with("ttl"); }
-
测试代码
@Test void ttlQueue(){ List<Object> list = Arrays.asList(1, "hello,rabbitmq", "wangyuxing"); rabbitTemplate.convertAndSend("ttlDirectExchange","ttl",list); }
延时队列
在 RabbitMQ 中延时队列属于一种特殊的队列模式
,简单来说就是让消息在一定时间类失效,并且转发到死信队列,在其原来的消息队列中不消费。用在定时发送消息的应用上
,用户比较简单,就不在说明。
延时队列优化
注意:延时队列最优方式,是创建一个普通的队列,然后将消息设置过期时间,在第一个队列中不被消费,延时一定时间后转到死信队列能很好的减少交互机的编写
延迟队列还有一种实现方式是基于插件的延时:
-
安装插件
# 切换到插件目录 [root@YuXingWang /]# cd /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.19/plugins/ # 将下载的文件放入其中 方法很多自行选择 # 启动插件 [root@YuXingWang plugins]# rabbitmq-plugins enable rabbitmq_delayed_message_exchange # 重启rabbitmq [root@master rabbitmq]# systemctl restart rabbitmq-server
-
查看web 界面
-
配置交换机和队列,并绑定
@Bean public Queue queue() { return new Queue("delayed.queue"); } //配置自定义交换机 @Bean CustomExchange customExchange() { Map<String, Object> args = new HashMap<>(); // 交互机类型 args.put("x-delayed-type", "direct"); //参数二为类型:必须是x-delayed-message return new CustomExchange("delayedExchange", "x-delayed-message", true, false, args); } //绑定队列到交换器 @Bean Binding binding(Queue queue, CustomExchange exchange) { return BindingBuilder.bind(queue).to(exchange).with("delayed.queue").noargs(); }
-
编写消息生产者
@Test public void send() { String msg = "插件延时队列"; rabbitTemplate.convertAndSend("delayedExchange", "delayed.queue", msg, message -> { message.getMessageProperties().setHeader("x-delay", 3000); // 消息过期的另外一种设置 单位毫秒 //message.getMessageProperties().setDelay(1000); return message; }); }
-
运行消息生产者查看,消息在交换机中停留三秒后才会发送到队列。测试完成
消息的发布确认
在RabbitMQ 中,假设因为某种原因,导致rabbitmq
服务不可用,那么会导致消息发送者发送的消息会消失不见,简单来说,就是消息发送不到交换机中,投递失败,那么这个给问题我们应该如何解决呢?
首先了解消息确认的机制:
事务机制
:我们在channel对象中可以看到 txSelect(),txCommit(),txrollback() 这些方法,分别对应着开启事务,提交事务,回滚。由于使用事务会造成生产者与Broker交互次数增加,造成性能资源的浪费
,而且事务机制是阻塞的,在发送一条消息后需要等待RabbitMq回应,之后才能发送下一条,因此事务机制不提倡RabbitMq使用事务进行消息确认的。
批量确认
:批量其实是一个节约资源的操作,但是在RabbitMq中我们使用批量操作会造成消息重复消费
,原因是批量操作是使客户端程序定期或者消息达到一定量,来调用方法等待Broker返回,这样其实是一个提高效率的做法,但是如果出现消息重发的情况,当前这批次的消息都需要重发,这就造成了重复消费,因此批量确认的操作性能没有提高反而下降。
异步确认
:异步确认虽然编程逻辑比上两个要复杂,但是性价比最高,无论是可靠性还是效率都没得说,他是利用回调函数来达到消息可靠性传递的,这个中间件也是通过函数回调来保证是否投递成功,下面就让我们来详细讲解异步确认是怎么实现的。
发送到交换机的消息确认:
-
在生产者中配置yaml
spring rabbitmq publisher-confirms: true
-
配置回调函数
@Component public class RabbitTemplateConfig implements RabbitTemplate.ConfirmCallback{ @Autowired private RabbitTemplate rabbitTemplate; @PostConstruct public void initRabbitTemplate() { // 设置生产者消息确认 rabbitTemplate.setConfirmCallback(this); } /** * 消息发送到 Broker 后触发回调,确认消息是否到达 Broker 服务器,也就是只确认是否正确到达 Exchange 中 * * @param correlationData * @param b * @param s */ @Override public void confirm(@Nullable CorrelationData correlationData, boolean b, @Nullable String s) { if (b) { System.out.println("消息到达rabbitmq服务器"); } else { System.out.println("消息可能未到达rabbitmq服务器"); } } }
-
在发送消息时添加
CorrelationData correlationData
参数即可
对于交换机和消息,也能配置应答,但是一般情况下只要绑定不错,那么基本上是不会出现问题,就不在解释,有兴趣可以到网上找资料看看
RabbitMQ 内存磁盘监控
在RabbitMQ 中,收到的消息会存储到内存和磁盘中,存放到内存的默认大小为本机内存的百分之四时,当内存存放的消息过多,并且为消费时,那么就可能找出内存溢出,达到上限值后,交换机将不在接收消息。
磁盘是防止重新启动时消息丢失而做的持久化,所以默认值也比较低。
自定义内存大小
- 命令设定
相对值设置 (取值0,1)
rabbitmqctl set_vm_memory_high_watermark 0.4
绝对值设置
rabbitmqctl set_vm_memory_high_watermark absolute 50MB
- 配置文件绑定
编辑配置文件:/etc/rabbitmq/rabbitmq.conf
# 相对值
vm_memory_high_watermark.relative = 0.4
# 绝对值
vm_memory_high_watermark.absolute = 50MB
自定义磁盘预警
-
命令方式
相对值设置 2 代表相对磁盘内存的百分之二
rabbitmq set_disk_free_limit memory_limit 2
绝对值设置
rabbitmq set_disk_free_limit 50MB
-
配置文件方式
相对值
disk_free_limit.relative = 2
绝对值
disk_free_limit.absolute = 50MB
RabbitMQ内存换页
什么是RabbitMQ的内存换页:简单来说,就是规定当消息保存在内存中到达多少值时,对其进行存盘操作,就是将其写入到磁盘中。
默认情况下,他的值是在大小最大内存的百分之50,也就一半是,将多余部分经行存盘操作,可以通过设置以下的值来经行设置阈值。
在配置文件中
vm_memory_high_watermark_paging_ratio=0.5
RabbitMQ 集群搭建
-
设置主机名
[root@localhost ~]# vim /etc/hostname
三台虚拟机分别设置为 master slave1 slave2
-
配置IP路径映射(三台服务器都一样)
[root@localhost ~]# vim /etc/hosts
192.168.100.129 master
192.168.100.130 slave1
192.168.100.131 slave2 -
重启三台服务器
-
安装RebbitMQ,参考前面()
-
将主节点的
.erlang.cookie
分发到另外的从节点.erlang.cookie 文件是在/var/lib/rabbitmq/ 目录下
# 同步到slave1 [root@master rabbitmq]# scp /var/lib/rabbitmq/.erlang.cookie root@slave1:/var/lib/rabbitmq/ # 同步到slave2 [root@master rabbitmq]# scp /var/lib/rabbitmq/.erlang.cookie root@slave2:/var/lib/rabbitmq/
-
三个节点重启服务
[root@master rabbitmq]# systemctl restart rabbitmq-server
-
三个节点打开rabbitmq监控插件
如果不打开的话,那么集群之间无法查看对方的数据,查看数据就是依靠web插件来实现的
[root@master rabbitmq]# rabbitmq-plugins enable rabbitmq_management
-
在主节点上添加用户登录信息及其权限,及其虚拟主机
# 添加用户 [root@master rabbitmq]# rabbitmqctl add_user admin 970699 Adding user "admin" ... Done. Don't forget to grant the user permissions to some virtual hosts! See 'rabbitmqctl help set_permissions' to learn more. # 设置虚拟主机,也叫虚拟路径 [root@master rabbitmq]# rabbitmqctl set_permissions -p / admin ".*" ".*" ".*" Setting permissions for user "admin" in vhost "/" ... # 设置用户权限 [root@master rabbitmq]# rabbitmqctl set_user_tags admin administrator Setting tags for user "admin" to [administrator] ...
-
将另外两个节点加入master集群中
简单说明:在rabbitmq中,一个节点也属于集群,可以又自己的集群信息
-
在 master 节点上使用如下命令查看信息
[root@master rabbitmq]# rabbitmqctl cluster_status Cluster status of node rabbit@master ... Basics # 记住这个值,后面需要 Cluster name: rabbit@master Disk Nodes rabbit@master Running Nodes rabbit@master Versions rabbit@master: RabbitMQ 3.9.2 on Erlang 23.3.4.5 # 还有一些为列出
重点记住
Cluster name
的值 -
将其他节点加入进来
# 在rabbitmq的集群中,其实最好只需要一个数据持久化的节点,其它节点都设置为内存节点,能更好的提高速度 # 停止节点 [root@slave1 lib]# rabbitmqctl stop_app Stopping rabbit application on node rabbit@slave1 ... # 重启设置 [root@slave1 lib]# rabbitmqctl reset Resetting node rabbit@slave1 ... # 将slave1 节点加入 master --ram代表该节点是否为数据持久化节点,不加默认就是持久化节点,rabbit@master就是上一步Cluster name的值 [root@slave1 lib]# rabbitmqctl join_cluster --ram rabbit@master Clustering node rabbit@slave1 with rabbit@master # 重启slave1 [root@slave1 lib]# rabbitmqctl start_app
-
另外一个节点的加入也一样。
-
切记:
集群后会需要多启动一个25672端口来保证集群直接的通讯,记得开发的端口又15672、25672、5672